在R到C++与fst中将对象写入磁盘

thc*_*thc 13 c++ r rcpp

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

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

使用自定义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();
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

Mar*_*lik 20

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

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

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

为了便于逐块写入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;
    }
')
Run Code Online (Sandbox Code Playgroud)

现在我们可以比较方法test,test_blocks并fst::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
Run Code Online (Sandbox Code Playgroud)

如您所见,修改后的方法test_blocks比原始方法快40%,甚至比fst包快一点.这是预期的,因为fst在存储列和表信息,(可能的)属性,散列和压缩信息方面存在一些开销.

请注意,我的系统fst与初始test方法之间的差异不太明显,再次显示使用基准测试来优化系统的挑战.