メモリから単精度浮動小数点数を読み取り、それらに対して倍精度で演算を実行することになっているコードを最適化しようとしています。データをメモリに単精度で格納するコードは、メモリにデータを倍精度で格納する同等のコードよりも大幅に遅くなるため、これは重大なパフォーマンスのボトルネックになりつつあります。以下は、私の問題の本質を捉えたおもちゃの C++ プログラムです。
#include <cstdio>
// noinline to force main() to actually read the value from memory.
__attributes__ ((noinline)) float* GetFloat() {
float* f = new float;
*f = 3.14;
return f;
}
int main() {
float* f = GetFloat();
double d = *f;
printf("%f\n", d); // Use the value so it isn't optimized out of existence.
}
GCC と Clang はどちらも、命令がソース引数としてメモリをサポートしていても、倍精度のロード*f
と倍精度への変換を 2 つの別個の命令として実行します。Agner Fogcvtss2sd
によると、ほとんどのアーキテクチャと同じくらい高速に実行され、あとがきを実行する必要がありません。それにもかかわらず、Clang は に対して次のコードを生成します。cvtss2sd r, m
movss r, m
cvtss2sd r, r
main()
main PROC
push rbp ;
mov rbp, rsp ;
call _Z8GetFloatv ;
movss xmm0, dword ptr [rax] ;
cvtss2sd xmm0, xmm0 ;
mov edi, offset ?_001 ;
mov al, 1 ;
call printf ;
xor eax, eax ;
pop rbp ;
ret ;
main ENDP
GCC も同様に効率の悪いコードを生成します。これらのコンパイラのどちらも、単に次のようなものを生成しないのはなぜcvtss2sd xmm0, dword ptr [rax]
ですか?
編集: 素晴らしい答え、スティーブンキャノン!Clang のアセンブリ言語出力を実際のユース ケースに使用し、それをインライン ASM としてソース ファイルに貼り付けてベンチマークを行い、ここで説明した変更を加えて再度ベンチマークを行いました。cvtss2sd [memory]
それが実際に遅いとは信じられませんでした。