R はライブラリも充実していて非常に便利ですが、
他の言語に比べて処理が遅いという印象があります。
処理が遅いと言っても、
プログラムの書き方によってかなり処理時間が変わってくるので、
いわゆるRのお作法のようなものを抑えておけば、
気にならないほど高速に実行できるというケースも多々あります。
今回は、繰り返し行う処理結果を、
可変長のベクトルに格納していくというよくある処理について、
高速化のための書き方を抑えてみましょう。
繰り返し結果の格納
ある処理を何回か繰り返し、
それぞれの処理の結果をベクトルに格納するというのはよくあることです。
例えば、コイントスをシミュレートして、
各コイントスの結果を格納していくということが考えられます。
R では、ベクトルに要素を追加する操作は、
vector = c(vector,element)
で実行できるので、
for(i in 1:N){ result = c(result, cointoss()) }
のよう形で、コイントスの結果を順に格納した
ベクトルresult
が得られます。
ところが、このコードを実行する時、
繰り返しの回数が大きいとかなり処理に時間がかかってしまいます。
処理時間がかかる原因
上の処理で時間がかかってしまう原因は、
ベクトルの結合演算c()
にあります。
result = c(result,cointoss())
という書き方を見れば分かるように、
このベクトルの結合は、元のベクトルの後ろに要素を追加するというよりは、
元のベクトルをコピーして、新しい要素を加えたものを、
新しいベクトルにするという処理になります。
このため、繰り返し回数が多くなると、
繰り返しの後半では、毎回、非常に長いベクトルを、
コピーする必要があり、処理が遅くなってしまうのです。
ということで、元のデータをコピーせずに、
単に、後ろにデータを付け足すという処理ができれば、
高速化ができそうですね。
これは、リストを使うことでできます。
リストを使った高速化
R のリストでは、新たな要素を付け足す場合、
直接、次のインデックスの要素 alength(a)+1 に代入すればオッケーです。
そのため、上の例と同じ処理は、
次のようにして実行することができます。
for(i in 1:N){ result[[length(result)]] = cointoss() }
結果はリストとして得られますが、
ベクトルに直したければ、unlist(result)
とすればオッケーです。
実際の計算時間例
上の高速化でどの程度、処理時間が早くなるかを見てみましょう。
ベクトルの結合を利用した場合
まずは、ベクトルの結合を利用した場合です。
空のベクトルに10万回1を付け足してしてみましょう。
N=100000 system.time( { a = NULL for(i in 1:N) a = c(a,1) } )
かかった時間は、10.12秒でした。
リストを使った場合
次にリストを使って同じことをしてみます。
system.time( { a = list() for(i in 1:N) a[[length(a)+1]] = 1 a = unlist(a) } )
こちらにかかった時間は、0.05秒でした。
明らかにリストを使った方が高速であることが分かります。
まとめ
R の高速化のためには、
ベクトルへの要素の追加は、
ベクトルの結合を使うのではなく、
リストへの代入を使うべきですね。