R
でループが遅いことと、代わりにベクトル化された方法で処理を行う必要があることを知っています。
しかし、なぜ?なぜループが遅く、apply
が速いのですか? apply
はいくつかのサブ関数を呼び出しますが、高速ではないようです。
更新:申し訳ありませんが、質問は不適切です。ベクトル化をapply
と混同していました。私の質問は、
"なぜベクトル化が高速化されるのですか?"
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できませんでした。
ループが低速でapply
が高速であるとは限りません。 2008年5月、R Newsの発行 でこれに関する素晴らしい議論があります:
Uwe LiggesとJohn Fox。 Rヘルプデスク:このループを回避または高速化するにはどうすればよいですか? Rニュース、8(1):46-50、2008年5月。
「ループ!」セクション(pg 48以降)、彼らは言う:
Rに関する多くのコメントは、ループの使用は特に悪い考えだと述べています。これは必ずしも真実ではありません。特定のケースでは、ベクトル化されたコードを記述することが困難であるか、ベクトル化されたコードが大量のメモリを消費する場合があります。
彼らはさらに提案します:
- ループ内でサイズを増やすのではなく、ループの前に新しいオブジェクトを完全な長さに初期化します。
- ループ外で実行できることをループで実行しないでください。
- ループを回避するためだけにループを回避しないでください。
for
ループには1.3秒かかりますが、apply
のメモリが不足する簡単な例があります。
提起された質問に対する唯一の回答は;ループはnot遅いif何らかの機能を実行するデータのセットを繰り返し処理する必要があるのは、その機能または操作ベクトル化されていません。 for()
ループは、一般にapply()
と同じくらい速くなりますが、lapply()
呼び出しよりも少し遅くなります。最後のポイントはSOで十分にカバーされています。たとえば、この Answer は、loopのセットアップと操作に関係するコードが重要な場合に適用されますloopの全体的な計算負荷の一部。
多くの人がfor()
ループが遅いと思うのは、ユーザーが悪いコードを書いているからです。一般的に(いくつかの例外はありますが)、オブジェクトを展開/成長する必要がある場合、それもコピーを伴うため、コピーのオーバーヘッドとオブジェクトの成長の両方があります。これは単にループに制限されているわけではありませんが、ループの各反復でコピー/成長を行うと、当然、コピー/成長操作が多数発生するため、ループが遅くなります。
Rでfor()
ループを使用する一般的なイディオムは、ループを開始する前に必要なストレージを割り当て、割り当てられたオブジェクトを埋めることです。そのイディオムに従えば、ループは遅くなりません。これはapply()
が管理するものですが、ビューからは隠されています。
もちろん、for()
ループで実装している操作に対してベクトル化された関数が存在する場合、それをしないでください。同様に、ベクトル化された関数が存在する場合は、do n'tapply()
などを使用します(例:apply(foo, 2, mean)
は、 colMeans(foo)
)。
比較として(あまり読みすぎないでください!):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;