ループは遅いので、R
代わりにベクトル化された方法で処理を行う必要があることを知っています。
しかし、なぜ?ループが遅いのにapply
速いのはなぜですか? apply
いくつかのサブ関数を呼び出します -- 高速ではないようです。
更新:申し訳ありませんが、質問の仕方が不適切でした。ベクトル化と を混同していましたapply
。私の質問は、
「なぜベクトル化は速いのですか?」
ループは遅いので、R
代わりにベクトル化された方法で処理を行う必要があることを知っています。
しかし、なぜ?ループが遅いのにapply
速いのはなぜですか? apply
いくつかのサブ関数を呼び出します -- 高速ではないようです。
更新:申し訳ありませんが、質問の仕方が不適切でした。ベクトル化と を混同していましたapply
。私の質問は、
「なぜベクトル化は速いのですか?」
ループが遅くてapply
速いとは限りません。これについては、R News の 2008 年 5 月号にすばらしい議論があります。
ウーヴェ・リゲスとジョン・フォックス。R Help Desk: このループを回避したり、高速化するにはどうすればよいですか? R ニュース、8(1):46-50、2008 年 5 月。
セクション「ループ!」(48ページから)、彼らは言う:
R に関する多くのコメントは、ループの使用は特に悪い考えであると述べています。これは必ずしも真実ではありません。場合によっては、ベクトル化されたコードを記述することが困難であったり、ベクトル化されたコードが大量のメモリを消費することがあります。
彼らはさらに次のように提案しています。
- ループ内でサイズを大きくするのではなく、ループの前に新しいオブジェクトを完全な長さに初期化します。
- ループ外で実行できることをループ内で実行しないでください。
- ループを回避するためだけにループを回避しないでください。
for
ループに1.3秒かかるapply
がメモリ不足になる簡単な例があります。
R のループが遅いのは、インタープリター型言語が遅いのと同じ理由です。すべての操作が多くの追加の荷物を運びます。
R_execClosure
in を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
}
})
(その例はRadford Nealによって発見されました)
(
in R は演算子であり、実際には使用するたびに名前のルックアップが必要になるためです。
> `(` = function(x) 2
> (3)
[1] 2
または、一般に、解釈された操作 (任意の言語で) には、より多くのステップがあります。もちろん、これらの手順には利点もあります。C ではそのようなトリックはできませんでした。(
提起された質問に対する唯一の回答は次のとおりです。ある関数を実行する一連のデータを反復処理する必要があり、その関数または操作がベクトル化されていない場合、ループは遅くありません。通常for()
、ループは と同じくらい高速ですが、呼び出しapply()
よりも少し遅くなる可能性があります。lapply()
最後のポイントは SO で十分にカバーされています。たとえば、この回答では、ループの設定と操作に関与するコードがループの全体的な計算負荷の重要な部分である場合に適用されます。
for()
多くの人がループが遅いと考える理由は、ユーザーが悪いコードを書いているからです。一般に (いくつかの例外はありますが)、オブジェクトを拡張/拡張する必要がある場合は、それにもコピーが必要になるため、オブジェクトのコピーと拡張の両方のオーバーヘッドが発生します。これはループに限ったことではありませんが、もちろん、ループの反復ごとにコピー/拡張すると、多くのコピー/拡張操作が発生するため、ループが遅くなります。
R でループを使用するための一般的なイディオムはfor()
、ループの開始前に必要なストレージを割り当ててから、割り当てられたオブジェクトを埋めることです。そのイディオムに従えば、ループは遅くなりません。これは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;