草庐IT

c++ - 通过 C++ 与 fst 在 R 中将对象写入磁盘

coder 2023-06-03 原文

我受到 fst 包的启发,尝试编写一个 C++ 函数来快速将我在 R 中的一些数据结构序列化到磁盘。

但即使在非常简单的对象上,我也无法达到相同的写入速度。下面的代码是将一个 1 GB 的大 vector 写入磁盘的简单示例。

使用自定义 C++ 代码,我实现了 135 MB/s 的写入速度,这是我的磁盘根据 CrystalBench 的限制。

在相同的数据上,write_fst 实现了 223 MB/s 的写入速度,这似乎是不可能的,因为我的磁盘无法写入那么快。 (注意,我使用的是 fst::threads_fst(1)compress=0 设置,文件的数据大小相同。)

我错过了什么?

如何让 C++ 函数更快地写入磁盘?

C++ 代码:

#include <Rcpp.h>
#include <fstream>
#include <cstring>
#include <iostream>

// [[Rcpp::plugins(cpp11)]]

using namespace Rcpp;

// [[Rcpp::export]]
void test(SEXP x) {
  char* d = reinterpret_cast<char*>(REAL(x));
  long dl = Rf_xlength(x) * 8;
  std::ofstream OutFile;
  OutFile.open("/tmp/test.raw", std::ios::out | std::ios::binary);
  OutFile.write(d, dl);
  OutFile.close();
}

R 代码:

library(microbenchmark)
library(Rcpp)
library(dplyr)
library(fst)
fst::threads_fst(1)

sourceCpp("test.cpp")

x <- runif(134217728) # 1 gigabyte
df <- data.frame(x)

microbenchmark(test(x), write_fst(df, "/tmp/test.fst", compress=0), times=3)
Unit: seconds
                                         expr      min       lq     mean   median       uq      max neval
                                      test(x) 6.549581 7.262408 7.559021 7.975235 8.063740 8.152246     3
 write_fst(df, "/tmp/test.fst", compress = 0) 4.548579 4.570346 4.592398 4.592114 4.614307 4.636501     3

file.info("/tmp/test.fst")$size/1e6
# [1] 1073.742

file.info("/tmp/test.raw")$size/1e6
# [1] 1073.742

最佳答案

对 SSD 写入和读取性能进行基准测试是一项棘手的工作,而且很难做到正确。有很多影响需要考虑。

例如,许多 SSD 使用技术来加速数据速度(智能),例如 DRAM 缓存。这些技术可以提高您的写入速度,尤其是在将相同的数据集多次写入磁盘的情况下,例如您的示例。为避免这种影响,基准测试的每次迭代都应将唯一的数据集写入磁盘。

写入和读取操作的 block 大小也很重要:SSD 的默认物理扇区大小为 4KB。写入较小的 block 会影响性能,但使用 fst 我发现写入大于几 MB 的数据 block 也会降低性能,因为 CPU 缓存效应。因为 fst 以相对较小的 block 将其数据写入磁盘,所以它通常比在单个大块中写入数据的替代方案更快。

为了方便这种逐 block 写入 SSD,您可以修改您的代码:

Rcpp::cppFunction('

  #include <fstream>
  #include <cstring>
  #include <iostream>

  #define BLOCKSIZE 262144 // 2^18 bytes per block

  long test_blocks(SEXP x, Rcpp::String path) {
    char* d = reinterpret_cast<char*>(REAL(x));

    std::ofstream outfile;
    outfile.open(path.get_cstring(), std::ios::out | std::ios::binary);

    long dl = Rf_xlength(x) * 8;
    long nr_of_blocks = dl / BLOCKSIZE;

    for (long block_nr = 0; block_nr < nr_of_blocks; block_nr++) {
      outfile.write(&d[block_nr * BLOCKSIZE], BLOCKSIZE);
    }

    long remaining_bytes = dl % BLOCKSIZE;
    outfile.write(&d[nr_of_blocks * BLOCKSIZE], remaining_bytes);

    outfile.close();

    return dl;
    }
')

现在我们可以在单个基准测试中比较方法 testtest_blocksfst::write_fst:

x <- runif(134217728) # 1 gigabyte
df <- data.frame(X = x)

fst::threads_fst(1)  # use fst in single threaded mode

microbenchmark::microbenchmark(
  test(x, "test.bin"),
  test_blocks(x, "test.bin"),
  fst::write_fst(df, "test.fst", compress = 0),
  times = 10)
#> Unit: seconds
#>                                          expr      min       lq     mean
#>                           test(x, "test.bin") 1.473615 1.506019 1.590430
#>                    test_blocks(x, "test.bin") 1.018082 1.062673 1.134956
#>  fst::write_fst(df, "test.fst", compress = 0) 1.127446 1.144039 1.249864
#>    median       uq      max neval
#>  1.600055 1.635883 1.765512    10
#>  1.131631 1.204373 1.264220    10
#>  1.261269 1.327304 1.343248    10

如您所见,修改后的方法 test_blocks 比原来的方法快了大约 40%,甚至比 fst 包还要快一点。这是意料之中的,因为 fst 在存储列和表信息、(可能的)属性、散列和压缩信息方面有一些开销。

请注意,fst 和您的初始 test 方法之间的差异在我的系统上不那么明显,再次显示了使用基准优化系统的挑战。

关于c++ - 通过 C++ 与 fst 在 R 中将对象写入磁盘,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51702620/

有关c++ - 通过 C++ 与 fst 在 R 中将对象写入磁盘的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  3. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  4. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  5. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  6. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  7. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  8. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  9. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  10. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

随机推荐