问题
我正在寻找一种快速(理想情况下是恒定时间)的方法来在 R 中获取一个长原始向量的大切片。例如:
obj <- raw(2^32)
obj[seq_len(2^31 - 1)]
即使使用 ALTREP,base R 也需要很长时间。
system.time(obj[seq_len(2^31 - 1)])
#> user system elapsed
#> 19.470 38.853 148.288
为什么?
因为我想加快速度storr
以加快速度drake
。我想storr
更快地保存长原始向量。writeBin()
速度非常快,但它仍然无法处理超过 2^31 - 1 bytes long 的向量。所以我想将数据保存在可管理的块中,如此处所述。这几乎可以工作,但是创建块太慢了,并且它在内存中复制了太多数据。
想法
让我们创建一个函数
slice_raw <- function(obj, from, to) {
# ???
}
这基本上相当于
obj[seq(from, to, by = 1L)]
在时间和内存上都是 O(1)。理论上,我们需要做的就是
- 传递
obj
给 C 函数。 - 创建一个指向 的第一个字节的新指针
obj
。 - 将新指针递增到切片的开头。
RAWSXP
在具有适当长度(小于 2^31 字节)的新指针处创建一个。- 返回
RAWSXP
.
我有 C 的背景,但我很难完全控制R 的内部结构。我想访问SEXP
s 中的 C 指针,这样我就可以进行基本的指针运算并从未修饰的 C 指针创建已知长度的 R 向量。我在 R 的 C 内部找到的资源似乎没有解释如何包装或解开指针。我们需要Rcpp
这个吗?
下面的粗略草图说明了我正在尝试做的事情。
library(inline)
sig <- c(
x = "raw", # Long raw vector with more than 2^31 - 1 bytes.
start = "integer", # Should probably be R_xlen_t.
bytes = "integer" # <= 2^31 - 1. Ideally coercible to R_xlen_t.
)
body <- "
Rbyte* result; // Just a reference. Want to avoid copying data.
result = RAW(x) + start; // Trying to do ordinary pointer arithmetic.
return asRaw(result); // Want to return a raw vector of length `bytes`.
"
slice_raw <- cfunction(sig = sig, body = body)
编辑:一些更多潜在的解决方法
感谢 Dirk 激发了我对此的思考。对于足够小的数据,我们可以使用fst
保存单列数据框,其中列是我们真正关心的原始向量。这种使用fst
比writeBin()
library(fst)
wrapper <- data.frame(actual_data = raw(2^31 - 1))
system.time(write_fst(wrapper, tempfile()))
#> user system elapsed
#> 0.362 0.019 0.103
system.time(writeBin(wrapper$actual_data, tempfile()))
#> user system elapsed
#> 0.314 1.340 1.689
由reprex 包(v0.3.0)于 2019 年 6 月 16 日创建
不幸的是,很难创建具有 2^31 或更多行的数据框。一个技巧是首先将原始向量转换为矩阵,我们避免了通常的整数溢出,因为 (2^31 - 1)^2 字节是几个艾字节。
library(fst)
x <- raw(2^32)
m <- matrix(x, nrow = 2^16, ncol = 2^16)
system.time(write_fst(as.data.frame(m), tempfile()))
#> user system elapsed
#> 8.776 1.459 9.519
由reprex 包(v0.3.0)于 2019 年 6 月 16 日创建
我们仍然在尘土中离开saveRDS()
,但我们不再击败writeBin()
。从数据框到矩阵的转换很慢,我不确定它是否能很好地扩展。
library(fst)
x <- raw(2^30)
m <- matrix(x, nrow = 2^15, ncol = 2^15)
system.time(write_fst(as.data.frame(m), tempfile()))
#> user system elapsed
#> 1.998 0.408 2.409
system.time(writeBin(as.raw(m), tempfile()))
#> user system elapsed
#> 0.329 0.839 1.397
由reprex 包(v0.3.0)于 2019 年 6 月 16 日创建
如果像 Dirk 建议的那样,我们可以使用 anR_xlen_t
来索引数据框的行,我们也许可以避免转换任何内容。