92

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

但为什么?为什么循环慢而apply快?apply调用几个子函数——这似乎并不快。

更新:对不起,这个问题提出来了。我将矢量化与apply. 我的问题应该是,

“为什么矢量化更快?”

4

4 回答 4

80

循环并不总是很慢而且apply很快。在2008 年 5 月的 R News 杂志上有一个很好的讨论:

乌维·里格斯和约翰·福克斯。R Help Desk:我怎样才能避免这个循环或让它更快?R 新闻,8(1):46-50,2008 年 5 月。

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

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

他们进一步建议:

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

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

于 2011-08-22T03:52:01.650 回答
73

R 中的循环很慢,原因与任何解释语言都很慢一样:每个操作都会带来很多额外的包袱。

R_execClosureineval.c(这是调用用户自定义函数的函数)。它有近 100 行长并执行各种操作——创建执行环境、将参数分配给环境等。

想想当你在 C 中调用一个函数时发生的事情有多少(将 args 推入堆栈、跳转、弹出 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 中做到这一点。(

于 2011-08-22T03:19:23.950 回答
38

对所提出问题的唯一答案是;如果您需要做的是迭代一组执行某些功能的数据并且该功能或操作未矢量化,则循环并不慢。一般来说,循环将与 一样快,但可能比调用慢一点。最后一点在 SO 中得到了很好的介绍,例如在这个Answer中,如果涉及设置和操作循环的代码是循环的整体计算负担的重要部分,则适用。for()apply()lapply()

为什么很多人认为for()循环很慢是因为他们,用户,正在编写糟糕的代码。通常(尽管有几个例外),如果您需要扩展/增长对象,那也将涉及复制,因此您既有复制对象的开销,也有增长对象的开销。这不仅限于循环,而且如果您在循环的每次迭代中复制/增长,当然,循环会很慢,因为您会产生许多复制/增长操作。

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

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

于 2011-08-22T08:28:43.547 回答
9

只是作为一个比较(不要读太多!):我在 R 中运行了一个(非常)简单的 for 循环,在 Chrome 和 IE 8 中的 JavaScript 中运行。请注意,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;
于 2011-09-30T22:53:07.740 回答