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

[インデックス 12863] ファイルの概要

このコミットは、Go言語のツールチェイン、特に6a (アセンブラ) と 6l (リンカ) にPREFETCH命令のサポートを追加するものです。これにより、Goプログラム内でこれらのCPU命令を直接利用できるようになり、データキャッシュの効率的な利用を通じてパフォーマンスの向上が期待されます。

コミット

commit 35d260fa4c1952079083b8868e9be5e4c77f70dc
Author: Russ Cox <rsc@golang.org>
Date:   Tue Apr 10 10:09:09 2012 -0400

    6a, 6l: add PREFETCH instructions
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5989073

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/35d260fa4c1952079083b8868e9be5e4c77f70dc

元コミット内容

6a, 6l: add PREFETCH instructions

R=ken2
CC=golang-dev
https://golang.org/cl/5989073

変更の背景

この変更の背景には、Go言語で記述された高性能なアプリケーションにおいて、CPUのデータキャッシュをより効率的に利用したいというニーズがあります。PREFETCH命令は、プログラマが明示的にメモリからデータをキャッシュに読み込むようCPUに指示することを可能にします。これにより、データが必要になる前にキャッシュにロードしておくことで、メモリレイテンシによるパフォーマンスのボトルネックを軽減できます。

Go言語はシステムプログラミングにも利用されるため、低レベルな最適化が可能なアセンブリ命令へのアクセスは重要です。特に、データ集約型の処理や、予測可能なアクセスパターンを持つデータ構造を扱う際に、PREFETCH命令は大きな効果を発揮する可能性があります。このコミットは、Goのツールチェインがこれらの命令を認識し、アセンブリコード内で使用できるようにするための基盤を整備するものです。

前提知識の解説

CPUキャッシュとメモリ階層

現代のCPUは、メインメモリ(RAM)よりもはるかに高速なSRAMベースのキャッシュメモリを内蔵しています。これは通常、L1、L2、L3といった階層構造になっており、L1が最も高速で容量が小さく、CPUコアに最も近い位置にあります。データがCPUによって要求されると、まずL1キャッシュ、次にL2、L3、そして最後にメインメモリという順で検索されます。メインメモリへのアクセスは非常に遅く(数百サイクルかかることもあります)、これがプログラムのパフォーマンスボトルネックとなることがよくあります。

プリフェッチ(Prefetching)

プリフェッチとは、CPUが将来必要になるであろうデータを予測し、事前にメインメモリからキャッシュに読み込んでおく技術です。これにより、実際にデータが必要になったときに、すでにキャッシュに存在するため、高速にアクセスできるようになります。プリフェッチにはハードウェアによる自動プリフェッチと、ソフトウェアによる明示的なプリフェッチがあります。

PREFETCH命令

PREFETCH命令は、ソフトウェアによる明示的なプリフェッチを実現するためのCPU命令です。プログラマが特定のメモリアドレスのデータをキャッシュに読み込むようCPUに指示します。Intel x86-64アーキテクチャには、いくつかのPREFETCH命令が存在し、それぞれ異なるキャッシュレベルやプリフェッチの挙動を指定します。

  • PREFETCHT0: データをすべてのキャッシュレベル(L1, L2, L3)に読み込みます。
  • PREFETCHT1: データをL2キャッシュとL3キャッシュに読み込みます。
  • PREFETCHT2: データをL2キャッシュに読み込みます。
  • PREFETCHNTA: データを非テンポラル(Non-Temporal)な方法でキャッシュに読み込みます。これは、データが一度しか使用されない可能性が高い場合に有用で、既存のキャッシュラインを汚染するのを避けるために、キャッシュの最も低いレベル(通常はL1)には読み込まず、L2/L3キャッシュに直接読み込むか、またはキャッシュをバイパスして直接メモリから読み込むようにヒントを与えます。

これらの命令は「ヒント」であり、CPUが必ずしもその指示に従うとは限りません。CPUの内部状態やリソースの利用状況によって、プリフェッチが実行されない場合や、異なる方法で実行される場合があります。しかし、適切に使用することで、データアクセスパターンが予測可能なループ処理などで大きなパフォーマンス改善をもたらすことがあります。

