我在r中有一个很大的性能问题。我写了一个迭代data.frame对象的函数。它只是简单地向data.frame添加一个新列并累积一些东西。(操作简单)。data.frame大约有850K行。我的电脑还在工作(大约10小时了),我不知道运行时间。
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
temp[i,10] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
temp[i,10] <- temp[i,9] + temp[i-1,10]
} else {
temp[i,10] <- temp[i,9]
}
} else {
temp[i,10] <- temp[i,9]
}
}
names(temp)[names(temp) == "V10"] <- "Kumm."
return(temp)
}
有什么办法可以加快这次行动吗?
这里的答案很好。有一个小方面没有被提及,那就是这个问题说的是“我的电脑还在工作(现在大约10小时了),我不知道运行时间”。在开发时,我总是将以下代码放入循环中,以了解更改如何影响速度,并监视完成所需的时间。
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
cat(round(i/nrow(temp)*100,2),"% \r") # prints the percentage complete in realtime.
# do stuff
}
return(blah)
}
也可以使用lapply。
dayloop2 <- function(temp){
temp <- lapply(1:nrow(temp), function(i) {
cat(round(i/nrow(temp)*100,2),"% \r")
#do stuff
})
return(temp)
}
如果循环中的函数非常快,但循环的数量很大,那么可以考虑偶尔打印一次,因为打印到控制台本身会有开销。如。
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
if(i %% 100 == 0) cat(round(i/nrow(temp)*100,2),"% \r") # prints every 100 times through the loop
# do stuff
}
return(temp)
}
通过使用索引或嵌套的ifelse()语句跳过循环,可以更快地实现这一点。
idx <- 1:nrow(temp)
temp[,10] <- idx
idx1 <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
temp[idx1,10] <- temp[idx1,9] + temp[which(idx1)-1,10]
temp[!idx1,10] <- temp[!idx1,9]
temp[1,10] <- temp[1,9]
names(temp)[names(temp) == "V10"] <- "Kumm."
加速R代码的一般策略
首先,弄清楚慢的部分到底在哪里。没有必要优化运行速度不慢的代码。对于少量的代码,简单地思考一下就可以了。如果失败了,RProf和类似的分析工具可能会有所帮助。
一旦你找到了瓶颈,考虑更有效的算法来做你想做的事情。如果可能,计算应该只运行一次,因此:
存储结果并访问它们,而不是重复地重新计算
从循环中取出非依赖于循环的计算
避免不必要的计算(例如,不要使用固定搜索的正则表达式)
使用更有效的函数可以产生中等或较大的速度增益。例如,paste0会产生很小的效率提高,而. colsum()及其相关项会产生更明显的效率提高。Mean特别慢。
这样你就可以避免一些特别常见的问题:
Cbind会很快让你慢下来。
初始化数据结构,然后填充它们,而不是逐个展开它们
时间。
即使使用预分配,您也可以切换到按引用传递方法而不是按值传递方法,但这样做可能不值得这么麻烦。
看看R地狱更多的陷阱要避免。
尝试更好的向量化,这通常有帮助,但并不总是有帮助。在这方面,固有的向量化命令,如ifelse、diff等,将比apply命令系列提供更多的改进(对于编写良好的循环,apply命令系列几乎没有提高速度)。
您还可以尝试为R函数提供更多信息。例如,使用vapply而不是sapply,并在读入基于文本的数据时指定colClasses。速度的增加取决于你消除了多少猜测。
接下来,考虑优化的包:数据。在数据操作和读取大量数据(fread)时,表包可以在可能的情况下产生巨大的速度增益。
接下来,尝试通过调用R的更有效的方法来提高速度:
Compile your R script. Or use the Ra and jit packages in concert for just-in-time compilation (Dirk has an example in this presentation).
Make sure you're using an optimized BLAS. These provide across-the-board speed gains. Honestly, it's a shame that R doesn't automatically use the most efficient library on install. Hopefully Revolution R will contribute the work that they've done here back to the overall community.
Radford Neal has done a bunch of optimizations, some of which were adopted into R Core, and many others which were forked off into pqR.
最后,如果以上所有方法仍然不能让您达到所需的速度,您可能需要使用更快的语言来处理速度较慢的代码片段。Rcpp和内联的组合使得用c++代码替换算法中最慢的部分特别容易。例如,这里是我第一次尝试这样做,它甚至击败了高度优化的R解。
如果在这之后你仍然有麻烦,你只是需要更多的计算能力。看看并行化(http://cran.r-project.org/web/views/HighPerformanceComputing.html)或者甚至是基于gpu的解决方案(gpu-tools)。
其他指引的连结
http://www.noamross.net/blog/2013/4/25/faster-talk.html
正如Ari在他的回答的最后提到的,Rcpp和内联包使事情变得非常容易。作为一个例子,试试下面的内联代码(警告:未测试):
body <- 'Rcpp::NumericMatrix nm(temp);
int nrtemp = Rccp::as<int>(nrt);
for (int i = 0; i < nrtemp; ++i) {
temp(i, 9) = i
if (i > 1) {
if ((temp(i, 5) == temp(i - 1, 5) && temp(i, 2) == temp(i - 1, 2) {
temp(i, 9) = temp(i, 8) + temp(i - 1, 9)
} else {
temp(i, 9) = temp(i, 8)
}
} else {
temp(i, 9) = temp(i, 8)
}
return Rcpp::wrap(nm);
'
settings <- getPlugin("Rcpp")
# settings$env$PKG_CXXFLAGS <- paste("-I", getwd(), sep="") if you want to inc files in wd
dayloop <- cxxfunction(signature(nrt="numeric", temp="numeric"), body-body,
plugin="Rcpp", settings=settings, cppargs="-I/usr/include")
dayloop2 <- function(temp) {
# extract a numeric matrix from temp, put it in tmp
nc <- ncol(temp)
nm <- dayloop(nc, temp)
names(temp)[names(temp) == "V10"] <- "Kumm."
return(temp)
}
对于#include也有类似的过程,只需要传递一个参数
inc <- '#include <header.h>
到cxxfunction,如include=inc。最酷的是它为你做了所有的链接和编译,所以原型制作非常快。
免责声明:我不完全确定tmp的类应该是数字而不是数字矩阵或其他东西。但我基本确定。
编辑:如果你在此之后仍然需要更快的速度,OpenMP是一个适合c++的并行化工具。我还没有尝试从内联使用它,但它应该工作。这个想法是,在n个核的情况下,循环迭代k由k % n执行。Matloff的《R编程的艺术》中有一个合适的介绍,在这里,第16章,求助于C。
在R中,您通常可以通过使用apply族函数来加速循环处理(在您的示例中,可能是复制)。看一下提供进度条的plyr包。
另一种选择是完全避免循环,用向量化算法代替它们。我不确定你到底在做什么,但你可能可以将你的函数一次性应用到所有行:
temp[1:nrow(temp), 10] <- temp[1:nrow(temp), 9] + temp[0:(nrow(temp)-1), 10]
这将会快得多,然后你可以用你的条件过滤行:
cond.i <- (temp[i, 6] == temp[i-1, 6]) & (temp[i, 3] == temp[i-1, 3])
temp[cond.i, 10] <- temp[cond.i, 9]
向量化算术需要更多的时间和思考问题,但有时可以节省几个数量级的执行时间。