我正在努力矢量化以下函数的重复应用程序,我目前已将其实现为 for 循环。这个小例子表明了较大数据集的问题,矢量化将允许有益的运行时改进:
action = function(x,y,i) {
firsttask = cumsum(x[which(x<y[i])])
secondtask = mean(firsttask)
thirdtask = min(firsttask[which(firsttask>secondtask)])
fourthtask = length(firsttask)
output = list(firsttask, data.frame(average=secondtask,
min_over_mean=thirdtask,
size=fourthtask))
return(output)
}
thingofinterest = c(1:10)
limits = c(5:10)
test = vector("list", length = length(limits))
for(i in 1:length(limits)) {
test[[i]] = action(thingofinterest, limits, i)
}
test
Run Code Online (Sandbox Code Playgroud)
我想用矢量化命令替换 for 循环,而不是任何apply函数系列,因为它们并不总是能提高性能(我并不是说 for 循环有任何问题,我只需要优化此中的速度)案例。参见:R 的 apply 系列比语法糖更重要吗?)。我该怎么做?
在开始尝试更改代码以使其更快之前,您需要了解代码中的瓶颈在哪里。例如:
timer <- function(action, thingofinterest, limits) {
st <- system.time({ # for the wall time
Rprof(interval=0.01) # Start R's profile timing
for(j in 1:1000) { # 1000 function calls
test = vector("list")
for(i in 1:length(limits)) {
test[[i]] = action(thingofinterest, limits, i)
}
}
Rprof(NULL) # stop the profiler
})
# return profiling results
list(st, head(summaryRprof()$by.total))
}
action = function(x,y,i) {
firsttask = cumsum(x[which(x<y[i])])
secondtask = min(firsttask[which(firsttask>mean(firsttask))])
thirdtask = mean(firsttask)
fourthtask = length(firsttask)
output = list(firsttask, data.frame(average=secondtask,
min_over_mean=thirdtask,
size=fourthtask))
return(output)
}
timer(action, 1:1000, 50:100)
# [[1]]
# user system elapsed
# 9.720 0.012 9.737
#
# [[2]]
# total.time total.pct self.time self.pct
# "system.time" 9.72 100.00 0.07 0.72
# "timer" 9.72 100.00 0.00 0.00
# "action" 9.65 99.28 0.24 2.47
# "data.frame" 8.53 87.76 0.84 8.64
# "as.data.frame" 5.50 56.58 0.44 4.53
# "force" 4.40 45.27 0.11 1.13
Run Code Online (Sandbox Code Playgroud)
您可以看到,在函数调用之外花费的时间非常少action。现在,for是一个特殊的基元,因此不会被探查器捕获,但探查器报告的总时间与实际时间非常相似,因此探查器时间不会丢失很多时间。
在函数中花费最多时间action的是对 的调用data.frame。去掉它,你就会得到巨大的加速。
action1 = function(x,y,i) {
firsttask = cumsum(x[which(x<y[i])])
secondtask = mean(firsttask)
thirdtask = min(firsttask[which(firsttask>mean(firsttask))])
fourthtask = length(firsttask)
list(task=firsttask, average=secondtask,
min_over_mean=thirdtask, size=fourthtask)
}
timer(action1, 1:1000, 50:100)
# [[1]]
# user system elapsed
# 1.020 0.000 1.021
#
# [[2]]
# total.time total.pct self.time self.pct
# "system.time" 1.01 100.00 0.06 5.94
# "timer" 1.01 100.00 0.00 0.00
# "action" 0.95 94.06 0.17 16.83
# "which" 0.57 56.44 0.23 22.77
# "mean" 0.25 24.75 0.13 12.87
# "<" 0.20 19.80 0.20 19.80
Run Code Online (Sandbox Code Playgroud)
现在您还可以删除对 的一个调用mean和对 的两个调用which。
action2 = function(x,y,i) {
firsttask = cumsum(x[x < y[i]])
secondtask = mean(firsttask)
thirdtask = min(firsttask[firsttask > secondtask])
fourthtask = length(firsttask)
list(task=firsttask, average=secondtask,
min_over_mean=thirdtask, size=fourthtask)
}
timer(action2, 1:1000, 50:100)
# [[1]]
# user system elapsed
# 0.808 0.000 0.808
#
# [[2]]
# total.time total.pct self.time self.pct
# "system.time" 0.80 100.00 0.12 15.00
# "timer" 0.80 100.00 0.00 0.00
# "action" 0.68 85.00 0.24 30.00
# "<" 0.20 25.00 0.20 25.00
# "mean" 0.13 16.25 0.08 10.00
# ">" 0.05 6.25 0.05 6.25
Run Code Online (Sandbox Code Playgroud)
现在您可以看到有“大量”时间花费在您的action职能之外的事情上。我用引号引起来,因为它占运行时间的 15%,但只有 120 毫秒。如果您的实际代码需要约 12 小时才能运行,则此新action函数将在约 1 小时内完成。
如果我在函数的循环test之外预先分配列表,结果会稍微好一点,但调用是最大的时间消耗。fortimerdata.frame