[インデックス 12864] ファイルの概要
このコミットは、Go言語のツールチェインにおいて、x86 (386) アーキテクチャ向けのアセンブラ (8a
) とリンカ (8l
) にPREFETCH
命令のサポートを追加し、さらにランタイムのasm_386.s
ファイルでPREFETCHNTA
命令の使用方法を更新するものです。これにより、Goプログラムがデータプリフェッチを活用し、メモリレイテンシを削減してパフォーマンスを向上させる可能性が生まれます。
コミット
commit 3d462449f670e2c0a810a6ef3bfc732a117f3cf7
Author: Russ Cox <rsc@golang.org>
Date: Tue Apr 10 10:09:27 2012 -0400
8a, 8l: add PREFETCH instructions
R=ken2
CC=golang-dev
https://golang.org/cl/5992082
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3d462449f670e2c0a810a6ef3bfc732a117f3cf7
元コミット内容
8a, 8l: add PREFETCH instructions
R=ken2
CC=golang-dev
https://golang.org/cl/5992082
変更の背景
この変更の背景には、プログラムのパフォーマンス最適化、特にメモリレイテンシの削減という目的があります。現代のCPUは非常に高速ですが、メインメモリからのデータ取得はCPUの処理速度に比べてはるかに遅いという「メモリウォール」問題に直面しています。このギャップを埋めるために、CPUはキャッシュメモリを利用しますが、キャッシュミスが発生すると、CPUはメインメモリからのデータロードを待つ必要があり、これがパフォーマンスのボトルネックとなります。
PREFETCH
命令は、プログラマが将来必要となるであろうデータを事前にキャッシュにロードするようCPUに「ヒント」を与えるための命令です。これにより、実際にデータが必要になったときにすでにキャッシュに存在している可能性が高まり、メモリレイテンシによるストール(処理の停止)を減らすことができます。
Go言語のツールチェインにこれらの命令のサポートを追加することで、Goプログラムが低レベルな最適化、特にデータアクセスパターンが予測可能な場合に、より高いパフォーマンスを達成できるようになります。これは、特に数値計算、データ処理、ゲーム開発など、メモリへのアクセスが頻繁に行われるアプリケーションにおいて重要です。
前提知識の解説
1. CPUキャッシュとメモリ階層
CPUは、処理速度の速い順にレジスタ、L1キャッシュ、L2キャッシュ、L3キャッシュ、そしてメインメモリ(RAM)という階層的なメモリシステムを持っています。CPUがデータにアクセスする際、まず最も高速なL1キャッシュから探し、見つからなければL2、L3と順に探し、最終的にメインメモリにアクセスします。メインメモリへのアクセスは、L1キャッシュへのアクセスに比べて数百倍もの時間がかかることがあります。
2. キャッシュミスとパフォーマンス
CPUが要求するデータがキャッシュに存在しない場合(キャッシュミス)、CPUは下位のメモリ階層からデータをロードする必要があります。このロードには時間がかかり、その間CPUはアイドル状態になるか、他のタスクに切り替えることになります。これがプログラムの実行速度を低下させる主要な原因の一つです。
3. プリフェッチ(Prefetching)
プリフェッチとは、CPUが実際にデータが必要になる前に、そのデータをキャッシュにロードしておく技術です。これはハードウェアによって自動的に行われる場合もありますが(ハードウェアプリフェッチ)、プログラマが明示的に指示することも可能です(ソフトウェアプリフェッチ)。PREFETCH
命令はソフトウェアプリフェッチの一種です。
4. PREFETCH
命令の種類 (x86/x64アーキテクチャ)
Intel/AMDのx86/x64アーキテクチャには、いくつかのPREFETCH
命令が存在し、それぞれ異なるキャッシュレベルやプリフェッチの挙動を指示します。
PREFETCHT0
: データをすべてのキャッシュレベル(L1, L2, L3)にロードするようヒントを与えます。最も積極的なプリフェッチです。PREFETCHT1
: データをL2キャッシュとL3キャッシュにロードするようヒントを与えます。L1キャッシュにはロードしない可能性があります。PREFETCHT2
: データをL2キャッシュにロードするようヒントを与えます。L3キャッシュにはロードしない可能性があります。PREFETCHNTA
: データを非テンポラル(Non-Temporal)な方法でキャッシュにロードするようヒントを与えます。これは、データが一度しか使用されない可能性が高い場合に有用です。キャッシュラインを汚染せず、既存のキャッシュデータを追い出す可能性を低減します。ストリーミングデータなど、一度読み込んだらすぐに不要になるデータに適しています。
これらの命令はあくまで「ヒント」であり、CPUが必ずしもその通りに実行するとは限りません。CPUの内部状態や実装によって、プリフェッチが無視されたり、異なる挙動をしたりすることがあります。しかし、適切に使用すれば、パフォーマンス向上に大きく貢献します。
5. Go言語のツールチェイン (8a
, 8l
)
8a
(Go Assembler for 386): Go言語のソースコード(特に.s
ファイルで書かれたアセンブリコード)を、386 (x86) アーキテクチャ向けのオブジェクトコードに変換するアセンブラです。Goのランタイムや一部の標準ライブラリは、パフォーマンスが重要な部分でアセンブリコードを使用しています。8l
(Go Linker for 386):8a
によって生成されたオブジェクトファイルや、Goコンパイラによって生成されたオブジェクトファイルをリンクし、実行可能なバイナリを生成するリンカです。
これらのツールは、Go言語のクロスコンパイル能力を支える重要なコンポーネントであり、特定のアーキテクチャ向けの低レベルな最適化を可能にします。
技術的詳細
このコミットは、Go言語の386アーキテクチャ向けのアセンブラとリンカにPREFETCH
命令のサポートを追加することで、GoプログラムがこれらのCPU命令を直接利用できるようにします。
具体的には、以下のファイルが変更されています。
-
src/cmd/8a/lex.c
:- このファイルは、
8a
アセンブラの字句解析器(lexer)の一部です。 PREFETCHT0
,PREFETCHT1
,PREFETCHT2
,PREFETCHNTA
という新しいニーモニック(命令のシンボル名)が追加されています。- これにより、Goのアセンブリコード内でこれらの
PREFETCH
命令を記述できるようになります。例えば、PREFETCHT0 (AX)
のように記述できるようになります。
- このファイルは、
-
src/cmd/8l/8.out.h
:- このヘッダファイルは、
8l
リンカが使用する命令コード(opcode)の定義を含んでいます。 APREFETCHT0
,APREFETCHT1
,APREFETCHT2
,APREFETCHNTA
という新しい定数(enum値)が追加されています。これらは、リンカが内部的にPREFETCH
命令を識別するために使用する数値コードです。
- このヘッダファイルは、
-
src/cmd/8l/optab.c
:- このファイルは、
8l
リンカのオペレーションテーブル(optab)を定義しています。オペレーションテーブルは、各命令のニーモニックと、それに対応する機械語コードの生成ルールをマッピングするものです。 yprefetch
という新しいオペランドタイプ(Ym
,Ynone
,Zm_o
,2
)が定義されています。これは、PREFETCH
命令がメモリオペランド(Ym
)またはオペランドなし(Ynone
)を取り、特定のバイト数(2
)の機械語を生成することを示唆しています。optab
配列に、APREFETCHT0
,APREFETCHT1
,APREFETCHT2
,APREFETCHNTA
に対応するエントリが追加されています。- これらのエントリは、各
PREFETCH
命令がyprefetch
オペランドタイプを使用し、機械語コードとして0x18
というオペコード(これはPREFETCH
命令の共通オペコードの一部)と、それに続く特定のバイト(01
,02
,03
,00
)を生成することを示しています。これらのバイトは、PREFETCH
命令の具体的な種類(T0, T1, T2, NTA)を指定するものです。
- これらのエントリは、各
- このファイルは、
-
src/pkg/runtime/asm_386.s
:- このファイルは、Goランタイムの386アーキテクチャ向けアセンブリコードを含んでいます。
runtime·prefetch
関数内で、コメントアウトされていた古いバイトコードによるPREFETCHNTA
命令の記述(BYTE $0x0f; BYTE $0x18; BYTE $0x00
)が削除され、新しく追加されたニーモニックPREFETCHNTA (AX)
に置き換えられています。- これは、アセンブラとリンカが
PREFETCH
命令をネイティブにサポートするようになったため、手動でバイトコードを記述する必要がなくなり、より可読性の高いアセンブリニーモニックを使用できるようになったことを示しています。
これらの変更により、Go言語のコンパイラやランタイム、あるいはユーザーが記述するアセンブリコードにおいて、PREFETCH
命令を直接利用できるようになり、特定のパフォーマンスクリティカルなセクションでメモリレイテンシを積極的に管理することが可能になります。
コアとなるコードの変更箇所
src/cmd/8a/lex.c
(アセンブラの字句解析器)
--- a/src/cmd/8a/lex.c
+++ b/src/cmd/8a/lex.c
@@ -667,6 +667,10 @@ struct
"MFENCE", LTYPE0, AMFENCE,
"SFENCE", LTYPE0, ASFENCE,
"EMMS", LTYPE0, AEMMS,
+ "PREFETCHT0", LTYPE2, APREFETCHT0,
+ "PREFETCHT1", LTYPE2, APREFETCHT1,
+ "PREFETCHT2", LTYPE2, APREFETCHT2,
+ "PREFETCHNTA", LTYPE2, APREFETCHNTA,
0
};
src/cmd/8l/8.out.h
(リンカの命令定義)
--- a/src/cmd/8l/8.out.h
+++ b/src/cmd/8l/8.out.h
@@ -451,6 +451,11 @@ enum as
ASFENCE,
AEMMS,
+
+ APREFETCHT0,
+ APREFETCHT1,
+ APREFETCHT2,
+ APREFETCHNTA,
ALAST
};
src/cmd/8l/optab.c
(リンカのオペレーションテーブル)
--- a/src/cmd/8l/optab.c
+++ b/src/cmd/8l/optab.c
@@ -349,6 +349,11 @@ uchar ysvrs[] =
Ym, Ynone, Zm_o, 2,
0
};
+uchar yprefetch[] =
+{
+ Ym, Ynone, Zm_o, 2,
+ 0,
+};
Optab optab[] =
/* as, ytab, andproto, opcode */
@@ -761,5 +766,10 @@ Optab optab[] =
{ AEMMS, ynone, Pm, 0x77 },
+ { APREFETCHT0, yprefetch, Pm, 0x18,(01) },
+ { APREFETCHT1, yprefetch, Pm, 0x18,(02) },
+ { APREFETCHT2, yprefetch, Pm, 0x18,(03) },
+ { APREFETCHNTA, yprefetch, Pm, 0x18,(00) },
+
0
};
src/pkg/runtime/asm_386.s
(Goランタイムのアセンブリコード)
--- a/src/pkg/runtime/asm_386.s
+++ b/src/pkg/runtime/asm_386.s
@@ -417,8 +417,7 @@ TEXT runtime·atomicstore64(SB), 7, $0
TEXT runtime·prefetch(SB), 7, $0
MOVL 4(SP), AX
- // PREFETCHNTA (AX)
- BYTE $0x0f; BYTE $0x18; BYTE $0x00
+ PREFETCHNTA (AX)
RET
// void jmpdefer(fn, sp);
コアとなるコードの解説
src/cmd/8a/lex.c
この変更は、GoのアセンブラがPREFETCHT0
, PREFETCHT1
, PREFETCHT2
, PREFETCHNTA
という新しい命令ニーモニックを認識できるようにするためのものです。LTYPE2
は、これらの命令が特定のオペランドタイプを持つことを示しています。これにより、Goのアセンブリソースファイル(.s
ファイル)内でこれらの命令を直接記述できるようになります。
src/cmd/8l/8.out.h
このヘッダファイルは、リンカが内部的に使用する命令の列挙型(enum)を定義しています。APREFETCHT0
などの新しいエントリが追加されたことで、リンカはこれらのPREFETCH
命令を個別の命令として識別し、処理できるようになります。
src/cmd/8l/optab.c
このファイルは、Goのリンカがアセンブリ命令を実際の機械語コードに変換するための「レシピ」を提供します。
yprefetch
配列は、PREFETCH
命令が取りうるオペランドのパターンを定義しています。Ym
はメモリオペランド、Ynone
はオペランドなし、Zm_o
はメモリオペランドのオフセット、2
は命令のバイト長に関連する情報を示唆しています。optab
配列に追加されたエントリは、各PREFETCH
命令(APREFETCHT0
など)がどのように機械語に変換されるかを具体的に示しています。0x18
は、PREFETCH
命令の共通のオペコードの一部です。- 括弧内の
01
,02
,03
,00
は、PREFETCH
命令の具体的な種類(T0, T1, T2, NTA)を指定するための追加のバイト(ModR/Mバイトの一部)です。例えば、PREFETCHNTA
は00
を使用し、PREFETCHT0
は01
を使用します。
これらの定義により、リンカはアセンブリコード中のPREFETCH
命令を正しく解析し、対応するx86機械語命令を生成できるようになります。
src/pkg/runtime/asm_386.s
この変更は、Goランタイム内のruntime·prefetch
関数におけるPREFETCHNTA
命令の記述方法を更新しています。
- 変更前は、
PREFETCHNTA
命令を直接バイトコード(BYTE $0x0f; BYTE $0x18; BYTE $0x00
)で記述していました。これは、アセンブラがこの命令をネイティブにサポートしていなかったため、手動で機械語を埋め込む必要があったことを意味します。 - 変更後は、新しく追加されたニーモニック
PREFETCHNTA (AX)
を使用しています。これは、8a
アセンブラと8l
リンカがPREFETCH
命令を完全にサポートするようになったため、より高レベルで可読性の高いアセンブリ構文を使用できるようになったことを示しています。AX
レジスタは、プリフェッチするデータのメモリアドレスを保持していると推測されます。
この変更は、GoランタイムがPREFETCH
命令をよりクリーンかつ標準的な方法で利用できるようになったことを示しており、将来的なメンテナンス性や可読性の向上に貢献します。
関連リンク
- Intel 64 and IA-32 Architectures Software Developer's Manuals:
PREFETCH
命令の詳細な仕様が記載されています。 - Go Assembly Language: Go言語のアセンブリ言語に関する公式ドキュメント。