ループが遅いので、R
代わりにベクトル化された方法で物事を実行しようとする必要があることを私は知っています。
しかし、なぜ?ループが遅くてapply
速いのはなぜですか?apply
いくつかのサブ関数を呼び出します-それは速くはないようです。
更新:申し訳ありませんが、質問は不適切でした。ベクトル化とを混同していましたapply
。私の質問は、
「なぜベクトル化が速いのですか?」
ループが遅いので、R
代わりにベクトル化された方法で物事を実行しようとする必要があることを私は知っています。
しかし、なぜ?ループが遅くてapply
速いのはなぜですか?apply
いくつかのサブ関数を呼び出します-それは速くはないようです。
更新:申し訳ありませんが、質問は不適切でした。ベクトル化とを混同していましたapply
。私の質問は、
「なぜベクトル化が速いのですか?」
回答:
Rのループは、インタープリター型言語が遅いのと同じ理由で遅いです。すべての操作は、多くの余分な荷物を運びます。
見てくださいR_execClosure
中eval.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.でトリックを
for()
ループで何をしようとしているのかわかりませんか?彼らはまったく同じことをしていません。for()
ループは、それぞれの要素を反復処理されA
、それらを合計します。apply()
コールは、全体のベクトルを渡しているA[,1]
(あなたがA
ベクトル化機能への単一の列を持っています)mean()
。これがどのように議論に役立つのかわかりませんし、状況を混乱させるだけです。
for()
vsのapply()
例で何を表示しようとしていたのかがはっきりしませんでした。合計が平均の計算の大部分である一方で、実際に示されているのは、mean()
要素に対するCのような反復でのベクトル化された関数の速度であるため、この例を削除する必要があると思います。
ループが遅くてapply
速いとは限りません。これについては、2008年5月のRNewsの号で素晴らしい議論があります。
UweLiggesとJohnFox。Rヘルプデスク:このループを回避または高速化するにはどうすればよいですか?R News、8(1):46-50、2008年5月。
「おっと!」のセクションで (48ページから)、彼らは言う:
Rに関する多くのコメントは、ループの使用は特に悪い考えであると述べています。これは必ずしも真実ではありません。場合によっては、ベクトル化されたコードを書くのが難しいか、ベクトル化されたコードが大量のメモリを消費する可能性があります。
彼らはさらに提案します:
- ループ内でサイズを増やすのではなく、ループの前に新しいオブジェクトを完全な長さに初期化します。
- ループの外側で実行できることをループ内で実行しないでください。
- 単にループを回避するためにループを回避しないでください。
for
ループに1.3秒かかるがapply
、メモリが不足するという簡単な例があります。
提起された質問に対する唯一の答えは; ある関数を実行するデータのセットを反復処理する必要があり、その関数または操作がベクトル化されていない場合、ループは遅くありません。for()
ループは、迅速な一般的には、などのようになりますapply()
が、おそらく少し遅くなるよりもlapply()
コール。最後のポイントは、たとえばこの回答でSOについて十分に説明されており、ループの設定と操作に関連するコードがループの全体的な計算負荷の重要な部分である場合に適用されます。
多くの人がfor()
ループが遅いと思うのは、ユーザーである彼らが悪いコードを書いているからです。一般に(いくつかの例外はありますが)、オブジェクトを展開/拡大する必要がある場合、それもコピーを伴うため、オブジェクトのコピーと拡大の両方のオーバーヘッドが発生します。これはループに限定されるだけでなく、ループの各反復でコピー/成長する場合、もちろん、多くのコピー/成長操作が発生するため、ループは遅くなります。
for()
Rでループを使用する一般的なイディオムは、ループが開始する前に必要なストレージを割り当ててから、割り当てられたオブジェクトを入力することです。そのイディオムに従えば、ループは遅くなりません。これはapply()
あなたのために管理するものですが、それは単に視界から隠されています。
もちろん、for()
ループで実装している操作にベクトル化された関数が存在する場合は、それを行わないでください。同様に、ベクトル化された関数が存在する場合は、etcを使用しないでくださいapply()
(たとえば、apply(foo, 2, mean)
を介して実行する方が適切ですcolMeans(foo)
)。
比較と同じように(あまり読みすぎないでください!):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;
system.time
...答えで戦争が始まる