Yoh2の日記: yoh2 += 1; 2
T/O
Endowsさんのトモダチの日記。 アナウンス:スラドは 2024 年 1 月 31 日で終了します。データ保存はお早めに。
T/O
です。
SSIA という略語を最近覚えた。
T/O
近頃気に言っている Rust には ++ がないのでこの記法にしてみた。
mut で宣言されていたか怪しいので
let yoh2 = yoh2 + 1;
の方がよかったかな?
あー、うん。
去年忘れてた。すっかり寄付かなくなってしまった (- -;
スラドにログインしてみた。
SE職についてはや10年以上。
しかしスラド的な知識からはどんどん遠ざかっているような気が…(^^;)
いまじゃおっさんコスプレイヤーって属性のほうがひどくなってるw
誰か助けてwww
最近前置派に宗旨替えしようとしてる。
x86系って、SIMDを明示的に使わない限り、アライメント境界が揃っていないデータ転送プログラムを書いても、パフォーマンスさえ気にしなければ特に問題ないという認識でいた。
が、実はそんな保証がないのではないかという現象に遭遇した。
それはx86_64でuint64_tの配列をコピーするコード。gcc -O3でコンパイルし、コピー元を奇数アドレスにしたらプログラムが落ちた。
試したgccは以下の3種類。いずれも現象発生。
以下、再現コード。上記コンパイラで-O2までは問題ないが-O3で落ちる。
// foo.c -- コピー関数。最適化で呼び出しが消されないようにファイルを分けた。
#include <stddef.h>
#include <stdint.h>
void copy_uint64(uint64_t *restrict dst, const uint64_t *restrict src, size_t n)
{
for(size_t i = 0; i < n; i++)
{
dst[i] = src[i];
}
}
// bar.c -- srcに奇数アドレスを設定してコピー関数呼び出し
#include <stdlib.h>
#include <stdint.h>
void copy_uint64(uint64_t *restrict dst, const uint64_t *restrict src, size_t n);
int main(void)
{
uint64_t *dst = (uint64_t *)malloc(sizeof(uint64_t) * 64);
// アライメントされていないアドレスにする。
uint64_t *src = (uint64_t *)((char *)malloc(sizeof(uint64_t) * 64 + 1) + 1);
copy_uint64(dst, src, 64);
return 0;
}
foo.cをgcc -O3 -Sしてみると、コピーしている部分と思われる箇所のアセンブリコードはこうなっていた。
movdqa (%r11,%rcx), %xmm0
addq $1, %r8
movdqu %xmm0, (%r10,%rcx)
addq $16, %rcx
srcから読み込んでいる部分がSSE命令の movdqa... 、dstに書き込んでいる部分が movdqu。
ここで曲者なのが movdqa。これは指定するアドレスが16バイト境界に揃っていなければならない命令。最適化の結果、これが使われてしまったために落ちていると思われる。
ちなみに、同じ効果を持ち、境界に揃っていなくてもよい movdqu という命令もある。このソースではdstへの書き込みで movdqu が使われている。
そのため、srcを境界整列させ、dstを奇数アドレスにした場合は問題なく実行が完了した。また、-Sで出力させたソースのmovdqaをmovdquに変更したものを使うと、srcが奇数アドレスでも問題なく実行が完了した。
最適化でSSE命令を使ってくれるのは歓迎なんだけど、アライメントなんて無視して横着したい身としてはこの最適化は厳しい。
んで結局これは (コンパイラの) バグなの? それとも (プログラムの) バグなの?
アレゲは一日にしてならず -- アレゲ見習い