【问题标题】:Vectorized version of charToRaw with good performance具有良好性能的 charToRaw 的矢量化版本
【发布时间】:2019-07-30 22:12:35
【问题描述】:

我想从字符向量中获取原始字节向量(应用加密函数,该函数需要原始字节作为 data.table 列的所有值的输入)。

charToRaw工作矢量化,但只处理矢量的第一个元素:

x <- c("hello", "my", "world")
charToRaw(x)
# Warning message:
# In charToRaw(x) : argument should be a character vector of length 1
# all but the first element will be ignored

charToRaw 是否有提供良好性能的矢量化版本?为什么base R的版本不提供矢量化版本?

我知道我可以使用 sapplymyapply 但我最终会在所有行上使用内部循环...

编辑 1: 结果应该是一个与“x”大小相同的向量,每个元素代表相应输入元素的原始字节。

编辑 2 + 3: 我的结果应该是这样的(例如,作为列表)

x.raw
[1] 68 65 6c 6c 6f
[2] 6d 79
[3] 77 6f 72 6c 64

问题在于 R 似乎不支持 raw 向量,因为 raw 本身就像字节向量...知道如何解决这个问题吗?

编辑 4 + 5:

我已经对当前的提案进行了基准测试:

library(microbenchmark)
x <- sample(c("hello", "my", "world"), 1E6, TRUE)
microbenchmark::microbenchmark(
  sapply_loop  = sapply(x, charToRaw),
  lapply_loop = lapply(x, charToRaw),
  vectorize_loop = { charToRawVec <-Vectorize(charToRaw, "x")
                     charToRawVec(x) },
  split = split(charToRaw(paste(x, collapse = "")), rep(seq_len(length(x)), nchar(x))),
  charToRaw_with_cpp = charToRaw_cpp(x),
  times = 5
)

@Brian 答案中的 Rcpp 解决方案比所有其他建议快 4 到 5 倍(取决于字符串的长度):

Unit: milliseconds
               expr       min        lq      mean    median        uq       max neval
        sapply_loop  761.6041 1149.7972 1153.1992 1202.6303 1306.2110 1345.7531     5
        lapply_loop  950.5337  972.1374 1172.4354 1134.9821 1300.4941 1504.0297     5
     vectorize_loop  951.9297  983.2725 1134.0204 1147.1145 1250.9649 1336.8201     5
              split 1201.5009 1275.7123 1409.3622 1425.0124 1529.5082 1615.0772     5
 charToRaw_with_cpp  111.7791  113.1815  313.5623  384.7327  466.9929  491.1253     5

【问题讨论】:

  • 一个使用split的选项:split(charToRaw(paste(x, collapse = "")), rep(seq_len(length(x)), nchar(x)))虽然没有测试性能。
  • 好主意!对一个字符使用多字节表示的编码可能会产生问题,但仍然是一种好方法!
  • 事实上我无法提供我的输出的dput,因为我不知道如何在向量中“嵌入”原始字节向量:-(
  • 是的,列表将是一个很好的结果类型。良好的性能是我的痛点(这就是为什么我要求charToRaw 的“矢量化”版本)
  • @markus 我尝试了很多变体,但split 步骤总是对性能造成巨大拖累。我认为矢量化并加快速度的唯一方法是 Rcpp。

标签: r vectorization


【解决方案1】:

这是一个将internal C source 用于charToRaw 的版本,没有任何错误检查。 Rcpp 中的循环应该尽可能快,尽管我不知道是否有更好的方法来处理内存分配。如您所见,与purrr::map 相比,您没有获得显着的性能提升,但比sapply 更好。

library(Rcpp)

Rcpp::cppFunction('List charToRaw_cpp(CharacterVector x) {
  int n = x.size();
  List l = List(n);

  for (int i = 0; i < n; ++i) {
    int nc = LENGTH(x[i]);
    RawVector ans = RawVector(nc);
    memcpy(RAW(ans), CHAR(x[i]), nc);
    l[i] = ans;
  }
  return l;
}')

# Random vector of 5000 strings of 5000 characters each
x <- unlist(purrr::rerun(5000, stringr::str_c(sample(c(letters, LETTERS), 5000, replace = T), collapse = "")))

microbenchmark::microbenchmark(
  sapply(x, charToRaw),
  purrr::map(x, charToRaw),
  charToRaw_cpp(x)
)
Unit: milliseconds
                    expr       min        lq      mean    median       uq       max neval cld
    sapply(x, charToRaw) 60.337729 69.313684 76.908557 73.232365 78.99251 398.00732   100   b
purrr::map(x, charToRaw)  8.849688  9.201125 17.117435  9.376843 10.09294 292.74068   100  a 
        charToRaw_cpp(x)  5.578212  5.827794  7.998507  6.151864  7.10292  23.81905   100  a

经过 1000 次迭代,您开始看到效果:

Unit: milliseconds
                    expr      min       lq      mean   median        uq      max neval cld
purrr::map(x, charToRaw) 8.773802 9.191173 13.674963 9.425828 10.602676 302.7293  1000   b
        charToRaw_cpp(x) 5.591585 5.868381  9.370648 6.119673  7.445649 295.1833  1000  a

关于性能的编辑说明:

我假设您会看到较大的字符串和向量在性能上的差异更大。但实际上到目前为止最大的区别是针对 50 个字符的 50 个长度的向量:

Unit: microseconds
                       expr    min     lq     mean median      uq     max neval cld
       sapply(x, charToRaw) 66.245 69.045 77.44593 70.288 72.4650 862.110   500   b
   purrr::map(x, charToRaw) 65.313 68.733 75.85236 70.599 72.7765 621.392   500   b
          charToRaw_cpp(x)  4.666  6.221  7.47512  6.844  7.7770  58.159   500  a

【讨论】:

  • 我现在已经为您对我的问题的回答添加了一个基准(优于一切:-) - 抱歉我的延迟回复。顺便说一句,很好的答案(显示了 Rcpp 的强度以及如果您了解 R 核心内部结构是多么容易;-)
【解决方案2】:

你可以使用Vectorize()来完成这个任务:

x <- c("hello", "my", "world")
charToRawVec <- Vectorize(FUN = charToRaw, vectorize.args = "x")
charToRawVec(x)

【讨论】:

  • 好主意。表现如何? Vectorize 函数只是行上的“循环”吗?那么我不希望有比使用sapplymapply 更好的性能...
  • Vectorizemapply 的包装器,我认为。
  • 我已经在“普通话”中添加了我想要的结果,但我的问题是 R 似乎不支持原始向量:-(
  • 我查看了“Vectorize”的源代码,它“只是”一个方便的包装器,并且确实在内部使用了mapply...
猜你喜欢
  • 1970-01-01
  • 2016-06-06
  • 2022-11-29
  • 2011-12-19
  • 2012-06-11
  • 2023-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多