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


86

ループが遅いので、R代わりにベクトル化された方法で物事を実行しようとする必要があることを私は知っています。

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

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

「なぜベクトル化が速いのですか?」


3
Rの「applyはforループよりもずっと速い」というのはちょっとした神話だという印象を受けました。してみましょうsystem.time...答えで戦争が始まる
joran

1
トピックのここで良い情報がたくさん:stackoverflow.com/questions/2275896/...
チェイス

7
記録のために:適用はベクトル化ではありません。Applyは、さまざまな(例:no)副作用のあるループ構造です。@Chaseのリンク先のディスカッションを参照してください。
Joris Meys 2011

4
SS-Plus?)のループは伝統的に遅いものでした。これはRには当てはまりません。そのため、あなたの質問は実際には関係ありません。今日のS-Plusの状況はわかりません。
Gavin Simpson

4
なぜこの質問が大幅に投票されたのかは私にはわかりません。この質問は他の地域からRに来る人々の間で非常に一般的であり、FAQに追加する必要があります。
patrickmdnet 2011

回答:


69

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

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

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

そのため、次のようなタイミングが得られます(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(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
    }
})

(その例はラドフォードニールによって発見されました)

そのため(Rに演算子であり、そして実際にあなたがそれを使用するたびに、ルックアップ名が必要です。

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

または、一般に、(任意の言語での)解釈された操作にはより多くのステップがあります。もちろん、これらのステップは、同様の利点を提供します。あなたができなかったという(C.でトリックを


10
では、最後の例のポイントは何ですか?Rで愚かなことをして、それがすぐにそれらを行うことを期待しませんか?
チェイス

6
@Chaseそれが1つの言い方だと思います。ええ、私はCのような言語はネストされた括弧で速度の違いがないことを意味しましたが、Rは最適化もコンパイルもしません。
オーウェン

1
また、()、またはループ本体の{}-これらはすべて名前のルックアップを伴います。または、一般的に、Rでもっと書くと、インタプリタはもっと多くのことをします。
オーウェン

1
for()ループで何をしようとしているのかわかりませんか?彼らはまったく同じことをしていません。for()ループは、それぞれの要素を反復処理されA、それらを合計します。apply()コールは、全体のベクトルを渡しているA[,1](あなたがAベクトル化機能への単一の列を持っています)mean()。これがどのように議論に役立つのかわかりませんし、状況を混乱させるだけです。
Gavin Simpson

3
@オーウェン私はあなたの一般的な点に同意します、そしてそれは重要なものです。Rは速度の記録を更新しているため使用していません。使いやすく、非常に強力であるため使用しています。その力には解釈の代償が伴います。for()vsのapply()例で何を表示しようとしていたのかがはっきりしませんでした。合計が平均の計算の大部分である一方で、実際に示されているのは、mean()要素に対するCのような反復でのベクトル化された関数の速度であるため、この例を削除する必要があると思います。
Gavin Simpson

78

ループが遅くてapply速いとは限りません。これについては、2008年5月のRNewsの号で素晴らしい議論があります。

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

「おっと!」のセクションで (48ページから)、彼らは言う:

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

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

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

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


35

提起された質問に対する唯一の答えは; ある関数を実行するデータのセットを反復処理する必要があり、その関数または操作がベクトル化されていない場合、ループは遅くありません。for()ループは、迅速な一般的には、などのようになりますapply()が、おそらく少し遅くなるよりもlapply()コール。最後のポイントは、たとえばこの回答でSOについて十分に説明されており、ループの設定と操作に関連するコードがループの全体的な計算負荷の重要な部分である場合に適用されます

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

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

もちろん、for()ループで実装している操作にベクトル化された関数が存在する場合は、それを行わないでください。同様に、ベクトル化された関数が存在する場合は、etcを使用しないくださいapply()(たとえば、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;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.