为什么R中的循环慢?


86

我知道循环很慢R,我应该尝试以向量化的方式来做事情。

但为什么?为什么循环慢而apply快?apply调用了几个子功能-看起来并不快。

更新:很抱歉,这个问题是不恰当的。我把向量化与混淆了apply。我的问题应该是

“为什么矢量化更快?”


3
我给人的印象是R中的“应用比循环快得多”是一个神话。让system.time战争在答案中开始……
joran 2011年

1
这里有很多关于这个主题的有用信息:stackoverflow.com/questions/2275896/…–
Chase

7
记录:Apply不是矢量化。Apply是一个具有不同(例如:无)副作用的循环结构。参见讨论@Chase链接。
乔里斯·梅斯

4
传统上,SS-Plus?)中的循环很慢。R不是这种情况。因此,您的问题并不是很重要。我不知道今天的S-Plus情况如何。
加文·辛普森,

4
我不清楚为什么这个问题被否决了-这个问题在其他地区来R的人中很常见,应该添加到FAQ中。
patrickmdnet

Answers:


69

出于任何解释语言都较慢的原因,R中的循环很慢:每个操作都会带来很多额外负担。

R_execClosureeval.c(这是所谓的调用用户定义函数的函数)。它大约有100行,可以执行各种操作-创建执行环境,向该环境分配参数,等等。

想想当您在C中调用一个函数(将args压入堆栈,跳转,pop args)时发生的事情少了多少。

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

A = matrix(as.numeric(1:100000))

使用循环:0.342秒:

system.time({
    Sum = 0
    for (i in seq_along(A)) {
        Sum = Sum + A[[i]]
    }
    Sum
})

使用sum:不可估量的小:

sum(A)

这有点令人不安,因为从渐近来看,循环与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
    }
})

(该示例由Radford Neal发现)

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

> `(` = function(x) 2
> (3)
[1] 2

或者,通常,解释操作(任何语言)都需要更多步骤。当然,这些步骤也可以带来好处:您无法在C语言中做到这一点(


10
那么最后一个示例的意义是什么?不要在R中做愚蠢的事情,并期望它能很快完成它们吗?
追逐

6
@Chase我想这是一种表达方式。是的,我的意思是像C这样的语言使用嵌套括号不会造成速度差异,但是R不会优化或编译。
欧文(Owen)

1
还有()或循环主体中的{}-所有这些都涉及名称查找。或者通常,在R中,当您编写更多内容时,解释程序会执行更多操作。
欧文(Owen)

1
我不确定要在for()循环中说明什么?他们根本没有做同样的事情。该for()循环迭代的每个元素A和它们求和。该apply()调用会将整个向量A[,1](您A只有一列)传递给向量化函数mean()。我看不出这对讨论有什么帮助,只会使情况混乱。
加文·辛普森

3
@Owen我同意你的一般观点,这很重要。我们之所以不使用R是因为它打破了速度记录,我们之所以使用它是因为它易于使用且功能强大。这种力量伴随着解释的代价。尚不清楚您要在for()vsapply()示例中显示什么。我认为您应该删除该示例,因为求和是计算均值的主要部分,而您的示例真正表明的是向量化函数mean()在元素上类似C的迭代中的速度。
加文·辛普森

78

循环并非总是慢而apply快的情况。在2008年5月的R News中,对此进行了很好的讨论:

Uwe Ligges和John Fox。R服务台:如何避免这种循环或使其更快?R新闻,8(1):46-50,2008年5月。

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

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

他们进一步建议:

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

他们有一个简单的示例,其中for循环耗时1.3秒,但apply内存不足。


35

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

许多人认为for()循环很慢的原因是因为它们(用户)正在编写错误的代码。一般来说(虽然有一些例外),如果你需要扩展/增长的目标,同样的情况也会涉及到复制,所以你必须复制的两个开销成长的对象。这不仅限于循环,而且如果您在循环的每个迭代中复制/增长,当然,由于您要进行许多复制/增长操作,因此循环将很慢。

for()在R中使用循环的一般习惯是,在循环开始之前分配所需的存储空间,然后填写由此分配的对象。如果遵循该习惯用法,循环将不会很慢。这就是apply()为您管理的内容,但是它只是隐藏起来而已。

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


9

就像一个比较(不要读太多!):我在R和JavaScript和Chrome和IE 8中运行了一个(非常简单的)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() )

@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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.