爆速っぷりを数値で示してみる

これこれの続きです。簡単な例を用いてC拡張の爆速ぶりを定量的に評価してみます。

条件など

大きな行列を用意してやって、その要素の全ての和を求めてやろうという試みですが、以下の3つで比較してみます。

  1. forで要素一つ一つにアクセスして和を求める
  2. applyとsumを組み合わせてベクトルで処理
  3. 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にとって鬼門すぎますね!!

まとめ

C拡張は素晴らしい。修論ではforの3重ループが必要だったので、C拡張を利用する恩恵をものすごく受けています。
でもその前に行列計算やベクトルの計算に落とし込めるか、さらにその前にRハッカーな人達がライブラリやパッケージを用意してくれているかよく考えてみようね☆

以上です。