具有良好性能的 charToRaw 的矢量化版本
Vectorized version of charToRaw with good performance
我想从字符向量中获取原始字节向量(以应用需要原始字节作为输入的加密函数 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 的版本不提供矢量化版本?
我知道我可以使用 sapply
或 myapply
但我最终会在所有行上进行内部循环...
编辑 1:
结果应为大小与 'x' 相同的向量,每个元素代表相应输入元素的原始字节。
编辑 2 + 3:
我的结果应该是这样的(例如列表)
x.raw
[1] 68 65 6c 6c 6f
[2] 6d 79
[3] 77 6f 72 6c 64
问题是 R 似乎不支持原始向量,因为 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
您可以使用Vectorize()
来完成这个任务:
x <- c("hello", "my", "world")
charToRawVec <- Vectorize(FUN = charToRaw, vectorize.args = "x")
charToRawVec(x)
这是一个使用 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
我想从字符向量中获取原始字节向量(以应用需要原始字节作为输入的加密函数 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 的版本不提供矢量化版本?
我知道我可以使用 sapply
或 myapply
但我最终会在所有行上进行内部循环...
编辑 1: 结果应为大小与 'x' 相同的向量,每个元素代表相应输入元素的原始字节。
编辑 2 + 3: 我的结果应该是这样的(例如列表)
x.raw
[1] 68 65 6c 6c 6f
[2] 6d 79
[3] 77 6f 72 6c 64
问题是 R 似乎不支持原始向量,因为 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
您可以使用Vectorize()
来完成这个任务:
x <- c("hello", "my", "world")
charToRawVec <- Vectorize(FUN = charToRaw, vectorize.args = "x")
charToRawVec(x)
这是一个使用 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