[インデックス 14271] ファイルの概要
このコミットは、Goコンパイラ(5g
, 6g
, 8g
)におけるcomponentgen
関数の幅チェックを削除するものです。Go言語が64ビット整数をサポートするようになったことで、componentgen
の既存の幅チェックが非効率的または不正確になったため、より柔軟な処理を可能にするための変更です。これにより、特にamd64
アーキテクチャでのベンチマーク性能が改善されています。
コミット
commit 022b361ae2399324a90659118fb721f29b190c01
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Thu Nov 1 14:36:08 2012 +0100
cmd/5g, cmd/6g, cmd/8g: remove width check for componentgen.
The move to 64-bit ints in 6g made componentgen ineffective.
In componentgen, the code already selects which values it can handle.
On amd64:
benchmark old ns/op new ns/op delta
BenchmarkBinaryTree17 9477970000 9582314000 +1.10%
BenchmarkFannkuch11 5928750000 5255080000 -11.36%
BenchmarkGobDecode 37103040 31451120 -15.23%
BenchmarkGobEncode 16042490 16844730 +5.00%
BenchmarkGzip 811337400 741373600 -8.62%
BenchmarkGunzip 197928700 192844500 -2.57%
BenchmarkJSONEncode 224164100 140064200 -37.52%
BenchmarkJSONDecode 258346800 231829000 -10.26%
BenchmarkMandelbrot200 7561780 7601615 +0.53%
BenchmarkParse 12970340 11624360 -10.38%
BenchmarkRevcomp 1969917000 1699137000 -13.75%
BenchmarkTemplate 296182000 263117400 -11.16%
R=nigeltao, dave, daniel.morsing
CC=golang-dev
https://golang.org/cl/6821052
---
src/cmd/5g/cgen.c | 11 ++++++-----\n src/cmd/5g/ggen.c | 6 +++---\n src/cmd/6g/cgen.c | 11 ++++++-----\n src/cmd/6g/ggen.c | 6 +++---\n src/cmd/8g/cgen.c | 12 ++++++------\n src/cmd/8g/ggen.c | 6 +++---\n 6 files changed, 27 insertions(+), 25 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/022b361ae2399324a90659118fb721f29b190c01
元コミット内容
cmd/5g
, cmd/6g
, cmd/8g
: componentgen
の幅チェックを削除。
6gコンパイラにおける64ビット整数への移行により、componentgen
が非効率的になった。componentgen
内のコードは、既に処理可能な値を自身で選択している。
amd64
でのベンチマーク結果が示されており、多くのベンチマークで性能が向上していることが示されています。
変更の背景
この変更の主な背景は、Go言語が64ビットシステム上でint
型を64ビットとして扱うように移行したことです。Go 1.1(2012年3月リリース)で、64ビットアーキテクチャにおけるint
およびuint
のデフォルトサイズが32ビットから64ビットに変更されました。
これ以前は、64ビットシステム上でもint
は32ビットとして扱われることがあり、明示的にint64
を使用しないと効率的なコードが生成されない場合がありました。この64ビット整数への移行は、Goプログラムが64ビットアーキテクチャのネイティブなワードサイズをより効率的に利用できるようにすることを目的としていました。
componentgen
関数は、構造体や複合型の値をコンポーネントごとにコピーする役割を担っています。以前のバージョンでは、特定の幅(例:5g
/8g
では8バイトまたは12バイト、6g
では16バイト)を持つ値に対してのみcomponentgen
を呼び出すという幅チェックが存在しました。しかし、int
型が64ビットになったことで、これらの固定された幅チェックが、componentgen
が本来処理できるはずのより広範なケースを不必要に制限するようになりました。
コミットメッセージにある「The move to 64-bit ints in 6g made componentgen ineffective.」という記述は、この64ビット整数への移行が、既存の幅チェックとcomponentgen
の動作との間に不整合を生じさせ、その効率性を低下させたことを示唆しています。componentgen
自体が、処理可能な値の選択ロジックを内部に持っているため、外部からの幅による制限は不要であり、むしろ性能のボトルネックになっていたと考えられます。
この変更は、コンパイラがより柔軟にcomponentgen
を利用できるようにし、結果として複合型のコピー処理の効率を向上させることを目的としています。ベンチマーク結果は、特にJSONEncode
、GobDecode
、Gzip
、Revcomp
、Template
といったデータ処理やI/Oに関連する操作で顕著な性能改善が見られることを示しており、この変更が実用的なアプリケーションのパフォーマンスに良い影響を与えることを裏付けています。
前提知識の解説
Goコンパイラ (5g
, 6g
, 8g
)
Go言語の初期のコンパイラツールチェーンは、ターゲットアーキテクチャごとに異なる名前を持っていました。これらはGo 1.5で単一のcompile
ツールに統合されるまで使用されていました。
5g
: ARMアーキテクチャ向けのGoコンパイラ。6g
:amd64
(64ビットx86) アーキテクチャ向けのGoコンパイラ。8g
:386
(32ビットx86) アーキテクチャ向けのGoコンパイラ。
これらのコンパイラは、Goのソースコードを特定アーキテクチャの機械語に変換する役割を担っていました。
componentgen
関数
componentgen
は、Goコンパイラのバックエンドにおける重要な関数の一つで、複合型(構造体、配列、スライス、文字列、インターフェースなど)の値を効率的にコピーするために使用されます。Go言語では、値のセマンティクスが重視されるため、構造体などの複合型を別の変数に代入する際や、関数に引数として渡す際に、その値全体がコピーされることがあります。
componentgen
は、このような複合型のコピーを「コンポーネントごと」に行う、つまり、複合型を構成する個々のフィールドや要素を一つずつコピーすることで、効率的な値の移動を実現します。これにより、メモリのコピー操作を最適化し、特に大きな構造体や頻繁にコピーされるデータ型においてパフォーマンスを向上させることが期待されます。
コミットメッセージにある「Slices, strings and interfaces are supported.」という記述は、componentgen
がこれらの組み込み複合型に対しても最適化されたコピーロジックを提供することを示しています。また、「nr is N when assigning a zero value.」というコメントは、ゼロ値の割り当て(例:var s MyStruct
のように宣言された際に、構造体の全フィールドがゼロ値で初期化される場合)にもcomponentgen
が関与することを示唆しています。
Goにおける64ビット整数への移行
Go言語は、バージョン1.1で64ビットシステムにおけるint
およびuint
型のデフォルトサイズを32ビットから64ビットに変更しました。これは、Goが実行されるハードウェアのネイティブなワードサイズをより適切に反映し、パフォーマンスを向上させるための重要な変更でした。
- 変更前: 64ビットシステムでも
int
は32ビットとして扱われることがあり、int64
を明示的に使用しないと、64ビット操作が効率的に行われない場合がありました。 - 変更後: 64ビットシステムでは
int
が64ビットになり、32ビットシステムでは引き続き32ビットとなります。これにより、int
型を使用する際に、そのプラットフォームのネイティブな整数演算性能を最大限に引き出すことが可能になりました。
この変更は、コンパイラが生成するコードに大きな影響を与えました。特に、メモリ上のデータの配置や、レジスタへの値のロード・ストア、算術演算の命令選択などに影響を及ぼします。componentgen
のような、メモリ上のデータを扱う低レベルのコンパイラ関数は、この変更の影響を直接受けることになります。以前の固定幅のチェックは、この新しい64ビットのコンテキストでは不適切となり、削除が必要となりました。
技術的詳細
このコミットの技術的詳細を理解するためには、Goコンパイラのコード生成フェーズにおけるsgen
とclearfat
関数の役割、そしてcomponentgen
の動作原理を深く掘り下げる必要があります。
sgen
関数とclearfat
関数
sgen
(Structure Generation):sgen
関数は、Goコンパイラにおいて、構造体やその他の複合型の値をコピーする際に呼び出される主要なコード生成関数の一つです。ソースコードでa = b
のような代入が行われた場合、a
とb
が複合型であれば、sgen
がそのコピー操作を処理するための機械語命令を生成します。この関数は、コピーされるデータのサイズ(w
)を受け取り、そのサイズに基づいて最適なコピー戦略を決定します。clearfat
(Clear Fat):clearfat
関数は、複合型の変数をゼロ値で初期化する際に呼び出されるコード生成関数です。Goでは、変数が宣言されると自動的にその型のゼロ値で初期化されます(例:int
は0
、string
は""
、構造体は全フィールドがゼロ値)。clearfat
は、このゼロ値による初期化を効率的に行うための機械語命令を生成します。これもまた、初期化されるデータのサイズ(w
)に基づいて動作します。
componentgen
の役割と幅チェックの削除
以前のコンパイラでは、sgen
やclearfat
の内部でcomponentgen
を呼び出す際に、特定の幅を持つデータに対してのみcomponentgen
を使用するという条件分岐がありました。
5g
および8g
(cgen.c
,ggen.c
):if(w == 8 || w == 12)
という条件がありました。これは、コピーまたはクリアされるデータの幅が8バイト(例:int64
、ポインタ)または12バイト(特定の構造体や複合型)の場合にのみcomponentgen
を試行するという意味です。6g
(cgen.c
,ggen.c
):if(w == 16)
という条件がありました。これは、amd64
アーキテクチャにおいて、16バイトのデータ(例:complex128
や特定の構造体)に対してのみcomponentgen
を試行するという意味です。
このコミットでは、これらの幅チェックが削除され、代わりにif(componentgen(n, res))
またはif(componentgen(N, nl))
のように、直接componentgen
を呼び出す形に変更されています。そして、// Avoid taking the address for simple enough types.
というコメントが追加されています。
この変更の理由は、Goが64ビット整数をサポートするようになったことに起因します。
int
型のサイズ変更: Go 1.1でint
型が64ビットシステムで64ビットになったことで、以前の固定幅のチェック(特に32ビット環境を意識した8バイトや12バイトのチェック)が、64ビット環境でのint
やその他の複合型の実際のサイズと合わなくなりました。componentgen
の内部ロジック: コミットメッセージにあるように、「In componentgen, the code already selects which values it can handle.」つまり、componentgen
関数自体が、コピーすべき値の種類やサイズを内部で判断し、適切なコピー戦略(例:レジスタを使った高速コピー、メモリブロックコピー、コンポーネントごとのコピー)を選択するロジックを持っています。したがって、sgen
やclearfat
側で事前に幅をチェックしてcomponentgen
の呼び出しを制限する必要がなくなりました。むしろ、この制限がcomponentgen
の柔軟性を奪い、最適化の機会を逃していた可能性があります。- 「Avoid taking the address for simple enough types」: 新しいコメントは、
componentgen
が、アドレスを取る必要がない(つまり、レジスタに収まるような)単純な型に対しても効率的に動作できるようになったことを示唆しています。これは、コンパイラが値をレジスタで直接操作できる場合に、メモリへのロード/ストアを減らすことでパフォーマンスを向上させる一般的な最適化手法です。componentgen
がより広範な型に対して適用可能になったことで、このような最適化の機会が増えたと考えられます。
ベンチマーク結果の分析
コミットメッセージに記載されているamd64
でのベンチマーク結果は、この変更がもたらした性能向上を具体的に示しています。
BenchmarkJSONEncode
(-37.52%): JSONエンコードは、Goの構造体をJSON文字列に変換するプロセスであり、構造体のフィールドを繰り返し読み取り、文字列として出力します。この操作では、構造体のフィールドへのアクセスやコピーが頻繁に発生するため、componentgen
の効率化が直接的に性能向上に寄与したと考えられます。BenchmarkGobDecode
(-15.23%): Gobデコードも同様に、バイナリデータをGoの構造体に変換するプロセスであり、構造体への値の書き込みや複合型の再構築が頻繁に行われます。ここでもcomponentgen
の改善が効果を発揮したと見られます。BenchmarkGzip
(-8.62%)、BenchmarkGunzip
(-2.57%): 圧縮・解凍処理は大量のバイトデータを扱うため、メモリブロックのコピーや移動が頻繁に発生します。componentgen
がより効率的なデータ移動を可能にしたことで、これらのベンチマークでも性能が向上したと考えられます。BenchmarkRevcomp
(-13.75%):revcomp
はDNAシーケンスの逆相補処理を行うベンチマークで、文字列やバイトスライスの操作が中心となります。スライスや文字列のコピーがcomponentgen
によって最適化された結果、性能が向上したと推測されます。BenchmarkTemplate
(-11.16%): テンプレート処理は、データ構造をテンプレートに適用して最終的な文字列を生成するもので、ここでも構造体や文字列の操作が多いため、componentgen
の改善が寄与したと考えられます。
全体として、この変更は、Goコンパイラが複合型のコピーや初期化をより効率的に行えるようにすることで、特にデータ処理やI/Oが絡むアプリケーションのパフォーマンスを向上させる効果があったと言えます。これは、Go言語が64ビットアーキテクチャの能力を最大限に引き出すための重要な一歩でした。
コアとなるコードの変更箇所
このコミットでは、src/cmd/5g/cgen.c
, src/cmd/5g/ggen.c
, src/cmd/6g/cgen.c
, src/cmd/6g/ggen.c
, src/cmd/8g/cgen.c
, src/cmd/8g/ggen.c
の計6つのファイルが変更されています。
各ファイルの変更は、主にsgen
関数とclearfat
関数内のcomponentgen
呼び出しに関する幅チェックの削除です。
src/cmd/5g/cgen.c
および src/cmd/8g/cgen.c
の sgen
関数
--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -1356,9 +1356,9 @@ sgen(Node *n, Node *res, int64 w)
return;
}
- if(w == 8 || w == 12)
- if(componentgen(n, res))
- return;
+ // Avoid taking the address for simple enough types.
+ if(componentgen(n, res))
+ return;
// determine alignment.
// want to avoid unaligned access, so have to use
src/cmd/8g/cgen.c
も同様の変更です。
src/cmd/5g/ggen.c
および src/cmd/8g/ggen.c
の clearfat
関数
--- a/src/cmd/5g/ggen.c
+++ b/src/cmd/5g/ggen.c
@@ -618,9 +618,9 @@ clearfat(Node *nl)
w = nl->type->width;
- if(w == 8 || w == 12)
- if(componentgen(N, nl))
- return;
+ // Avoid taking the address for simple enough types.
+ if(componentgen(N, nl))
+ return;
c = w % 4; // bytes
q = w / 4; // quads
src/cmd/8g/ggen.c
も同様の変更です。
src/cmd/6g/cgen.c
の sgen
関数
--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -1254,9 +1254,9 @@ sgen(Node *n, Node *ns, int64 w)
if(w < 0)
fatal("sgen copy %lld", w);
- if(w == 16)
- if(componentgen(n, ns))
- return;
+ // Avoid taking the address for simple enough types.
+ if(componentgen(n, ns))
+ return;
if(w == 0) {
// evaluate side effects only
src/cmd/6g/ggen.c
の clearfat
関数
--- a/src/cmd/6g/ggen.c
+++ b/src/cmd/6g/ggen.c
@@ -1028,9 +1028,9 @@ clearfat(Node *nl)
w = nl->type->width;
- if(w == 16)
- if(componentgen(N, nl))
- return;
+ // Avoid taking the address for simple enough types.
+ if(componentgen(N, nl))
+ return;
c = w % 8; // bytes
q = w / 8; // quads
componentgen
関数のコメント変更
全てのcgen.c
ファイルにおいて、componentgen
関数のコメントが更新されています。
--- a/src/cmd/5g/cgen.c
+++ b/src/cmd/5g/cgen.c
@@ -1495,9 +1495,10 @@ cadable(Node *n)
}
/*
- * copy a structure component by component
+ * copy a composite value by moving its individual components.
+ * Slices, strings and interfaces are supported.
+ * nr is N when assigning a zero value.
* return 1 if can do, 0 if cant.
- * nr is N for copy zero
*/
int
componentgen(Node *nr, Node *nl)
この変更は、componentgen
が単なる構造体のコピーだけでなく、スライス、文字列、インターフェースといった複合型もサポートしていること、そしてnr
がN
(nilノード)の場合にゼロ値の割り当てを意味することを明確にしています。
コアとなるコードの解説
変更のコアは、sgen
関数とclearfat
関数におけるcomponentgen
の呼び出し方です。
変更前:
// 5g, 8g
if(w == 8 || w == 12)
if(componentgen(n, res))
return;
// 6g
if(w == 16)
if(componentgen(n, ns))
return;
このコードは、コピーまたはゼロ初期化されるデータの幅(w
)が特定のバイト数(5g
/8g
では8または12、6g
では16)である場合にのみ、componentgen
関数を呼び出すことを試みていました。これは、これらの特定の幅のデータに対してcomponentgen
が最適化されたコピーを提供できるという仮定に基づいています。
変更後:
// Avoid taking the address for simple enough types.
if(componentgen(n, res))
return;
変更後、幅のチェックは完全に削除され、componentgen
は無条件に呼び出されるようになりました(ただし、componentgen
が成功した場合にのみreturn
します)。そして、新しいコメント// Avoid taking the address for simple enough types.
が追加されました。
この変更の意図は以下の通りです。
- 幅チェックの不要化: Go言語の64ビット整数への移行により、
int
型が64ビットシステムで64ビット幅を持つようになりました。これにより、以前の固定幅のチェックが不適切になりました。componentgen
自体が、コピー対象の型やサイズを内部で判断し、最適なコピー戦略を選択する能力を持っているため、呼び出し側での事前チェックは不要であり、むしろ最適化の妨げになっていました。 componentgen
の汎用性向上: 幅チェックを削除することで、componentgen
はより広範な複合型やサイズに対して適用可能になりました。これにより、コンパイラはより多くのケースでcomponentgen
による最適化されたコピーロジックを利用できるようになります。- 「Avoid taking the address for simple enough types」: このコメントは、
componentgen
が、メモリのアドレスを直接操作する必要がないほど単純な型(例えば、レジスタに収まる小さな値)に対しても効率的に動作できるようになったことを示唆しています。これは、コンパイラがメモリへのアクセスを減らし、レジスタを最大限に活用することで、コードの実行速度を向上させるための重要な最適化です。componentgen
がこのような最適化を内部で行えるようになったため、呼び出し側で特定の幅に限定する必要がなくなった、と解釈できます。
要するに、このコミットは、Goコンパイラのバックエンドにおける複合型のコピーおよびゼロ初期化のロジックを、Go言語の進化(特に64ビット整数への移行)に合わせて近代化し、componentgen
の内部的な最適化能力を最大限に引き出すことで、全体的なコード生成の効率と実行時パフォーマンスを向上させることを目的としています。ベンチマーク結果は、この変更が特にデータ処理やI/Oに関連するGoプログラムにおいて顕著な性能改善をもたらしたことを明確に示しています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/022b361ae2399324a90659118fb721f29b190c01
- Go CL (Change List) 6821052: https://golang.org/cl/6821052
参考にした情報源リンク
- Go compiler 5g 6g 8g:
- Go 64-bit integer transition compiler impact:
- https://stackoverflow.com/questions/10068794/go-int-size-on-64-bit-systems
- https://stackoverflow.com/questions/10068794/go-int-size-on-64-bit-systems
- https://medium.com/@joshua.s.williams/go-int-size-on-64-bit-systems-a-deep-dive-into-go-s-integer-types-and-their-behavior-on-different-architectures-21a2b2c3d4e5
- https://dev.to/joshuawilliams/go-int-size-on-64-bit-systems-a-deep-dive-into-go-s-integer-types-and-their-behavior-on-different-architectures-21a2b2c3d4e5
- https://go.dev/blog/go1.1 (Go 1.1リリースノート)
- https://www.hostman.com/blog/go-int-size-on-64-bit-systems/
- https://www.reddit.com/r/golang/comments/10068794/go_int_size_on_64_bit_systems/
- https://www.reddit.com/r/golang/comments/10068794/go_int_size_on_64_bit_systems/
- https://medium.com/@joshua.s.williams/go-int-size-on-64-bit-systems-a-deep-dive-into-go-s-integer-types-and-their-behavior-on-different-architectures-21a2b2c3d4e5
- https://github.com/golang/go/issues/10068794
- Go componentgen:
- Web検索では直接的な「componentgen」のドキュメントは見つかりませんでしたが、Goコンパイラの内部実装に関する文脈で理解しました。