SSブログ

コンパイラによる3バイト配列同士の演算の最適化 [プログラミング]

(細かいのは後で書くか放置)

C++では参照を使うことでコピー用の一時オブジェクトが省略される場合があるというのは知っていた

そこで某bitboardのu32 p[3]な配列がoperator | で一時オブジェクト無しにアセンブラが吐出されるかを確認してみた

struct bitboard {
   unsigned int p[3];
};
inline bitboard operator | ( const bitboard &l, const bitboard &r ) 
{
    bitboard tmp;
    tmp.p[0] = l.p[0] | r.p[0];
    tmp.p[1] = l.p[1] | r.p[1];
    tmp.p[2] = l.p[2] | r.p[2];
}

bitboard b[3];
b[0] = b[1] | b[2];


みたいな感じで書いた時にどういうアセンブラが出るか

コンパイラはVisutalStudio2008を使った(古い)

当然Releaseビルドで、速度優先最適化をした後、アセンブリコードを吐き出させる

まずインラインによって、push/popがないのは確認した。ここは合格。

次にoperator | の展開だがb[1]とb[2]のpと思われる領域と
レジスタeax,ecx,edxを使用したmov と orが3個ずつ生成され、
ダイレクトにb[0]と思われる領域にmovしていた

mov eax, b[1].p[0]
mov ecx, b[1].p[1]
mov edx, b[1].p[2]
or    eax, b[2].p[0]
or    ecx, b[2].p[1]
or    edx, b[2].p[2]
mov b[1].p[0], eax
mov b[1].p[1], ecx
mov b[1].p[2], edx


3つの汎用データレジスタが一時オブジェクトの代わりになっていて無駄なコピーが発生していない
(ebxを使わないのはなにか歴史的な意味があるのだと思う)

次にコンパイルオプションで /arch:SSEとしても何も変わらなかった

さらに/arch:SSE2としたときに問題がおきた

コピーをmovq(quad word move)を使おうとするのだが、配列は3個しか無い
そこで、変な領域の値(Txxxxx)をコピーしてORした後もとに戻していた

<詳細は覚えていたら後で書く>

「コンパイラは8バイト単位でコピーしつつ、それによって上書きしてしまった領域をもとに戻そうとする」
というコードに変換してしまう

なので、SSE2最適化は8バイトでアライメントされていないとパフォーマンスが落ちる可能性が高い?
(速度的な検証はしていない)

また上記のpの配列を4つ(4*4=16バイト)にしても同じで、operator | が12バイトしか使わないコードであっても最後の4バイトを使うつもりがないとはコンパイラにはわからない

そこで、16バイト化+SSE4.1の128bitmove(movqなんとか)を使用するとわずか3命令で動作するのは確認できた

<これも後で書く>

というわけで、

・8バイト単位のアクセスになっている保証がない場合にはSSE2は使わないほうが良さそう
・なのでSSE4やAVXなどで自力で書けば紛れがないので書く価値はある

Intelコンパイラは持ってないのでわからん。
あとGCCやLLVMでも試してみたいところだが、環境が無い罠
nice!(0)  コメント(0)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。