Fed*_*sso 20 performance r lapply
人们常说,人们应该更喜欢lapply过for循环.有一些例外,例如Hadley Wickham在他的Advance R书中指出.
(http://adv-r.had.co.nz/Functionals.html)(修改到位,递归等).以下是这种情况之一.
仅仅为了学习,我试图以功能形式重写感知器算法,以便对相对性能进行基准测试.来源(https://rpubs.com/FaiHas/197581).
这是代码.
# prepare input
data(iris)
irissubdf <- iris[1:100, c(1, 3, 5)]
names(irissubdf) <- c("sepal", "petal", "species")
head(irissubdf)
irissubdf$y <- 1
irissubdf[irissubdf[, 3] == "setosa", 4] <- -1
x <- irissubdf[, c(1, 2)]
y <- irissubdf[, 4]
# perceptron function with for
perceptron <- function(x, y, eta, niter) {
# initialize weight vector
weight <- rep(0, dim(x)[2] + 1)
errors <- rep(0, niter)
# loop over number of epochs niter
for (jj in 1:niter) {
# loop through training data set
for (ii in 1:length(y)) {
# Predict binary label using Heaviside activation
# function
z <- sum(weight[2:length(weight)] * as.numeric(x[ii,
])) + weight[1]
if (z < 0) {
ypred <- -1
} else {
ypred <- 1
}
# Change weight - the formula doesn't do anything
# if the predicted value is correct
weightdiff <- eta * (y[ii] - ypred) * c(1,
as.numeric(x[ii, ]))
weight <- weight + weightdiff
# Update error function
if ((y[ii] - ypred) != 0) {
errors[jj] <- errors[jj] + 1
}
}
}
# weight to decide between the two species
return(errors)
}
err <- perceptron(x, y, 1, 10)
### my rewriting in functional form auxiliary
### function
faux <- function(x, weight, y, eta) {
err <- 0
z <- sum(weight[2:length(weight)] * as.numeric(x)) +
weight[1]
if (z < 0) {
ypred <- -1
} else {
ypred <- 1
}
# Change weight - the formula doesn't do anything
# if the predicted value is correct
weightdiff <- eta * (y - ypred) * c(1, as.numeric(x))
weight <<- weight + weightdiff
# Update error function
if ((y - ypred) != 0) {
err <- 1
}
err
}
weight <- rep(0, 3)
weightdiff <- rep(0, 3)
f <- function() {
t <- replicate(10, sum(unlist(lapply(seq_along(irissubdf$y),
function(i) {
faux(irissubdf[i, 1:2], weight, irissubdf$y[i],
1)
}))))
weight <<- rep(0, 3)
t
}
Run Code Online (Sandbox Code Playgroud)
由于上述问题,我没想到会有任何持续的改进.但是当我看到使用lapply和使用时的急剧恶化时,我真的很惊讶replicate.
我使用库中的microbenchmark函数获得了这个结果microbenchmark
可能是什么原因?可能是一些内存泄漏?
expr min lq mean median uq
f() 48670.878 50600.7200 52767.6871 51746.2530 53541.2440
perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 4184.131 4437.2990 4686.7506 4532.6655 4751.4795
perceptronC(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 95.793 104.2045 123.7735 116.6065 140.5545
max neval
109715.673 100
6513.684 100
264.858 100
Run Code Online (Sandbox Code Playgroud)
第一个函数是lapply/ replicatefunction
第二个是带for循环的函数
第三个是在相同的功能C++使用Rcpp
根据Roland的功能分析.我不确定我能以正确的方式解释它.在我看来,大部分时间用于子集化 功能分析
Jor*_*eys 39
首先,这是一个已经很久被揭穿的神话,for循环比任何慢lapply.forR中的循环已经变得更加高效,目前至少和它一样快lapply.
也就是说,你必须重新考虑你在lapply这里的使用.您的实现需要分配到全局环境,因为您的代码要求您在循环期间更新权重.这是不考虑的正当理由lapply.
lapply是一种你应该用于副作用(或没有副作用)的功能.该函数lapply自动将结果组合在一个列表中,并且不会影响您使用的环境,这与for循环相反.同样的道理replicate.另见这个问题:
您的lapply解决方案速度慢得多的原因是因为您使用它的方式会产生更多的开销.
replicate除了sapply内部之外别无其他,所以你实际上要结合sapply并lapply实现你的双循环.sapply产生额外的开销,因为它必须测试结果是否可以简化.所以for循环实际上比使用更快replicate.lapply匿名函数中,您必须为每个观察访问x和y的数据帧.这意味着 - 在你的for循环中相反 - 例如,$每次都必须调用该函数.for的解决方案,只要求26的这些额外功能lapply的解决方案包括像函数的调用match,structure,[[,names,%in%,sys.call,duplicated,...所有功能for循环不需要,因为那个不执行任何这些检查.如果你想看到这个额外的开销从何而来,看的内部代码replicate,unlist,sapply和simplify2array.
您可以使用以下代码来更好地了解失去性能的位置lapply.逐行运行!
Rprof(interval = 0.0001)
f()
Rprof(NULL)
fprof <- summaryRprof()$by.self
Rprof(interval = 0.0001)
perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10)
Rprof(NULL)
perprof <- summaryRprof()$by.self
fprof$Fun <- rownames(fprof)
perprof$Fun <- rownames(perprof)
Selftime <- merge(fprof, perprof,
all = TRUE,
by = 'Fun',
suffixes = c(".lapply",".for"))
sum(!is.na(Selftime$self.time.lapply))
sum(!is.na(Selftime$self.time.for))
Selftime[order(Selftime$self.time.lapply, decreasing = TRUE),
c("Fun","self.time.lapply","self.time.for")]
Selftime[is.na(Selftime$self.time.for),]
Run Code Online (Sandbox Code Playgroud)
Don*_*nen 13
还有更多关于何时使用for或lapply哪种“性能”更好的问题。有时速度很重要,有时记忆很重要。更复杂的是,时间复杂度可能不是您所期望的 - 也就是说,可以在不同的范围内观察到不同的行为,从而使任何笼统的陈述(例如“快于”或“至少一样快”)无效。最后,一个经常被忽视的性能指标是思想到代码,不成熟的优化yada yada。
也就是说,在R 简介中,作者暗示了一些性能问题:
\n\n\n警告:R 代码中使用 for() 循环的频率远低于编译语言中的频率。采用 \xe2\x80\x98整个对象\xe2\x80\x99 视图的代码在 R 中可能会更清晰、更快。
\n
给定类似的用例、输入和输出,不考虑用户偏好,其中一个明显优于另一个吗?
\n我比较了计算 1 到N 个 斐波那契数的方法(受到benchmarkme包的启发),避开第二个圆圈并确保每种方法的输入和输出相同。另外还有四种方法可以火上浇油——矢量化方法 和purrr::map,以及*apply变体vapply和sapply。
fib <- function(x, ...){\n x <- 1:x ; phi = 1.6180339887498949 ; v = \\() vector("integer", length(x))\n bench::mark(\n vector = {\n y=v(); y = ((rep(phi, length(x))^x) - ((-rep(phi, length(x)))^-x)) / sqrt(5); y},\n lapply = {\n y=v(); y = unlist(lapply(x, \\(.) (phi^. - (-phi)^(-.)) / sqrt(5)), use.names = F); y},\n loop = {\n y=v(); `for`(i, x, {y[i] = (phi^i - (-phi)^(-i)) / sqrt(5)}); y},\n sapply = {\n y=v(); y = sapply(x, \\(.) (phi^. - (-phi)^(-.)) / sqrt(5)); y},\n vapply = {\n y=v(); y = vapply(x, \\(.) (phi^. - (-phi)^(-.)) / sqrt(5), 1); y},\n map = {\n y=v(); y <- purrr::map_dbl(x, ~ (phi^. - (-phi)^(-.))/sqrt(5)); y\n }, ..., check = T\n )[c(1:9)]\n}\nRun Code Online (Sandbox Code Playgroud)\n以下是按中位时间排名的性能比较。
\nlapply(list(3e2, 3e3, 3e4, 3e5, 3e6, 3e7), fib) # n iterations specified separately\nN = 300\n expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time\n1 vector 38.8us 40.9us 21812. 8.44KB 0 1000 0 45.8ms\n2 vapply 500us 545us 1653. 3.61KB 1.65 999 1 604ms\n3 sapply 518us 556us 1725. 12.48KB 0 1000 0 580ms\n4 lapply 513.4us 612.8us 1620. 6KB 8.14 995 5 614.2ms\n5 loop 549.9us 633.6us 1455. 3.61KB 8.78 994 6 683.3ms\n6 map 649.6us 754.6us 1312. 3.61KB 9.25 993 7 756.9ms\n\nN = 3000\n1 vector 769.7us 781.5us 1257. 82.3KB 1.26 999 1 794.83ms\n2 vapply 5.38ms 5.58ms 173. 35.2KB 0.697 996 4 5.74s\n3 sapply 5.59ms 5.83ms 166. 114.3KB 0.666 996 4 6.01s\n4 loop 5.38ms 5.91ms 167. 35.2KB 8.78 950 50 5.69s\n5 lapply 5.24ms 6.49ms 156. 58.7KB 8.73 947 53 6.07s\n6 map 6.11ms 6.63ms 148. 35.2KB 9.13 942 58 6.35s\n\nN = 30 000\n1 vector 10.7ms 10.9ms 90.9 821KB 0.918 297 3 3.27s\n2 vapply 57.3ms 60.1ms 16.4 351.66KB 0.741 287 13 17.5s\n3 loop 59.2ms 60.7ms 15.9 352KB 16.7 146 154 9.21s\n4 sapply 59.6ms 62.1ms 15.7 1.05MB 0.713 287 13 18.2s\n5 lapply 57.3ms 67.6ms 15.1 586KB 20.5 127 173 8.43s\n6 map 66.7ms 69.1ms 14.4 352KB 21.6 120 180 8.35s\n\nN = 300 000\n1 vector 190ms 193ms 5.14 8.01MB 0.206 100 4 19.45s\n2 loop 693ms 713ms 1.40 3.43MB 7.43 100 532 1.19m\n3 map 766ms 790ms 1.26 3.43MB 7.53 100 598 1.32m\n4 vapply 633ms 814ms 1.33 3.43MB 0.851 100 39 45.8s\n5 lapply 685ms 966ms 1.06 5.72MB 9.13 100 864 1.58m\n6 sapply 694ms 813ms 1.27 12.01MB 0.810 100 39 48.1s\n\nN = 3 000 000\n1 vector 3.17s 3.21s 0.312 80.1MB 0.249 20 16 1.07m\n2 vapply 8.22s 8.37s 0.118 34.3MB 4.97 20 845 2.83m\n3 loop 8.3s 8.42s 0.119 34.3MB 4.35 20 733 2.81m\n4 map 9.09s 9.17s 0.109 34.3MB 4.91 20 903 3.07m\n5 lapply 10.42s 11.09s 0.0901 57.2MB 4.10 20 909 3.7m\n6 sapply 10.43s 11.28s 0.0862 112.1MB 3.58 20 830 3.87m\n\nN = 30 000 000\n1 vector 44.8s 45.94s 0.0214 801MB 0.00854 10 4 7.8m\n2 vapply 1.56m 1.6m 0.0104 343MB 0.883 10 850 16m\n3 loop 1.56m 1.62m 0.00977 343MB 0.366 10 374 17.1m\n4 map 1.72m 1.74m 0.00959 343MB 1.23 10 1279 17.4m\n5 lapply 2.15m 2.22m 0.00748 572MB 0.422 10 565 22.3m\n6 sapply 2.05m 2.25m 0.00747 1.03GB 0.405 10 542 22.3m\n\n# Intel i5-8300H CPU @ 2.30GHz / R version 4.1.1 / purrr 0.3.4\nRun Code Online (Sandbox Code Playgroud)\nfor和lapply方法的性能类似,但lapply在内存方面更加贪婪,并且当输入大小增加时(对于此任务)速度会慢一些。请注意,purrr::map内存使用量相当于for-loop,优于lapply,本身就是一个有争议的话题。*apply*然而,当使用适当的时候,这里的vapply性能是相似的。但这种选择可能会对内存使用产生很大影响,sapply其内存效率明显低于vapply.
深入了解这些方法可以揭示不同性能的原因。for循环执行许多类型检查,从而产生一些开销。另一方面,lapply受到有缺陷的语言设计的影响,其中惰性求值或使用 Promise 是有代价的,源代码确认了 和 的X参数FUN是.Internal(lapply)Promise。
矢量化方法速度很快,并且可能比fororlapply方法更理想。请注意,与其他方法相比,矢量化方法的增长如何不规则。然而,矢量化代码的美观可能是一个问题:您更喜欢调试哪种方法?
总的来说,我认为普通 R 用户不应该考虑在lapply或之间进行选择。for坚持最容易编写、思考和调试的内容,或者不太容易(无声?)出错的内容。性能上的损失可能会被节省的写作时间所抵消。对于性能关键型应用程序,请确保使用不同的输入大小运行一些测试并正确地对代码进行分块。