X86で整数レジスタをゼロ値に設定する2つのよく知られた方法があります。
どちらか
mov reg, 0
または
xor reg, reg
値0がコードに格納されておらず、生成されたマシンコードの数バイトを節約するため、2番目のバリアントの方が優れているという意見があります。これは間違いなく良いことです-使用される命令キャッシュが少なく、これによりコードの実行が速くなる場合があります。多くのコンパイラがそのようなコードを生成します。
ただし、xor命令と、同じレジスタを変更する以前の命令との間には、正式には命令間の依存関係があります。依存関係があるため、後者の命令は前者が完了するまで待機する必要があり、これによりプロセッサユニットの負荷が減少し、パフォーマンスが低下する可能性があります。
add reg, 17
;do something else with reg here
xor reg, reg
Xorの結果は、初期レジスタ値に関係なくまったく同じになることは明らかです。しかし、プロセッサはこれを認識できますか?
VC++ 7で次のテストを試しました。
const int Count = 10 * 1000 * 1000 * 1000;
int _tmain(int argc, _TCHAR* argv[])
{
int i;
DWORD start = GetTickCount();
for( i = 0; i < Count ; i++ ) {
__asm {
mov eax, 10
xor eax, eax
};
}
DWORD diff = GetTickCount() - start;
start = GetTickCount();
for( i = 0; i < Count ; i++ ) {
__asm {
mov eax, 10
mov eax, 0
};
}
diff = GetTickCount() - start;
return 0;
}
最適化をオフにすると、両方のループにまったく同じ時間がかかります。これは、プロセッサがxor reg, reg
命令が以前のmov eax, 0
命令に依存していないことを認識していることを合理的に証明していますか?これをチェックするためのより良いテストは何でしょうか?
あなたへの実際の答え:
Intel 64およびIA-32アーキテクチャ最適化リファレンスマニュアル
セクション3.5.1.8はあなたが見たいところです。
要するに、xorまたはmovが好まれる状況があります。問題は、依存関係チェーンと条件コードの保存に集中しています。
1966年のHRステーションワゴンを販売した後、自分の車を修理することができなくなりました。私は最近のCPUで同様の修正を行っています:-)
それは実際には基礎となるマイクロコードまたは回路に依存します。 CPUが"XOR Rn,Rn"
を認識し、内容を気にせずにすべてのビットをゼロにする可能性は十分にあります。しかしもちろん、それは"MOV Rn, 0"
でも同じことをするかもしれません。優れたコンパイラーはとにかくターゲットプラットフォームに最適なバリアントを選択するため、これは通常、アセンブラーでコーディングしている場合にのみ問題になります。
CPUが十分に賢い場合、XOR
依存関係は消えます知っている値は無関係であり、とにかくゼロに設定されます(これも実際に使用されているCPUによって異なります)。
ただし、コードで数バイトまたは数クロックサイクルを気にするのはずっと前のことです。これは、マイクロ最適化がうまくいかなかったようです。
x86には可変長命令があります。 MOV EAX、0は、コード空間にXOR EAX、EAXよりも1バイトまたは2バイト多く必要です。
最近のCPUでは、XORパターンが推奨されます。より小さく、より高速です。
多くの実際のワークロードでは、パフォーマンスを制限する主な要因の1つがiキャッシュのミスであるため、実際には小さい方が重要です。これは、2つのオプションを比較するマイクロベンチマークではキャプチャされませんが、現実の世界では、コードの実行がわずかに速くなります。
そして、減少したiキャッシュミスを無視すると、過去数年間のどのCPUでもXORはMOVと同じか、それよりも高速です。MOV命令を実行するよりも速いのは何ですか?最近のIntelプロセッサでは、ディスパッチ/名前変更ロジックがXORパターンを認識し、結果がゼロになることを認識し、レジスタを物理的なゼロレジスタに向けるだけです。次に、命令を実行する必要がないため、命令を破棄します。
最終的な結果として、XORパターンはゼロ実行リソースを使用し、最近のIntel CPUでは、サイクルごとに4つの命令を「実行」できます。MOVはサイクルごとに3つの命令で最高になります。
詳細については、私が書いたこのブログ投稿を参照してください。
https://randomascii.wordpress.com/2012/12/29/the-surprising-subtleties-of-zeroing-a-register/
ほとんどのプログラマーはこれについて心配する必要はありませんが、コンパイラーの作成者は心配する必要があります。生成されているコードを理解するのは良いことであり、それはとてつもなくクールです。
以前のアーキテクチャでは、mov eax, 0
命令がxor eax, eax
よりも少し長くかかっていたと思います...理由を正確に思い出せません。ただし、mov
sがもっとたくさんある場合を除いて、コードに1つのリテラルが格納されているためにキャッシュミスが発生する可能性は低いと思います。
また、メモリから、フラグのステータスはこれらのメソッド間で同一ではありませんが、私はこれを誤って覚えている可能性があることに注意してください。