ポイント
- 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 件のコメント:
コメントを投稿
コメント表示は承認制に設定しています