Go言語のツールチェイン (6a, 6l)

  • 6a (アセンブラ): Go言語のツールチェインにおけるアセンブラです。Goのソースコードから生成されたアセンブリコード(Goのアセンブリ構文で書かれたもの)を機械語に変換する役割を担います。このコミットでは、PREFETCH命令のニーモニック(例: PREFETCHT0)を認識し、対応するオペコードに変換できるようにlex.cが変更されています。
  • 6l (リンカ): Go言語のツールチェインにおけるリンカです。アセンブラによって生成されたオブジェクトファイルや、コンパイラによって生成されたオブジェクトファイルを結合し、実行可能なバイナリを生成します。このコミットでは、PREFETCH命令に対応するオペコードの定義が6.out.hに追加され、リンカがこれらの命令を正しく処理できるようにoptab.cが更新されています。

技術的詳細

このコミットは、Go言語のツールチェインがx86-64アーキテクチャのPREFETCH命令をサポートするための変更を導入しています。具体的には、以下のファイルが修正されています。

  1. src/cmd/6a/lex.c:

    • このファイルは、6aアセンブラの字句解析器(lexer)の一部です。
    • PREFETCHT0, PREFETCHT1, PREFETCHT2, PREFETCHNTAという新しいニーモニックが追加され、それぞれに対応する内部的なアセンブリ命令コード(APREFETCHT0など)にマッピングされます。これにより、アセンブラがこれらの命令を認識できるようになります。
  2. src/cmd/6l/6.out.h:

    • このヘッダファイルは、6lリンカが使用するアセンブリ命令の列挙型(enum)定義を含んでいます。
    • APREFETCHT0, APREFETCHT1, APREFETCHT2, APREFETCHNTAという新しい命令コードが列挙型asに追加されています。これは、リンカがこれらの命令を内部的に識別するために必要です。
  3. src/cmd/6l/optab.c:

    • このファイルは、6lリンカにおけるオペコードテーブルの定義を含んでいます。オペコードテーブルは、各アセンブリ命令がどのように機械語に変換されるか、そのオペランドの形式、命令のバイトコードなどを定義します。
    • 新しいuchar yprefetch[]配列が追加されています。これはPREFETCH命令のオペランドの形式を定義しており、メモリオペランド(Ym)のみを受け取り、レジスタオペランド(Ynone)は受け取らないことを示しています。また、命令のエンコーディング形式(Zm_o)とバイト長(2)も指定されています。
    • Optab optab[]配列に、APREFETCHT0, APREFETCHT1, APREFETCHT2, APREFETCHNTAに対応するエントリが追加されています。これらのエントリは、各PREFETCH命令のオペコード(0x18)と、命令のサブオペコード(01, 02, 03, 00)を定義しています。これらのサブオペコードは、PREFETCH命令のどのバリアント(T0, T1, T2, NTA)であるかを指定するために使用されます。
  4. src/pkg/runtime/asm_amd64.s:

    • このファイルは、Goランタイムの一部として提供されるAMD64アーキテクチャ向けのアセンブリコードを含んでいます。
    • runtime·prefetch関数の実装が変更されています。以前はPREFETCHNTA命令をバイトコード(BYTE $0x0f; BYTE $0x18; BYTE $0x00)で直接記述していましたが、このコミットにより、新しく追加されたPREFETCHNTA (AX)というニーモニックを使用するように変更されています。これは、ツールチェインがPREFETCH命令をネイティブにサポートするようになったため、より可読性の高いアセンブリ構文を使用できるようになったことを示しています。

これらの変更により、Goのアセンブリコード内でPREFETCHT0, PREFETCHT1, PREFETCHT2, PREFETCHNTAといった命令を直接記述できるようになり、Goプログラムから低レベルなキャッシュ最適化を行う道が開かれました。

コアとなるコードの変更箇所

src/cmd/6a/lex.c

--- a/src/cmd/6a/lex.c
+++ b/src/cmd/6a/lex.c
@@ -1001,6 +1001,10 @@ struct
 	"XORPS",	LTYPE3,	AXORPS,
 	"CRC32B",	LTYPE4, ACRC32B,
 	"CRC32Q",	LTYPE4, ACRC32Q,
+	"PREFETCHT0",		LTYPE2,	APREFETCHT0,
+	"PREFETCHT1",		LTYPE2,	APREFETCHT1,
+	"PREFETCHT2",		LTYPE2,	APREFETCHT2,
+	"PREFETCHNTA",		LTYPE2,	APREFETCHNTA,
 
 	0
 };

src/cmd/6l/6.out.h

--- a/src/cmd/6l/6.out.h
+++ b/src/cmd/6l/6.out.h
@@ -736,6 +736,11 @@ enum	as
 	ACRC32B,
 	ACRC32Q,
 	AIMUL3Q,
+	
+	APREFETCHT0,
+	APREFETCHT1,
+	APREFETCHT2,
+	APREFETCHNTA,
 
 	ALAST
 };

