ポイント
- FILETIME構造体、ULONGLONG型、ULARGE_INTEGER型の変換は簡単だ。ULONGLONG型を基準に考えるのが良いかもしれない
- __int64型について。ULONGLONG型の正体はunsigned __int64型である
- __int64型のオーバーフローしない加算・乗算について。キャストのルールは16ビット整数や32ビット整数と同じように考えて良いようだ
開発・実行環境
プロセッサ: Intel(R) Core(TM) i5-5200U CPU @2.20GHz
メモリ: 8.00 GB
OS: Windows 8.1 (64bit)
compiler: Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x86
Visual Studio: 14.0
ワンポイント
テスト用の関数とマクロ
#define show(EXPRESSION) (_show(#EXPRESSION, (EXPRESSION))) void _show(const char* text, unsigned __int64 p) { printf("%s\n", text); printf(" 0x%I64x\n", p); printf("\n"); }
本題に入る前に、工夫した点について説明する。
このマクロと関数を用いることで、プログラムの計算式と結果を両方同時にコンソールに表示することができる。
符号なし64ビット整数 unsigned __int64 の16進数のフォーマット指定子は%I64xを用いる[1]。
FILETIME、ULONGLONG、ULARGE_INTEGER
CPU時間を取得するGetSystemTimesなどのAPI関数は、測定したCPUのクロックサイクルをFILETIME構造体を通してプログラマに伝える。
FILETIME構造体をそのまま四則演算に用いることはできない。加算や減算といった演算を行うためには、一度ULONGLONG型もしくはULARGE_INTEGER型に変換するのが便利である。
ULONGLONG
ULONGLONG a; show(a = 0x0123456789abcdef);
ULONGLONG型はunsigned __int64のシノニム[2]である。
これを実行すると以下のようになる。
a = 0x0123456789abcdef 0x123456789abcdef
ULARGE_INTEGER
ULARGE_INTEGER b; show(b.QuadPart = a); show(b.LowPart); show(b.HighPart);
ULARGE_INTEGER は64ビット整数を含む共用体である[3]。
QuadPartメンバはULONGLONG型であるため、そのまま代入できる。
LowPartメンバとHighPartメンバは、それぞれ上位32ビットと下位32ビットである。
これを実行すると以下のようになる。
b.QuadPart = a 0x123456789abcdef b.LowPart 0x89abcdef b.HighPart 0x1234567
FILETIME
FILETIME c; show(c.dwLowDateTime = (DWORD)(a & 0xffffffff)); show(c.dwHighDateTime = (DWORD)(a >> 32)); show(((unsigned __int64)c.dwHighDateTime << 32) | c.dwLowDateTime);
FILETIME型はULARGE_INTEGER型の同様、上位32ビットと下位32ビットごとにアクセスできるメンバを持つ[4]。
しかしながら、ULARGE_INTEGER型のQuadPataメンバのように一発で64bit整数を取得できるメンバが存在しない点が異なっている。
そのため、一度ULONGLONG型変数に代入する[5]。
これを実行すると以下のようになる。
c.dwLowDateTime = (DWORD)(a & 0xffffffff) 0x89abcdef c.dwHighDateTime = (DWORD)(a >> 32) 0x1234567 ((unsigned __int64)c.dwHighDateTime << 32) | c.dwLowDateTime 0x123456789abcdef
以下のようにやろうとすると、下位32ビット分しか計算できずに失敗する(コンパイラに警告される)。
show((unsigned)(c.dwHighDateTime << 32) | c.dwLowDateTime); show((c.dwHighDateTime << 32) | c.dwLowDateTime);
さて、ULONGLONG型、FILETIME型、ULARGE_INTEGER型についての説明が終わった。
これらの64ビット整数を扱う型はすべてunsigned __int64型がもとになっていることが分かった。
よって、64ビット整数の演算について考えるということは、__int64型の計算について考えることであるといってもよいだろう(良いのか?)。
ここではそういうことにする。
__int64型
ポータブルなコードを実現するための型名である。
__int64型のオーバーフローしない加算・乗算
キャストの仕方によっては、64ビットで計算されなくなってしまう[7]。
とりあえず、例にならい加算と乗算についてオーバーフローしないキャストの仕方を調べた。
キャストについては奥深いものがある[7]ので、落とし穴を把握しておくことは大切だ。
加算のテストコード
unsigned int p = 0x10000000; unsigned int q = 0xffffffff; printf("p = 0x%8x\n", p); printf("q = 0x%8x\n", q); printf("\n"); show(p + q); show((unsigned __int64)p + q); show((unsigned __int64)(p + q)); show((unsigned __int64)p + (unsigned __int64)q);
pとqはともに32ビットの符号なし整数である。
キャストなしで加える、加えて括弧なしでキャスト、加えて括弧してキャスト、キャストしてから加える、の四通りのパターンを試す。
加算の実行結果
p = 0x10000000 q = 0xffffffff p + q 0xfffffff (unsigned __int64)p + q 0x10fffffff (unsigned __int64)(p + q) 0xfffffff (unsigned __int64)p + (unsigned __int64)q 0x10fffffff
乗算のテストコード
unsigned int r = 0x80000000; unsigned int s = 2; printf("r = 0x%8x\n", r); printf("s = %d\n", s); printf("\n"); show(p * r); show((unsigned __int64)p * r); show((unsigned __int64)(p * r)); show((unsigned __int64)p * (unsigned __int64)r);
rとsはともに32ビットの符号なし整数である。
キャストなしで掛ける、掛けて括弧なしでキャスト、掛けて括弧してキャスト、キャストしてから掛ける、の四通りのパターンを試す。
乗算の実行結果
r = 0x80000000 s = 2 r * s 0x0 (unsigned __int64)r * s 0x100000000 (unsigned __int64)(r * s) 0x0 (unsigned __int64)r * (unsigned __int64)s 0x100000000
まとめ
この性質を最も直線的に示す型がULONGLONG型であるため、パフォーマンス測定系のクラスを作成する際には、ULONGLONG型を基底として用いるのがわかりやすいだろう。
あと、64ビット整数のキャストについては、16ビット整数や32ビット整数に当てはまるルールがそのまま当てはまることが分かった。
何か特別なことをしなくても、自然にコーディングすれば事故は起こらないだろう。
感想
CPUパフォーマンス測定はなかなか完了しないなぁ。
0 件のコメント:
コメントを投稿
コメント表示は承認制に設定しています