爆速っぷりを数値で示してみる
これとこれの続きです。簡単な例を用いてC拡張の爆速ぶりを定量的に評価してみます。
条件など
大きな行列を用意してやって、その要素の全ての和を求めてやろうという試みですが、以下の3つで比較してみます。
- forで要素一つ一つにアクセスして和を求める
- applyとsumを組み合わせてベクトルで処理
- Cに投げる
として、それぞれの関数を用意します。
行列は全ての要素が1の正方行列で大きさは関数の引数で決めてやり、行列を作成する部分は全ての関数に加えておきます。
実はこの部分を書いている間にプログラムを動かしているので、共用ライブラリの読み込み時間等を考慮すると、実は総合的にはapplyの方が速かったとかいう結果にならないか若干心配です。
手順
前回と全く同じ手順でできますが…
行列の要素の和を求めるCの関数をmatsum.cとしてこのように書いておきます。
void matsum(int *mat, int *row, int *col, int *sum) { int i, j; *sum = 0; for (i = 0; i < *row; i++) { for (j = 0; j < * col; j++) { *sum += mat[i*(*col) + j]; } } }
これを
$R CMD SHLIB matsum.c
として共用ライブラリ、matsum.soを作ってやります。
後は、Rから共用ライブラリを呼び出します。record3がそれに相当します。
今回は行列の大きさが100×100の場合と、10000×10000の場合で実験しました。
# forで頑張る>< record <- function(n) { mat <- matrix(rep(1,n),n,n) sum <- 0 for (i in 1:n) { for (j in 1:n) { sum <- sum + mat[i,j] } } return(sum) } # ちょっと工夫してapplyとsum record2 <- function(n) { mat <- matrix(rep(1,n),n,n) sum <- sum(apply(mat,1,sum)) return(sum) } # C拡張をくらいやがれ! record3 <- function(n) { mat <- matrix(rep(1,n),n,n) dyn.load("matsum.so") matsum <- function(mat) { .C("matsum", as.integer(mat), as.integer(n), as.integer(n), sum = as.integer(0))$sum } sum <- matsum(mat) return(sum) } system.time(record(100)) system.time(record2(100)) system.time(record3(100)) system.time(record(10000)) system.time(record2(10000)) system.time(record3(10000))
ドキドキの実行結果
> system.time(record(100)) ユーザ システム 経過 0.027 0.000 0.027 > system.time(record2(100)) ユーザ システム 経過 0.002 0.000 0.002 > system.time(record3(100)) ユーザ システム 経過 0.000 0.000 0.002 > > system.time(record(10000)) ユーザ システム 経過 270.670 2.993 279.753 > system.time(record2(10000)) ユーザ システム 経過 11.385 10.081 333.346 > system.time(record3(10000)) ユーザ システム 経過 3.816 2.759 27.624
record2(10000)の経過時間の結果が若干気になるところではありますが、概ね予想通りの結果が得られました。
C拡張最強ですね!!あと、forの多重ループはRにとって鬼門すぎますね!!