为什么R中的循环缓慢?

iso*_*mes 80 performance r apply

我知道循环很慢R,我应该尝试以矢量化的方式做事.

但为什么?为什么循环缓慢且apply速度快?apply调用几个子功能 - 这似乎不快.

更新:对不起,这个问题不合适.我混淆了矢量化apply.我的问题应该是,

"为什么矢量化更快?"

Kar*_*arl 76

循环速度慢且apply速度快并非总是如此.在R News的2008年5月刊中有一个很好的讨论:

Uwe Ligges和John Fox.R帮助台:如何避免此循环或使其更快?R News,8(1):46-50,2008年5月.

在"循环!"部分 (从第48页开​​始),他们说:

关于R的许多评论都指出使用循环是一个特别糟糕的主意.这不一定是真的.在某些情况下,很难编写矢量化代码,或者矢量化代码可能会消耗大量内存.

他们进一步建议:

  • 在循环之前将新对象初始化为全长,而不是在循环内增加它们的大小.
  • 不要在可以在循环外完成的循环中执行操作.
  • 不要仅仅为了避免循环而避免循环.

他们有一个简单的例子,for循环耗时1.3秒,但apply内存不足.


Owe*_*wen 69

由于任何解释性语言都很慢,R中的循环速度很慢:每个操作都带有很多额外的包袱.

看看R_execClosureeval.c(这是所谓的调用用户定义函数的函数).它近100行并执行各种操作 - 创建执行环境,为环境分配参数等.

想想当你在C中调用一个函数时会发生多少事情(推动args on stack,jump,pop args).

所以这就是为什么你得到像这样的时间(正如joran在评论中指出的那样,实际上并不是apply那么快;它是内部C循环,mean 因为它很快.apply只是常规的旧R代码):

A = matrix(as.numeric(1:100000))
Run Code Online (Sandbox Code Playgroud)

使用循环:0.342秒:

system.time({
    Sum = 0
    for (i in seq_along(A)) {
        Sum = Sum + A[[i]]
    }
    Sum
})
Run Code Online (Sandbox Code Playgroud)

使用总和:不可测量的小:

sum(A)
Run Code Online (Sandbox Code Playgroud)

这有点令人不安,因为渐近地,循环就像那样好sum; 没有实际的理由它应该缓慢; 它只是在每次迭代时做更多的额外工作.

所以考虑:

# 0.370 seconds
system.time({
    I = 0
    while (I < 100000) {
        10
        I = I + 1
    }
})

# 0.743 seconds -- double the time just adding parentheses
system.time({
    I = 0
    while (I < 100000) {
        ((((((((((10))))))))))
        I = I + 1
    }
})
Run Code Online (Sandbox Code Playgroud)

(这个例子是由Radford Neal发现的)

因为(在R中是一个运算符,并且每次使用它时实际上都需要进行名称查找:

> `(` = function(x) 2
> (3)
[1] 2
Run Code Online (Sandbox Code Playgroud)

或者,通常,解释操作(以任何语言)具有更多步骤.当然,这些步骤也提供了好处:你无法在C中做到这一点(.

  • 那么最后一个例子的重点是什么?不要在R中做愚蠢的事情并期望它能快速完成吗? (10认同)
  • @Chase我想这是一种说法.是的,我的意思是像C这样的语言与嵌套括号没有速度差异,但R不优化或编译. (6认同)
  • @Owen我同意你的一般观点,这是一个重要的观点; 我们不使用R,因为它是打破速度记录,我们使用它因为它易于使用且非常强大.这种力量伴随着解释的代价.目前还不清楚你想在`for()`vs`applied()`例子中显示什么.我认为你应该删除那个例子,因为虽然求和是计算均值的很大一部分,但你所有的例子都显示了向量化函数的速度,`mean()`,超过元素的C类迭代. (3认同)

Rei*_*son 35

提出的问题的唯一答案是; 如果您需要做的是迭代执行某个函数和该函数的一组数据或操作未向量化,循环不会很慢.一个循环将一样快,一般为,但可能比慢一点点通话.最后一点是很好地覆盖在SO,例如在这个答案,并适用于如果参与建立和运行的代码回路是的整体计算负担显著部分循环.for()apply()lapply()

为什么许多人认为for()循环缓慢是因为他们(用户)编写了错误的代码.通常(虽然有几个例外),如果你需要扩展/增长一个对象,那也将涉及复制,因此你既有复制增长对象的开销.这不仅仅局限于循环,但是如果你在循环的每次迭代中复制/增长,那么循环将会很慢,因为你需要进行许多复制/增长操作.

for()在R中使用循环的一般习惯是在循环开始之前分配所需的存储,然后填写这样分配的对象.如果你遵循这个习语,循环不会很慢.这是apply()为您管理的,但它只是隐藏在视图之外.

当然,如果您使用for()循环实现的操作存在向量化函数,请不要这样做.同样,如果存在矢量化函数,则不要使用apply()等(例如,apply(foo, 2, mean)通过更好地执行colMeans(foo)).


Tom*_*mmy 9

就像比较一样(不要过多地阅读它!):我在R和Chrome和IE 8中的JavaScript中运行了一个(非常)简单的for循环.请注意,Chrome会编译为本机代码,R会使用编译器编译包编译为字节码.

# In R 2.13.1, this took 500 ms
f <- function() { sum<-0.5; for(i in 1:1000000) sum<-sum+i; sum }
system.time( f() )

# And the compiled version took 130 ms
library(compiler)
g <- cmpfun(f)
system.time( g() )
Run Code Online (Sandbox Code Playgroud)

@Gavin Simpson:顺便说一句,在S-Plus中耗时1162毫秒......

和JavaScript一样"相同"的代码:

// In IE8, this took 282 ms
// In Chrome 14.0, this took 4 ms
function f() {
    var sum = 0.5;
    for(i=1; i<=1000000; ++i) sum = sum + i;
    return sum;
}

var start = new Date().getTime();
f();
time = new Date().getTime() - start;
Run Code Online (Sandbox Code Playgroud)