web-dev-qa-db-ja.com

Rでループが遅いのはなぜですか?

Rでループが遅いことと、代わりにベクトル化された方法で処理を行う必要があることを知っています。

しかし、なぜ?なぜループが遅く、applyが速いのですか? applyはいくつかのサブ関数を呼び出しますが、高速ではないようです。

更新:申し訳ありませんが、質問は不適切です。ベクトル化をapplyと混同していました。私の質問は、

"なぜベクトル化が高速化されるのですか?"

78
isomorphismes

Rのループは、インタープリター言語が遅いのと同じ理由で遅くなります。すべての操作は多くの余分な荷物を持ち運びます。

R_execClosure in eval.c を見てください(これはユーザー定義関数を呼び出すために呼び出される関数です)。それはほぼ100行の長さで、実行のための環境の作成、環境への引数の割り当てなど、あらゆる種類の操作を実行します。

Cで関数を呼び出すと、引数がどれだけ少なくなるかを考えてください(引数をプッシュして、スタック、ジャンプ、ポップ引数をポップします)。

だから、これらのようなタイミングを得る理由です(コメントでjoranが指摘したように、実際にはapplyは高速ではありません。meanの内部Cループは高速です。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で(トリックを(doできませんでした。

68
Owen

ループが低速でapplyが高速であるとは限りません。 2008年5月、R Newsの発行 でこれに関する素晴らしい議論があります:

Uwe LiggesとJohn Fox。 Rヘルプデスク:このループを回避または高速化するにはどうすればよいですか? Rニュース、8(1):46-50、2008年5月。

「ループ!」セクション(pg 48以降)、彼らは言う:

Rに関する多くのコメントは、ループの使用は特に悪い考えだと述べています。これは必ずしも真実ではありません。特定のケースでは、ベクトル化されたコードを記述することが困難であるか、ベクトル化されたコードが大量のメモリを消費する場合があります。

彼らはさらに提案します:

  • ループ内でサイズを増やすのではなく、ループの前に新しいオブジェクトを完全な長さに初期化します。
  • ループ外で実行できることをループで実行しないでください。
  • ループを回避するためだけにループを回避しないでください。

forループには1.3秒かかりますが、applyのメモリが不足する簡単な例があります。

76
Karl

提起された質問に対する唯一の回答は;ループはnot遅いif何らかの機能を実行するデータのセットを繰り返し処理する必要があるのは、その機能または操作ベクトル化されていません。 for()ループは、一般にapply()と同じくらい速くなりますが、lapply()呼び出しよりも少し遅くなります。最後のポイントはSOで十分にカバーされています。たとえば、この Answer は、loopのセットアップと操作に関係するコードが重要な場合に適用されますloopの全体的な計算負荷の一部。

多くの人がfor()ループが遅いと思うのは、ユーザーが悪いコードを書いているからです。一般的に(いくつかの例外はありますが)、オブジェクトを展開/成長する必要がある場合、それもコピーを伴うため、コピーのオーバーヘッドオブジェクトの成長の両方があります。これは単にループに制限されているわけではありませんが、ループの各反復でコピー/成長を行うと、当然、コピー/成長操作が多数発生するため、ループが遅くなります。

Rでfor()ループを使用する一般的なイディオムは、ループを開始する前に必要なストレージを割り当て、割り当てられたオブジェクトを埋めることです。そのイディオムに従えば、ループは遅くなりません。これはapply()が管理するものですが、ビューからは隠されています。

もちろん、for()ループで実装している操作に対してベクトル化された関数が存在する場合、それをしないでください。同様に、ベクトル化された関数が存在する場合は、do n'tapply()などを使用します(例:apply(foo, 2, mean)は、 colMeans(foo))。

34
Gavin Simpson

比較として(あまり読みすぎないでください!):Rで(非常に)シンプルなforループを実行し、ChromeとIE8.で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() )

@ギャビンシンプソン:ところで、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;
9
Tommy