src/cmd/6l/optab.c

--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -539,6 +539,11 @@ uchar	ycrc32l[] =
 {
 	Yml,	Yrl,	Zlitm_r,	0,
 };
+uchar	yprefetch[] =
+{
+	Ym,	Ynone,	Zm_o,	2,
+	0,
+};
 
 /*
  * You are doasm, holding in your hand a Prog* with p->as set to, say, ACRC32,
@@ -1270,8 +1275,13 @@ Optab optab[] =
 	{ AXADDQ,	yrl_ml,	Pw, 0x0f,0xc1 },
 	{ AXADDW,	yrl_ml,	Pe, 0x0f,0xc1 },
 
-	{ ACRC32B,       ycrc32l,Px, 0xf2,0x0f,0x38,0xf0,0},
-	{ ACRC32Q,       ycrc32l,Pw, 0xf2,0x0f,0x38,0xf1,0},
+	{ ACRC32B,       ycrc32l,Px, 0xf2,0x0f,0x38,0xf0,0 },
+	{ ACRC32Q,       ycrc32l,Pw, 0xf2,0x0f,0x38,0xf1,0 },
+	
+	{ APREFETCHT0,	yprefetch,	Pm,	0x18,(01) },
+	{ APREFETCHT1,	yprefetch,	Pm,	0x18,(02) },
+	{ APREFETCHT2,	yprefetch,	Pm,	0x18,(03) },
+	{ APREFETCHNTA,	yprefetch,	Pm,	0x18,(00) },
 
 	{ AEND },
 	0

src/pkg/runtime/asm_amd64.s

--- a/src/pkg/runtime/asm_amd64.s
+++ b/src/pkg/runtime/asm_amd64.s
@@ -443,8 +443,7 @@ TEXT runtime·atomicstore64(SB), 7, $0
 
 TEXT runtime·prefetch(SB), 7, $0
 	MOVQ    8(SP), AX
-	// PREFETCHNTA (AX)
-	BYTE $0x0f; BYTE $0x18; BYTE $0x00
+	PREFETCHNTA	(AX)
 	RET
 
 // void jmpdefer(fn, sp);

コアとなるコードの解説

このコミットの核心は、GoのアセンブラとリンカがPREFETCH命令をネイティブにサポートするように拡張された点にあります。

  • src/cmd/6a/lex.c: ここでは、アセンブラがPREFETCHT0PREFETCHT1PREFETCHT2PREFETCHNTAといった人間が読めるニーモニックを、内部的な命令コード(APREFETCHT0など)に変換するためのマッピングが追加されています。これにより、Goのアセンブリファイル内でこれらの命令を直接記述できるようになります。
  • src/cmd/6l/6.out.h: 新しい命令コードが列挙型に追加されることで、リンカがこれらの命令を識別し、処理するための準備が整います。
  • src/cmd/6l/optab.c: このファイルは最も重要な変更を含んでいます。
    • yprefetchという新しいオペランド定義が追加され、PREFETCH命令がメモリオペランドのみを受け入れることを指定しています。
    • Optab optab[]テーブルに、各PREFETCH命令に対応するエントリが追加されています。これらのエントリは、命令の実際の機械語エンコーディング(オペコード0x18とサブオペコード)を定義しています。例えば、PREFETCHNTAはオペコード0x18とサブオペコード00の組み合わせでエンコードされます。これにより、リンカはこれらの命令を正しく機械語に変換できるようになります。
  • src/pkg/runtime/asm_amd64.s: runtime·prefetch関数は、以前はPREFETCHNTA命令を直接バイトコードで記述していましたが、この変更により、新しくサポートされたニーモニックPREFETCHNTA (AX)を使用するように簡略化されました。これは、ツールチェインの機能拡張が実際にランタイムコードの可読性向上に貢献していることを示しています。

これらの変更により、Go言語のユーザーは、必要に応じてアセンブリコード内でPREFETCH命令を直接利用し、データキャッシュの利用を最適化することで、特定のワークロードにおけるパフォーマンスを向上させることが可能になります。

関連リンク

  • Go言語の公式ドキュメント: Go言語のアセンブリに関する情報は、公式ドキュメントやGoのソースコードリポジトリ内のdoc/asm.mdなどで確認できます。
  • Intel 64 and IA-32 Architectures Software Developer's Manuals: PREFETCH命令の詳細な仕様は、Intelの公式ドキュメントで確認できます。特に、ボリューム2Aの「Instruction Set Reference, A-M」セクションに記載されています。

参考にした情報源リンク