我受到 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;
}
')
现在我们可以在单个基准测试中比较方法 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
如您所见,修改后的方法 test_blocks
比原来的方法快了大约 40%,甚至比 fst
包还要快一点。这是意料之中的,因为 fst
在存储列和表信息、(可能的)属性、散列和压缩信息方面有一些开销。
请注意,fst
和您的初始 test
方法之间的差异在我的系统上不那么明显,再次显示了使用基准优化系统的挑战。
关于c++ - 通过 C++ 与 fst 在 R 中将对象写入磁盘,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51702620/