CPUのクロックを測定する


Pentium以降のCPUでは,起動されてからの総クロック数を取得するRDTSC命令が サポートされている. RDTSC命令を呼び出すと,総クロック数の下位4バイトがeaxレジスタに, 上位4バイトがedxレジスタに格納される. したがって,ある時点とその1秒後の総クロック数をそれぞれ取得すれば,両者の差が いわゆるCPUクロック(Hz)になる.

しかしながら,Delphi5のインラインアセンブラではRDTSC命令はサポートされていないらしく, コンパイル時にエラーとなる. いろいろ調べてみると,DW命令にアドレス$310Fを指定することで, RDTSCの擬似命令を発行することができるようだ.

総クロック数の引き算を行うためには,読み出したeaxレジスタとedxレジスタの値を 二つの4バイト変数にコピーし,これらからInt64型の変数を生成する必要がある.

ある時点の総クロック数を得るには,例えば以下のようにする.

var
  CurrentLo, CurrentHi: DWORD;
  Current: Int64;
  ......
  
  asm
     DW   $310F          // RDTSC
     mov CurrentLo, eax
     mov CurrentHi, edx
   end;
   Current := CurrentHi;
   Current := CurrentLo or Current shl 32;

まず,RDTSCの擬似命令を発行し,eaxおよびedxをそれぞれDWORD値にコピーする. それから,上位バイトのDWORD値をInt64型変数に代入し, それを左に32ビットだけシフトさせ,下位バイトのDWORD値とのor(ビットごとの論理和)をとる.

与えられた測定時間内のクロック数を得るために,以下のような関数GetCPUClockを作った.

function GetCPUClock( iWait: DWORD ): DWORD;
var
  TickTime, StartLo, StartHi, CurrentLo, CurrentHi: DWORD;
  Start, Current, Clock: Int64;
begin
   if iWait > 1000 then iWait := 1000;
   TickTime := GetTickCount;
   asm
     DW   $310F
     mov StartLo, eax
     mov StartHi, edx
   end;
   while ( GetTickCount > TickTime + iWait ) do begin end;
   asm
     DW   $310F
     mov CurrentLo, eax
     mov CurrentHi, edx
   end;
   Start := StartHi;
   Start := StartLo or Start shl 32;
   Current := CurrentHi;
   Current := CurrentLo or Current shl 32;
   Clock := Current - Start;
   if Clock > $FFFFFFFF then Clock := $FFFFFFFF;
   Result := Clock;
end;

引数iWaitは測定時間をミリ秒単位で指定し,最大値は1000(=1秒)である. 他の処理系との互換性を考え,戻り値はDWORDに丸める. 結果が$FFFFFFFF(すなわち4GHz)以上なら,桁あふれを防ぐため強制的に$FFFFFFFFとする.

本来ならば,CPUID命令などを使ってRDTSC命令がサポートされているか(Pentium以降のCPUであるか)を 事前に調べた方がいいだろう. サポートされていない命令を発行すると,最悪の場合マシンが落ちる可能性がある. また,eaxやedxの内容をどこかに退避させておいた方がいいかも知れない. この辺りの情報はここここにある.

PentiumIII 600MHzのマシン(Win2K)での実行結果を以下に示す.

procedure TForm1.Button1Click(Sender: TObject);
var
  Clock: DWORD;
begin
   Clock := GetCPUClock( 500 );
   Edit1.Text := FormatFloat( '###.00', Clock * 2 / 1000000 );
end;

BIOS設定の値とは数クロックの誤差があるが,時間測定やタスクスイッチングによるものだろう. AthlonやCyrixIIIでも試してみたが,両者とも一応測定できた.

Athlon 1.4GHz(Win98)
CyrixIII 533MHz(WinMe)

全く同じコードをCでも書いてみた. DLLからエクスポートするための関数である.

#include <windows.h>

DWORD __declspec( dllexport ) __stdcall GetCPUClock( DWORD iWait ){

    __int64 start, current, clock;
    unsigned long start_lo, start_hi, current_lo, current_hi;
    DWORD TickCount, result;

    if ( iWait > 1000 ) iWait = 1000;
    TickCount = GetTickCount();

    __asm{
       rdtsc
       mov start_lo, eax
       mov start_hi, edx
    }

    while ( 1 ) {
        if ( GetTickCount() - TickCount > iWait ) break;
    }

    __asm{
       rdtsc
       mov current_lo, eax
       mov current_hi, edx
    }

    start = start_hi;
    start = start_lo | start << 32;
    current = current_hi;
    current = current_lo | current << 32;
    clock = current - start;
    if ( clock > 0xffffffff ) clock = 0xffffffff;
    result = clock;
    return result;
}

これをVisual C/C++ 5.0でコンパイルしたところ,Delphiと同じく,インラインアセンブラで rdtscがサポートされていないとのエラーメッセージが出た. 仕方が無いので,以下のマクロを追加した.

#define rdtsc __asm __emit 0fh __asm __emit 031h

上記のコードからDLLをビルドすると,エクスポートされる関数名が_GetCPUClock@4になるので, 以下の内容のDEFファイルをプロジェクトに追加してリビルドする.

EXPORTS
GetCPUClock  =  _GetCPUClock@4

なお,VC/C++ 6.0ではrdtscがサポートされているようで,マクロを書かずともコンパイルできた.


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

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