浮動小数点演算のベンチマーク


エラトステネスのふるいに簡単なベンチマークの結果を書いたが, 今度はもう少し骨のある浮動小数点演算でベンチマークを取ってみる. 以下はCardanoの公式を使って任意係数の3次方程式を1,000,000個解き, それに要した時間を表示するものである.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <windows.h>

#define PI 3.14159265358979323846264

int cubic_equation( double a, double b, double c, double d, double roots[] );

int main(){

    double a, b, c, d, roots[ 3 ];
    int i, n;
    DWORD started, terminated;

    started = GetCurrentTime(); 
    for ( i = 1; i <= 1000000; i++ ){
      a = rand(); b = rand(); c = rand(); d = rand();
      if ( a != 0 ) n = cubic_equation( a, b, c, d, roots );
    }
    terminated = GetCurrentTime();
    printf( " total %10.3f seconds\n", ( terminated - started ) * 1e-3 );
    return 0;
}

double cubic_root( double x ){

    double root, prev_root, eps;
    int converged, n;
    
    if ( x == 0 ) return 0;
    else {
       converged = 0;
       root = 1e0;
       n = 1;
       do {
          prev_root = root;
          root -= ( root * root * root - x ) / ( 3 * root * root );
          eps = fabs( root / prev_root - 1e0 );
          if ( ( eps < 1e-8 ) || ( fabs( root ) < 1e-8 ) ) converged = 1;
          n++;
       } while ( converged == 0 );
       return root;
    }
}

int cubic_equation( double a, double b, double c, double d, double roots[] ){

    double p, q, t, a3, b3;
    int num_realroots;

    b /= ( 3 * a );  c /= a;  d /= a;
    p = b * b - c / 3;
    q = ( b * ( c - 2 * b * b ) - d ) / 2;
    a = q * q - p * p * p;

    if ( a > 0 ){
        num_realroots = 1;
        if ( q > 0 ) a3 = cubic_root( q + sqrt( a ) );
        else         a3 = cubic_root( q - sqrt( a ) );
        b3 = p / a3;
        roots[ 0 ] = a3 + b3 - b;
        roots[ 1 ] = -0.5 * ( a3 + b3 ) - b;
        roots[ 2 ] = fabs( a3 - b3 ) * sqrt( 3e0 ) / 2;
    }
    else {
        num_realroots = 3;
        a = sqrt( p );
        t = acos( q / ( p * a ) );
        a *= 2;
        roots[ 0 ] = a * cos( t / 3 ) - b;
        roots[ 1 ] = a * cos( ( t + 2 * PI ) / 3 ) - b;
        roots[ 2 ] = a * cos( ( t + 4 * PI ) / 3 ) - b;
    }
    return num_realroots;
}
関数cubic_rootはf(x)=x^3-a=0の根をNewton-Raphson法で求めるものである. pow(a,1e0/3e0)などとするよりも誤差は小さくなるだろう.

上記コードをVisual C/C++ 5.0でコンパイルしたバイナリ(45KB)

今回はPCだけでなくUNIX系マシンでもベンチマークを取ってみた. ただし,UNIXではGetCurrentTime()などという関数は無いので timeコマンドを使って実行時間を計測する. time はcshおよびtcshの内部コマンドと/usr/bin/timeの2種類がある. 例えば,カレントにあるa.outの実行時間を計測する場合は 以下のようにすればよい.

$ time ./a.out
11.660u 0.010s 0:11.67 100.0%   0+0k 0+0io 82pf+0w
出力結果は「a.outというコマンドの実行には11.67秒かかり, そのうちユーザモードで11.660秒,システムのオーバーヘッドで0.010秒消費した」という意味になる. その後の出力はファイルシステムとの入出力などに関する情報を示している. くわしくはここ

テストしたマシンのCPU,メモリ容量,OSおよびバイナリ作成に用いたコンパイラを下表に示す.

CPU Memory (MB) OS Compiler
Pentium !!! 600MHz128Windows 2000 Visual C/C++ 5.0
Pentium !!! 700MHz256Windows 98SE Visual C/C++ 5.0
Athlon K7 650MHz 256Windows 2000 Visual C/C++ 5.0
Athlon K7 650MHz 256Windows 98SE Visual C/C++ 5.0
Athlon TB 1.2GHz 128Windows Me Visual C/C++ 5.0
Celeron 333MHz 64 Windows Me Visual C/C++ 5.0
K6-2 300MHz 98 RedHat 6.2 gcc ver. egcs-2.91.66
Alpha 667MHz 768Digital UNIX 4.0 Digital UNIX C Compiler 4.0
HP Visualize C180 256HP-UX ver.10.0 HP-UX C Compiler

結果は以下のようになった. コンパイラやOSがそれぞれ異なるので絶対的な比較はできないが,CPU速度比較の おおよその目安にはなるだろう.

やはりAlpha 667MHzがずばぬけて速い. 64bitバスの威力だろう. しかし何故かHP C180は異常に遅い. テストしたマシンの中で一番遅かった. 古めのWSではあるが,現在でも研究室での数値計算に使われている現役マシンである. もしかしたら,他に誰か計算を流していたのかも知れない. RedHat6.2のK6-2 300MHzがWinMeのCeleron333MHzよりも速いのは OS自体の軽さによるものだろう. 同じK7 AthlonでもWin2KとWin98SEではWin2Kの方が10%程度速かった.


その後,研究室のマシンのリプレースが進んだので,いくつかのマシンで上のプログラムを走らせてみた. テストしたマシンのスペックおよび結果は以下の通りである.

CPU Memory (MB) OS Compiler
Celeron 633MHz 128 Windows 98SE Visual C/C++ 5.0
Celeron 300A 64 Windows 98SE Visual C/C++ 5.0
Pentium !!! 1GHz 128 Windows Me Visual C/C++ 5.0
Athlon TB 1.4GHz 128 Windows 98SE Visual C/C++ 5.0
Cyrix III 533MHz 128 Windows Me Visual C/C++ 5.0

Pentium !!! 1GHzは予想通り速い. Athlon 1.4GHzは1.2GHzよりも遅い結果となったが, これは測定誤差+環境の違いによるものだろう. CyrixIIIは極めて遅く(ワースト1),Cyrixの伝統が引き継がれている.


お問い合わせはメールにて: akasaka@klc.ac.jp

戻る
SEO [PR] 爆速!無料ブログ 無料ホームページ開設 無料ライブ放送