Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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は以下の理由から推奨されてきました。

  1. 命令サイズが小さい: XOR reg, regは通常2バイトの命令ですが、MOV reg, 0は即値のサイズによって3バイトになることがあります。命令サイズが小さいことは、命令キャッシュの効率向上に繋がります。
  2. 依存関係の解消: 古いCPUアーキテクチャでは、XOR reg, regは特別な命令として認識され、データ依存関係を解消する効果がありました。これにより、CPUはレジスタの以前の値を待つことなくXOR演算を実行でき、パイプライン処理の効率が向上しました。

しかし、現代のx86プロセッサ(Intel Core, AMD Zenなど)では、これらの命令のパフォーマンス差はほとんど無視できるレベルになっています。現代のCPUは高度な最適化機能を備えており、XOR reg, regを「ゼロ化イディオム」として認識し、非常に効率的に実行します。また、即値0を移動するMOV reg, 0も同様に高度に最適化されています。多くの場合、両方の命令は内部的に単一のマイクロオペレーション(uop)に分解されます。

このコミットが行われた2012年時点では、CPUアーキテクチャの進化により、MOV $0, AXXOR 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レジスタに移動させることを意味し、これもレジスタをゼロクリアする目的で使用されます。
  • 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パイプラインでわずかに有利に働くこともあります。

このコミットの背景には、当時の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に変更したものです。

関連リンク

参考にした情報源リンク

  • Web検索: "x86 assembly XOR AX AX vs MOV 0 AX performance" (Google Search)