[インデックス 12841] ファイルの概要
このコミットは、Goランタイムのx86アセンブリコードにおいて、レジスタをゼロクリアする命令をXORL AX, AX
からMOVL $0, AX
に変更するものです。具体的には、src/pkg/runtime/asm_386.s
ファイル内のruntime·cas64
関数のcas64_fail
ラベルの箇所でこの変更が行われています。これは、特定の条件下でのパフォーマンス最適化、またはコードの可読性向上を目的としている可能性があります。
コミット
commit fd04f05f2f5225f9a17a34a21535fd79cceb687d
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Apr 5 18:59:50 2012 +0400
runtime: replace XOR AX, AX with MOV $0, AX
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5985048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fd04f05f2f5225f9a17a34a21535fd79cceb687d
元コミット内容
このコミットは、Go言語のランタイムにおけるx86アセンブリコードの変更です。具体的には、AX
レジスタをゼロに設定する命令を、従来のXOR AX, AX
からMOV $0, AX
に置き換えることを目的としています。この変更は、src/pkg/runtime/asm_386.s
ファイル内のruntime·cas64
関数(64ビットの比較と交換操作)の失敗パス(cas64_fail
)で行われています。
変更の背景
x86アセンブリにおいて、レジスタをゼロクリアする一般的な方法は2つあります。XOR reg, reg
(レジスタ自身とのXOR演算)とMOV reg, 0
(レジスタに即値0を移動)です。歴史的に、XOR reg, reg
は以下の理由から推奨されてきました。
- 命令サイズが小さい:
XOR reg, reg
は通常2バイトの命令ですが、MOV reg, 0
は即値のサイズによって3バイトになることがあります。命令サイズが小さいことは、命令キャッシュの効率向上に繋がります。 - 依存関係の解消: 古いCPUアーキテクチャでは、
XOR reg, reg
は特別な命令として認識され、データ依存関係を解消する効果がありました。これにより、CPUはレジスタの以前の値を待つことなくXOR演算を実行でき、パイプライン処理の効率が向上しました。
しかし、現代のx86プロセッサ(Intel Core, AMD Zenなど)では、これらの命令のパフォーマンス差はほとんど無視できるレベルになっています。現代のCPUは高度な最適化機能を備えており、XOR reg, reg
を「ゼロ化イディオム」として認識し、非常に効率的に実行します。また、即値0を移動するMOV reg, 0
も同様に高度に最適化されています。多くの場合、両方の命令は内部的に単一のマイクロオペレーション(uop)に分解されます。
このコミットが行われた2012年時点では、CPUアーキテクチャの進化により、MOV $0, AX
がXOR AX, AX
と同等か、場合によってはわずかに高速になる可能性がありました。また、MOV $0, AX
の方が「AXレジスタを0にする」という意図がより明確に伝わり、コードの可読性が向上するという側面もあります。この変更は、特定のCPUアーティファクトや、より明確な意図表現を優先した結果であると考えられます。
前提知識の解説
- x86アセンブリ: IntelおよびAMDのCPUで使用される命令セットアーキテクチャ(ISA)に基づく低レベルプログラミング言語。レジスタ、メモリ、命令などを直接操作します。
- レジスタ (Registers): CPU内部にある高速な記憶領域。データやアドレスを一時的に保持するために使用されます。
- AX (Accumulator Register): x86アーキテクチャにおける汎用レジスタの一つ。多くの場合、算術演算やデータ転送の際に使用されます。32ビットモードではEAX、64ビットモードではRAXと呼ばれますが、ここでは16ビットまたは32ビットの文脈でAXが使われています。
- 命令:
- XOR (Exclusive OR): ビットごとの排他的論理和演算を行う命令。
XOR A, B
はAとBの各ビットに対してXOR演算を行い、結果をAに格納します。XOR reg, reg
のように同じレジスタに対してXORを行うと、そのレジスタの全てのビットが0になるため、レジスタをゼロクリアする目的でよく使われます。 - MOV (Move): データを転送する命令。
MOV destination, source
はsourceの値をdestinationにコピーします。MOV $0, AX
は即値の0をAXレジスタに移動させることを意味し、これもレジスタをゼロクリアする目的で使用されます。
- XOR (Exclusive OR): ビットごとの排他的論理和演算を行う命令。
- Goランタイム (Go Runtime): Goプログラムの実行を管理する低レベルのコンポーネント。ガベージコレクション、スケジューリング、システムコールなど、Go言語の機能を実現するためにアセンブリコードを含む様々な最適化されたコードを含んでいます。
- CAS (Compare-And-Swap): マルチスレッドプログラミングにおけるアトミック操作の一つ。メモリ上の特定のアドレスにある値が期待する値と一致する場合にのみ、新しい値に更新します。これはロックフリーなデータ構造を実装する際に不可欠な操作です。
runtime·cas64
は64ビット値に対するCAS操作を指します。
技術的詳細
この変更は、x86アセンブリにおけるレジスタのゼロクリア方法に関するものです。
-
XORL AX, AX
:- これは、
AX
レジスタの各ビットと、AX
レジスタ自身の対応するビットとの排他的論理和を取る命令です。 - 任意のビット
b
に対してb XOR b = 0
であるため、この操作はAX
レジスタの全てのビットを0に設定します。 - 歴史的に、この命令は
MOV $0, AX
よりも高速であるとされてきました。その主な理由は、XOR reg, reg
がCPUの内部で「ゼロ化イディオム」として特別に扱われ、レジスタの以前の値に依存しない(依存関係を解消する)ため、パイプラインのストールを防ぐことができたからです。また、命令のエンコーディングが短く、命令キャッシュの効率が良いという利点もありました。
- これは、
-
MOVL $0, AX
:- これは、即値の0を
AX
レジスタに移動させる命令です。 - この命令も
AX
レジスタを0に設定します。 - 現代のCPUでは、
MOV
命令も非常に高度に最適化されており、即値の0をレジスタに移動させる操作は、XOR reg, reg
と同様に効率的に実行されます。多くの最新のCPUでは、これらの命令は単一のマイクロオペレーションに分解され、実行速度にほとんど差がありません。場合によっては、MOV
命令の方がより予測可能で、特定のCPUパイプラインでわずかに有利に働くこともあります。
- これは、即値の0を
このコミットの背景には、当時のGoランタイムがターゲットとしていたCPUアーキテクチャにおけるパフォーマンス特性の変化、またはコードの意図をより明確にするための可読性向上の意図があったと考えられます。MOVL $0, AX
は、AX
を0にするという目的がXORL AX, AX
よりも直接的に表現されているため、アセンブリコードの可読性が向上します。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/asm_386.s
+++ b/src/pkg/runtime/asm_386.s
@@ -323,7 +323,7 @@ TEXT runtime·cas64(SB), 7, $0
cas64_fail:
MOVL AX, 0(SI)
MOVL DX, 4(SI)
- XORL AX, AX
+ MOVL $0, AX
RET
// bool casp(void **p, void *old, void *new)
コアとなるコードの解説
変更はsrc/pkg/runtime/asm_386.s
ファイル内のruntime·cas64
関数の内部で行われています。
TEXT runtime·cas64(SB), 7, $0
:runtime·cas64
という関数の定義を示しています。これはGoランタイムの内部関数で、64ビットの比較と交換(Compare-And-Swap)操作をアトミックに実行するためのものです。cas64_fail:
: これは、runtime·cas64
操作が失敗した場合にジャンプするラベルです。CAS操作は、メモリ上の値が期待する値と一致しない場合に失敗します。MOVL AX, 0(SI)
:SI
レジスタが指すアドレス(おそらくCAS操作の対象となるメモリ位置)から32ビット値をAX
レジスタにロードします。MOVL DX, 4(SI)
:SI
レジスタが指すアドレスから4バイトオフセットした位置から32ビット値をDX
レジスタにロードします。これは、64ビット値を2つの32ビットレジスタ(AXとDX)に分割して扱うx86の慣習によるものです。- XORL AX, AX
: 変更前のコードです。AX
レジスタをゼロクリアしています。+ MOVL $0, AX
: 変更後のコードです。AX
レジスタに即値の0を移動させることで、AX
レジスタをゼロクリアしています。RET
: 関数からリターンします。
このcas64_fail
パスでは、CAS操作が失敗した際に、何らかの理由でAX
レジスタをゼロに設定する必要があったと考えられます。これは、関数の戻り値として成功/失敗を示すフラグをAX
レジスタに設定しているか、あるいは後続の処理のためにAX
レジスタをクリーンな状態にする必要があるためです。このコミットは、そのゼロクリアの方法をXORL AX, AX
からMOVL $0, AX
に変更したものです。
関連リンク
- Go CL (Change List): https://golang.org/cl/5985048
参考にした情報源リンク
- Web検索: "x86 assembly XOR AX AX vs MOV 0 AX performance" (Google Search)