開発のヒホ

iOSとかAndroidとかのアプリを開発するのに四苦八苦するブログ

iOSのvDSP(Accelerate.framework)とEigenの行列の掛け算の実行速度テスト

 ※2013/12/20追記※
 ↓Steve氏のコメントを受けて再検証しました。↓
 iOSのBLAS(Accelerate.framework)とEigenの行列の掛け算の実行速度テスト - 開発のヒホ


結論 : Eigenの方が早い。

 iOSの端末で行列計算するには、普通にforでぶん回して計算するよりも、Accelerate.frameworkにあるvDSPを使うのが速いと言われております。
 Accelerate Framework Reference

 でもAccelerate.frameworkはiOSMacOS限定なわけですね。
 できれば将来Androidに移植することも考えて汎用的なC++コードで書きたいわけですよ。

 そこで見つけてきたのがEigenというライブラリ。
 Eigen

 それなりに早そう。
 Accelerate.frameworkには劣るだろうけど移植のことも考えてこれ使わせてもらおうかな。

 ということでとりあえずスピードテストをしてみました。

    const int SIZE = 150 ;
    
    Matrix<float, SIZE, SIZE> mat1 = Matrix<float, SIZE, SIZE>::Random() ;
    Matrix<float, SIZE, SIZE> mat2 = Matrix<float, SIZE, SIZE>::Random() ;
    
    float* mat1_ = (float *)malloc(sizeof(float)*SIZE*SIZE) ;
    float* mat2_ = (float *)malloc(sizeof(float)*SIZE*SIZE) ;
    for( int i=0 ; i<SIZE ; i++ ) {
        for( int j=0 ; j<SIZE ; j++ ) {
            mat1_[j+i*SIZE] = mat1(i, j) ;
            mat2_[j+i*SIZE] = mat2(i, j) ;
        }
    }
    
    __block NSTimeInterval time_eigen = 0 ;
    __block NSTimeInterval time_dsp = 0 ;
    __block NSTimeInterval time_forloop = 0 ;
    
    // multiplication by eigen
    void (^testEigen)() = ^{
        NSTimeInterval time_start = [NSDate timeIntervalSinceReferenceDate] ;
        for( int i=0 ; i<10 ; i++ ) {
            volatile Matrix<float, SIZE, SIZE> mat_ans = mat1 * mat2 ;
            //for( int a=SIZE-3 ; a<SIZE ; a++ ) { printf("%f\n", mat_ans(a, a)) ; }
        }
        NSTimeInterval time_stop = [NSDate timeIntervalSinceReferenceDate] ;
        time_eigen += time_stop - time_start ;
    } ;
    
    // multiplication by vDSP
    void (^testDSP)() = ^{
        NSTimeInterval time_start = [NSDate timeIntervalSinceReferenceDate] ;
        for( int i=0 ; i<10 ; i++ ) {
            float* volatile mat_ans_ = (float *)malloc(sizeof(float)*SIZE*SIZE) ;
            vDSP_mmul(mat1_, 1, mat2_, 1, mat_ans_, 1, SIZE, SIZE, SIZE) ;
            //for( int a=SIZE-3 ; a<SIZE ; a++ ) { printf("%f\n", mat_ans_[a+a*SIZE]) ; }
            free(mat_ans_) ;
        }
        NSTimeInterval time_stop = [NSDate timeIntervalSinceReferenceDate] ;
        time_dsp += time_stop - time_start ;
    } ;
    
    // multiplication by my hand
    void (^testForloop)() = ^{
        NSTimeInterval time_start = [NSDate timeIntervalSinceReferenceDate] ;
        for( int i=0 ; i<10 ; i++ ) {
            float* volatile mat_ans_ = (float *)malloc(sizeof(float)*SIZE*SIZE) ;
            for( int x=0 ; x<SIZE ; x++ ) {
                for( int y=0 ; y<SIZE ; y++ ) {
                    float sum=0 ;
                    for( int xx=0 ; xx<SIZE ; xx++ ) {
                        sum += mat1_[xx+y*SIZE] * mat2_[x+xx*SIZE] ;
                    }
                    mat_ans_[x+y*SIZE] = sum ;
                }
            }
            //for( int a=SIZE-3 ; a<SIZE ; a++ ) { printf("%f\n", mat_ans_[a+a*SIZE]) ; }
            free(mat_ans_) ;
        }
        NSTimeInterval time_stop = [NSDate timeIntervalSinceReferenceDate] ;
        time_forloop += time_stop - time_start ;
    } ;
    
    for( int i=0 ; i<10 ; i++ ) {
        testEigen() ;
        testDSP() ;
        testForloop() ;
    }
    
    printf("SIZE : %d\n", SIZE) ;
    printf("Eigen : %f\n", time_eigen) ;
    printf("vDSP : %f\n", time_dsp) ;
    printf("ForLoop : %f\n", time_forloop) ;
    
    return ;

 2つの行列を掛け算するだけの簡単なお仕事。
 3種類(Eiganを使ったもの、vDSPを使ったもの、forでぶん回したもの)を用意して測定します。

 第4世代iPod touchで実行した結果は以下のとおり・・・

SIZE : 150
Eigen : 42.472766
vDSP : 3.634694
ForLoop : 36.157178

 実行時間が表示されているので、値が少ないほど速いです。
 Eigenおっそ!!!

 いやいや、いくらなんでもforで回したのより遅いのはありえないだろ・・・

 こうゆうときに疑うのが最適化オプションですね。
 知っていると「ああ、これか」と思うのですが、知らないとアホみたいに遅いことに混乱しそうですね。

 Xcodeで最適化オプションをいじるのは.xcodeprojBuildSettingsOptimization Levelにあるらしい。

f:id:hihokaruta:20131218233056p:plain

 ビンゴ!
 Debugのとこが-O0(最適化しない)になっているので、-Os(頑張って最適化)にします。

f:id:hihokaruta:20131218233302p:plain

 実行結果は・・・

SIZE : 150
Eigen : 0.716408
vDSP : 3.606088
ForLoop : 3.595570

 Eigenはっや!!!
 驚いたことにvDSPよりも速い。理由はよくわかりませんが、かなり驚きです。恐るべし。

 ちなみに、こうゆうライブラリは一長一短だったりします。
 よくあるのが、行列のサイズが小さいと結果が違ってたきたりします。

 行列を150×150から10×10にしてみると・・・

SIZE : 10
Eigen : 0.000794
vDSP : 0.001283
ForLoop : 0.001237

 ・・・あれ?
 Eigen優秀ですね・・・

 よし、Eigen使お。