Yal*_*Dan 7 optimization performance loops r
我正在尝试做的极简示例:
dX_i <- rnorm(100, 0, 0.0002540362)
p_vec <- seq(0, 1, 0.25)
gamma_vec <- seq(1, 2, 0.25)
a_vec <- seq(2, 6, 1)
sigma_hat_vec <- c(0.03201636, 0.05771143, 0.07932116, 0.12262327, 0.15074560)
delta_j_vec <- c(0.0000005850109, 0.0000011700217, 0.0000017550326, 0.0000035100651, 0.0000052650977)
parameters <- expand.grid("p" = p_vec, "gamma" = gamma_vec, "a" = a_vec, "sigma_hat" = sigma_hat_vec, "delta_j" = delta_j_vec)
result <- sapply(1:nrow(parameters), function(x) {
tmp <- parameters[x,]
p <- tmp$p
a <- tmp$a
gamma <- tmp$gamma
sigma_hat <- tmp$sigma_hat
delta_j <- tmp$delta_j
B <- sum( (abs(dX_i)^p) * ( abs(dX_i) < gamma * a * sigma_hat * delta_j^(1/2) ))
return(B)
})
Run Code Online (Sandbox Code Playgroud)
目标:我需要B在给定 p、a、gamma、sigma_hat、delta_j 的所有组合的情况下计算向量 dX。
然而,实际上网格parameters有 ~600k 行,dX_i长度 ~80k。此外,我有一个 ~1000 的列表dX_i。因此,我想让这个计算尽可能高效。其他方法,例如转换parameters为 data.table 并sapply在该 data.table 中运行似乎没有提供加速。
我尝试并行化该函数(我仅限于在虚拟 Windows 机器上运行脚本):
cl <- makePSOCKcluster(numCores)
num.iter <- 1:nrow(parameters)
parSapply(cl, num.iter, function(x, parameters, dX_i) {
tmp <- parameters[x,]
p <- tmp$p
a <- tmp$a
gamma <- tmp$gamma
sigma_hat <- tmp$sigma_hat
delta_j <- tmp$delta_j
sum( (abs(dX_i)^p) * ( abs(dX_i) < gamma * a * sigma_hat * delta_j^(1/2) ))
}, parameters, dX_i)
stopCluster(cl)
Run Code Online (Sandbox Code Playgroud)
虽然这给了我一个加速,但我仍然觉得我并没有真正以最有效的方式解决这个问题,并且希望得到任何建议。
F. *_*ivé 13
@josliber 的回答非常好。然而,它使它看起来像 R 很糟糕......你必须切换到 C++ 以获得性能。
他们的答案中实施了三个技巧:
dX_i前两个技巧只是一个称为“向量化”的 R 技巧-> 基本上在整个向量上而不是在循环中的单个元素上执行您的操作(例如gamma * a * sigma_hat * delta_j^(1/2)或abs())。
这正是您在使用时所做的sum( dX_i^p * vec_boolean );它是矢量化的(*和sum),所以它应该非常快。
如果我们只实现这两个技巧(我们真的不能用同样的方法来做第三个,因为它破坏了矢量化),它给出:
abs_dX_i <- abs(dX_i)
thresh <- with(parameters, gamma * a * sigma_hat * sqrt(delta_j))
p <- parameters$p
result3 <- sapply(1:nrow(parameters), function(i) {
in_sum <- (abs_dX_i < thresh[i])
sum(abs_dX_i[in_sum]^p[i])
})
all.equal(result, result3) # TRUE
Run Code Online (Sandbox Code Playgroud)
如果我们对所有三个解决方案进行基准测试:
microbenchmark::microbenchmark(
OP = {
result <- sapply(1:nrow(parameters), function(x) {
tmp <- parameters[x,]
p <- tmp$p
a <- tmp$a
gamma <- tmp$gamma
sigma_hat <- tmp$sigma_hat
delta_j <- tmp$delta_j
B <- sum( (abs(dX_i)^p) * ( abs(dX_i) < gamma * a * sigma_hat * delta_j^(1/2) ))
return(B)
})
},
RCPP = {
result2 <- proc(sort(abs(dX_i)), parameters$gamma * parameters$a *
parameters$sigma_hat * parameters$delta_j^(1/2), parameters$p)
},
R_VEC = {
abs_dX_i <- abs(dX_i)
thresh <- with(parameters, gamma * a * sigma_hat * sqrt(delta_j))
p <- parameters$p
result3 <- sapply(1:nrow(parameters), function(i) {
in_sum <- (abs_dX_i < thresh[i])
sum(abs_dX_i[in_sum]^p[i])
})
},
times = 10
)
Run Code Online (Sandbox Code Playgroud)
我们得到:
Unit: milliseconds
expr min lq mean median uq max neval
OP 224.8414 235.4075 289.90096 270.2767 347.1727 399.3262 10
RCPP 14.8172 15.4691 18.83703 16.3979 20.3829 29.6624 10
R_VEC 28.3136 29.5964 32.82456 31.4124 33.2542 45.8199 10
Run Code Online (Sandbox Code Playgroud)
通过稍微修改 R 中的原始代码,它提供了巨大的加速。这比 Rcpp 代码慢不到两倍,并且可以像以前使用parSapply().
jos*_*ber 10
当我想加速难以矢量化的代码时,我经常求助于 Rcpp。在一天结束时,您试图总结abs(dX_i)^p,限制为abs(dX_i)小于阈值的值gamma * a * sigma_hat * delta_j^(1/2)。您想为一堆对p和一个阈值执行此操作。您可以通过以下方式完成此操作:
library(Rcpp)
cppFunction(
"NumericVector proc(NumericVector dX_i, NumericVector thresh, NumericVector p) {
const int n = thresh.size();
const int m = dX_i.size();
NumericVector B(n);
for (int i=0; i < n; ++i) {
B[i] = 0;
for (int j=0; j < m; ++j) {
if (dX_i[j] < thresh[i]) {
B[i] += pow(dX_i[j], p[i]);
} else {
break;
}
}
}
return B;
}"
)
result2 <- proc(sort(abs(dX_i)), parameters$gamma * parameters$a * parameters$sigma_hat * parameters$delta_j^(1/2), parameters$p)
all.equal(result, result2)
# [1] TRUE
Run Code Online (Sandbox Code Playgroud)
请注意,我的代码对 dX_i 的绝对值进行排序,因此一旦遇到超过阈值的第一个值,它就可以停止计算。
在我的机器上,我看到了 20 倍的加速,从您的代码的 0.158 秒到 Rcpp 代码的 0.007 秒(使用 测量system.time)。
p一个观察结果是,参数集中的每个值实际上都有大量重复。您可以单独处理每个p值;这样,您只需将总和dX_i提高到特定p值一次即可。
result4 <- rep(NA, nrow(parameters))
sa_dX_i <- sort(abs(dX_i))
thresh <- parameters$gamma * parameters$a * parameters$sigma_hat * parameters$delta_j^(1/2)
loc <- findInterval(thresh, sa_dX_i)
loc[loc == 0] <- NA # Handle threshold smaller than everything in dX_i
for (pval in unique(parameters$p)) {
this.p <- parameters$p == pval
cs_dX_i_p <- cumsum(sa_dX_i^pval)
result4[this.p] <- cs_dX_i_p[loc[this.p]]
}
result4[is.na(result4)] <- 0 # Handle threshold smaller than everything in dX_i
all.equal(result, result4)
# [1] TRUE
Run Code Online (Sandbox Code Playgroud)
为了看到这一点的实际效果,让我们将原始数据集扩展到问题中描述的内容(约 600k 行参数和约 80k 值dX_i):
set.seed(144)
dX_i <- rnorm(80000, 0, 0.0002540362)
p_vec <- seq(0, 1, 0.025)
gamma_vec <- seq(1, 2, 0.025)
a_vec <- seq(2, 6, 0.3)
sigma_hat_vec <- c(0.03201636, 0.05771143, 0.07932116, 0.12262327, 0.15074560)
delta_j_vec <- c(0.0000005850109, 0.0000011700217, 0.0000017550326, 0.0000035100651, 0.0000052650977)
parameters <- expand.grid("p" = p_vec, "gamma" = gamma_vec, "a" = a_vec, "sigma_hat" = sigma_hat_vec, "delta_j" = delta_j_vec)
dim(parameters)
# [1] 588350 5
length(unique(parameters$p))
# [1] 41
Run Code Online (Sandbox Code Playgroud)
加速相当显着——这段代码在我的计算机上需要 0.27 秒,而我在这个问题的其他答案中发布的 Rcpp 代码需要 655 秒(使用纯 R,加速了 2400 倍!)。显然,只有当数据框中的值相对较少p(每个值重复多次)时,这种加速才有效parameters。如果每个p值都是唯一的,这可能会比建议的其他方法慢得多。
| 归档时间: |
|
| 查看次数: |
355 次 |
| 最近记录: |