アライメント
通常の業務ソフトや軽いソフトなどは別に気にする必要性はあまりありませんが、
CPUを最大限に活かしたいゲームなど、高速処理が売りのソフトなどは
気にしながら作成しないと、痛い目を見ます。
ですが、VCの場合だと勝手にアライメントを整えてくれているように見えるのですが・・・*1
そもそもアライメントとは、CPUが一回の処理で引っ張ってこれるデータサイズです。
アライメントがずれるとは、
16バイトアライメントのCPUに17バイトの構造体を定義してアクセルすると
16バイト+1バイトを取得するため合計で2回アクセルすることになります。
まあ2回アクセルする事自体は問題ないです。
問題なのは、次のデータが2バイト目からスタートすることが問題になります。
■=1バイトメモリ
とした場合
■■■■■■■■■■■■■■■■
データサイズが16バイトぴったりだと、一回のアクセスで終わり。
■■■■■■■■■■■■■■■■
■○
データサイズが17バイトだと2回のアクセスで終わり。
で、次のデータ位置は○の場所。
もし次のデータサイズが15バイトだと、問題ありませんが、
16バイト以上だと、2回アクセスする必要があります。
元々1回で、済むものが2回に増えます。
しかも一度ずれるとその後がずれっぱなしなので、
アライメントと同じサイズでも2回アクセスしてしまいます。
アライメントを跨いだデータを取得する場合、
全てのデータを取得するまで、CPU側で保持する必要性が出てきて、
無駄にクロック数を奪われてしまいます。
ちなみにアライメント以下の場合でも
CPUはアライメントサイズでしか、データを取得できません。
アライメントが16バイトの場合8バイトと16バイトのアクセス速度は変わりません。
これはVCで適当に8バイトと16バイトの構造体を定義して、アクセス速度を比較したら分かります。*2
アライメントの復讐
アライメントを意識せずにプログラムを作成すると痛い仕打ちを食らうケースがあります。
暗号化(Crypt)
DESやAESなどの暗号方式の暗号アルゴリズムは、ビットレベルで適当にスクランブルを行い暗号処理を行います。*3
その時にアライメントを気にしていないと、
その空白領域にゴミが入ってしまい複合処理に失敗してしまう場合があります。
struct DATA { int a; int b; int c; //<- 残り4バイト暗黙的に領域確保されます。(VCだと) }; struct DATA2 { int a; int b; int c; int pad; //<- 明示的にしたほうがバグも発見しやすくなります。 }; void f(){ DATA data ={0,0,0}; DATA data2; memset( &data2, 0, sizeof(DATA) ); }
上記f()の中にあるdataとdata2をビット単位で比較すると一致しません。
アライメントの整え方
プログラムでアライメントを整える場合、次のようにすれば整います。
size_t alignment = 16; size_t mask = alignment - 1; void* pPtr = ((size_t*)(pPtr) + mask) & ~mask;
これはビット計算をチマチマやれば分かります。
16バイトを手計算するとめんどくさいので
アライメントを8ビットでやってみます。
サンプル1
データを7ビットとする。
この場合計算結果が8ビットになればいい。
8ビット+7ビット=15ビット
15ビットを2進化 = 1111
7ビットを2進化 = 0111
2進化した7ビットを反転 = 1000
15ビットと反転7ビットをAND判定 1111 & 1000 = 1000
1000を10進化 = 8ビット
サンプル2
データを9ビットとする
この場合計算結果が16ビットになればいい。
8ビット+9ビット=17ビット
17ビットを2進化 = 0001 0001
7ビットを2進化 = 0000 0111
2進化した7ビットを反転 = 1111 1000
17ビットと反転7ビットをAND判定 0001 0001 & 1111 1000 = 0001 0000
0001 0000 を10進化 = 16ビット