如何避免超大std::vector执行Rcpp::wrap时产生拷贝以加速C++到R的数据传输
问题描述
我有一个基于htslib实现的Rcpp函数,用于读取大小为1~20GB的大型BAM文件,读取过程中会生成多个超长std::vector,元素数量最高可达8000万。由于读取前无法预知元素总数,我无法直接使用Rcpp::IntegerVector和Rcpp::CharacterVector存储数据。据我了解,调用Rcpp::wrap对这些std::vector进行包装以便后续在R中使用时,会产生完整的数据拷贝。请问这种场景下有没有方法可以加速C++到R的数据传输?是否存在可以在Rcpp函数内创建、push_back元素的速度与std::vector相当、且可以按引用传递给R的数据结构?
当前实现代码
向量创建代码
std::vector<std::string> seq, xm; std::vector<int> rname, strand, start;
数据包装返回代码
Rcpp::IntegerVector w_rname = Rcpp::wrap(rname); w_rname.attr("class") = "factor"; w_rname.attr("levels") = chromosomes; // chromosomes存储BAM文件中参考序列的名称 Rcpp::IntegerVector w_strand = Rcpp::wrap(strand); w_strand.attr("class") = "factor"; w_strand.attr("levels") = strands; // std::vector<std::string> strands = {"+", "-"}; Rcpp::DataFrame res = Rcpp::DataFrame::create( Rcpp::Named("rname") = w_rname, Rcpp::Named("strand") = w_strand, Rcpp::Named("start") = start, Rcpp::Named("seq") = seq, Rcpp::Named("XM") = xm ); return(res);
编辑1(2021.10.19)
感谢大家的评论,我还需要更多时间验证stringfish是否适用,但我参考cpp11包的说明文档做了微调测试,将其与std::vector的性能做了对比。测试代码和结果如下,结果显示即便返回时需要执行Rcpp::wrap,std::vector<int>的运行速度依然更快:
Rcpp::cppFunction(' #include <Rcpp.h> using namespace Rcpp; //[[Rcpp::export]] std::vector<int> stdint_grow_(SEXP n_sxp) { R_xlen_t n = REAL(n_sxp)[0]; std::vector<int> x; R_xlen_t i = 0; while (i < n) { x.push_back(i++); } return x; }') library(cpp11test) grid <- expand.grid(len = 10 ^ (0:7), pkg = c("cpp11", "stdint"), stringsAsFactors = FALSE) b_grow <- bench::press(.grid = grid, { fun = match.fun(sprintf("%sgrow_", ifelse(pkg == "cpp11", "", paste0(pkg, "_")))) bench::mark( fun(len) ) } )[c("len", "pkg", "min", "mem_alloc", "n_itr", "n_gc")] print(b_grow, n=Inf)
测试结果:
# A tibble: 12 × 6 len pkg min mem_alloc n_itr n_gc <dbl> <chr> <bch:tm> <bch:byt> <int> <dbl> 1 100 cpp11 1.9µs 1.89KB 9999 1 2 1000 cpp11 6.1µs 16.03KB 9999 1 3 10000 cpp11 58.11µs 256.22KB 7267 12 4 100000 cpp11 488.15µs 2MB 815 11 5 1000000 cpp11 4.34ms 16MB 88 14 6 10000000 cpp11 97.39ms 256MB 4 5 7 100 stdint 1.6µs 2.93KB 10000 0 8 1000 stdint 3.36µs 6.45KB 9998 2 9 10000 stdint 19.87µs 41.6KB 9998 2 10 100000 stdint 181.88µs 393.16KB 2571 4 11 1000000 stdint 1.91ms 3.82MB 213 3 12 10000000 stdint 36.09ms 38.15MB 9 1
编辑2
该测试条件下,std::vector<std::string>的运行速度略慢于cpp11::writable::strings,但内存效率更高:
Rcpp::cppFunction(' #include <Rcpp.h> using namespace Rcpp; //[[Rcpp::export]] std::vector<std::string> stdstr_grow_(SEXP n_sxp) { R_xlen_t n = REAL(n_sxp)[0]; std::vector<std::string> x; R_xlen_t i = 0; while (i++ < n) { std::string s (i, 33); x.push_back(s); } return x; }') cpp11::cpp_source(code=' #include "cpp11/strings.hpp" [[cpp11::register]] cpp11::writable::strings cpp11str_grow_(R_xlen_t n) { cpp11::writable::strings x; R_xlen_t i = 0; while (i++ < n) { std::string s (i, 33); x.push_back(s); } return x; } ') library(cpp11test) grid <- expand.grid(len = 10 ^ (0:5), pkg = c("cpp11str", "stdstr"), stringsAsFactors = FALSE) b_grow <- bench::press(.grid = grid, { fun = match.fun(sprintf("%sgrow_", ifelse(pkg == "cpp11", "", paste0(pkg, "_")))) bench::mark( fun(len) ) } )[c("len", "pkg", "min", "mem_alloc", "n_itr", "n_gc")] print(b_grow, n=Inf)
测试结果:
# A tibble: 12 × 6 len pkg min mem_alloc n_itr n_gc <dbl> <chr> <bch:tm> <bch:byt> <int> <dbl> 1 1 cpp11str 1.22µs 0B 10000 0 2 10 cpp11str 3.02µs 0B 9999 1 3 100 cpp11str 22µs 1.89KB 9997 3 4 1000 cpp11str 765.28µs 541.62KB 602 2 5 10000 cpp11str 66.69ms 47.91MB 8 0 6 100000 cpp11str 6.83s 4.62GB 1 0 7 1 stdstr 1.38µs 2.49KB 10000 0 8 10 stdstr 1.86µs 2.49KB 10000 0 9 100 stdstr 16.44µs 3.32KB 10000 0 10 1000 stdstr 898.23µs 10.35KB 511 0 11 10000 stdstr 73.55ms 80.66KB 7 0 12 100000 stdstr 7.54s 783.79KB 1 0
解决方案(2022.01.12)
在此分享给有同类问题的开发者:我的实际场景中不需要在R环境内直接使用std::vector里的数据,因此XPtr可以轻松解决问题,将BAM文件加载耗时降低了近一半。
指针创建代码
std::vector<std::string>* seq = new std::vector<std::string>; std::vector<std::string>* xm = new std::vector<std::string>;
将指针存储为data.frame的属性
Rcpp::DataFrame res = Rcpp::DataFrame::create( Rcpp::Named("rname") = w_rname, Rcpp::Named("strand") = w_strand, Rcpp::Named("start") = start ); Rcpp::XPtr<std::vector<std::string>> seq_xptr(seq, true); res.attr("seq_xptr") = seq_xptr; Rcpp::XPtr<std::vector<std::string>> xm_xptr(xm, true); res.attr("xm_xptr") = xm_xptr;
其他位置复用数据的方法
Rcpp::XPtr<std::vector<std::string>> seq((SEXP)df.attr("seq_xptr")); Rcpp::XPtr<std::vector<std::string>> xm((SEXP)df.attr("xm_xptr"));
内容的提问来源于stack exchange,提问作者notch
相关产品推荐
相关产品推荐

