2

问题

我正在寻找一种快速(理想情况下是恒定时间)的方法来在 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)。理论上,我们需要做的就是

  1. 传递obj给 C 函数。
  2. 创建一个指向 的第一个字节的新指针obj
  3. 将新指针递增到切片的开头。
  4. RAWSXP在具有适当长度(小于 2^31 字节)的新指针处创建一个。
  5. 返回RAWSXP.

我有 C 的背景,但我很难完全控制R 的内部结构。我想访问SEXPs 中的 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保存单列数据框,其中列是我们真正关心的原始向量。这种使用fstwriteBin()

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来索引数据框的行,我们也许可以避免转换任何内容。

4

2 回答 2

1

有同样的挑战。这是完成任务的小 Rcpp 函数

Rcpp::RawVector raw_slice(
  const Rcpp::RawVector &x, 
  const R_xlen_t offset, 
  const R_xlen_t size) {

  Rcpp::RawVector result = Rcpp::no_init(size);
  memcpy ( &result[0], &x[offset - 1], size );
  return result;
}
于 2020-06-10T07:29:41.877 回答
1

尽管目前尚不能很好地支持具有长向量列的 data.frame,但您仍然可以使用它fst来序列化长原始向量:

# method for writing a raw vector to disk
write_raw <- function(x, path, compress = 50) {

  # create a list and add required attributes
  y <- list(X = x)
  attributes(y) <- c(attributes(y), class = "data.frame")

  # serialize and compress to disk
  fst::write_fst(y, path, compress)
}

# create raw vector of length >2^31
x <- rep(as.raw(0:255), 2^23 + 10)

# write raw vector
write_raw(x, "raw_vector.fst", 100)

使用此方案,无需将向量拆分为多个部分(正如您已经指出的那样,这将显着减慢序列化速度)。无需任何复制或切片即可重新读取原始向量:

# method for reading a raw vector from disk
read_raw <- function(path) {

  # read from disk
  z <- fst::read_fst(path)

  z$X
}

z <- read_raw("raw_vector.fst")

fst::hash_fst(x) == fst::hash_fst(z)
#> [1] TRUE TRUE

(请注意,目前您需要 fst 开发版本来支持长矢量阅读)

在您的设置中,您将始终将完整的原始向量作为一个整体序列化到磁盘(就像saveRDS().因为您不需要随机访问存储的向量,所以存储在 fst 文件中的元数据有点矫枉过正。您还可以测试一个设置,在该设置中使用压缩原始向量compress_fst(),然后使用saveRDS(raw_vec, compress = FALSE).

这种设置的优点是压缩器可以使用更大的块进行压缩,从而提高压缩比(效果可能很显着)。使用更大的块也可以加快压缩速度。

另一方面,缺点是您在写入磁盘期间没有像使用write_fst(). 而且您不再具有随机访问权限,但无论如何您并不真正需要它。

如果您实现了一个两步过程(首先压缩数据,然后对其进行序列化),那么如果用户愿意,您将能够允许使用不同的压缩器(例如,对于慢速磁盘具有非常高压缩比的较慢压缩器)。

于 2019-06-19T21:07:45.090 回答