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

[インデックス 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を利用できるようにし、結果として複合型のコピー処理の効率を向上させることを目的としています。ベンチマーク結果は、特にJSONEncodeGobDecodeGzipRevcompTemplateといったデータ処理や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コンパイラのコード生成フェーズにおけるsgenclearfat関数の役割、そしてcomponentgenの動作原理を深く掘り下げる必要があります。

sgen関数とclearfat関数

  • sgen (Structure Generation): sgen関数は、Goコンパイラにおいて、構造体やその他の複合型の値をコピーする際に呼び出される主要なコード生成関数の一つです。ソースコードでa = bのような代入が行われた場合、abが複合型であれば、sgenがそのコピー操作を処理するための機械語命令を生成します。この関数は、コピーされるデータのサイズ(w)を受け取り、そのサイズに基づいて最適なコピー戦略を決定します。
  • clearfat (Clear Fat): clearfat関数は、複合型の変数をゼロ値で初期化する際に呼び出されるコード生成関数です。Goでは、変数が宣言されると自動的にその型のゼロ値で初期化されます(例:int0string""、構造体は全フィールドがゼロ値)。clearfatは、このゼロ値による初期化を効率的に行うための機械語命令を生成します。これもまた、初期化されるデータのサイズ(w)に基づいて動作します。

componentgenの役割と幅チェックの削除

以前のコンパイラでは、sgenclearfatの内部で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ビット整数をサポートするようになったことに起因します。

  1. int型のサイズ変更: Go 1.1でint型が64ビットシステムで64ビットになったことで、以前の固定幅のチェック(特に32ビット環境を意識した8バイトや12バイトのチェック)が、64ビット環境でのintやその他の複合型の実際のサイズと合わなくなりました。
  2. componentgenの内部ロジック: コミットメッセージにあるように、「In componentgen, the code already selects which values it can handle.」つまり、componentgen関数自体が、コピーすべき値の種類やサイズを内部で判断し、適切なコピー戦略(例:レジスタを使った高速コピー、メモリブロックコピー、コンポーネントごとのコピー)を選択するロジックを持っています。したがって、sgenclearfat側で事前に幅をチェックしてcomponentgenの呼び出しを制限する必要がなくなりました。むしろ、この制限がcomponentgenの柔軟性を奪い、最適化の機会を逃していた可能性があります。
  3. 「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.csgen 関数

--- 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.cclearfat 関数

--- 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.csgen 関数

--- 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.cclearfat 関数

--- 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が単なる構造体のコピーだけでなく、スライス、文字列、インターフェースといった複合型もサポートしていること、そしてnrN(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.が追加されました。

この変更の意図は以下の通りです。

  1. 幅チェックの不要化: Go言語の64ビット整数への移行により、int型が64ビットシステムで64ビット幅を持つようになりました。これにより、以前の固定幅のチェックが不適切になりました。componentgen自体が、コピー対象の型やサイズを内部で判断し、最適なコピー戦略を選択する能力を持っているため、呼び出し側での事前チェックは不要であり、むしろ最適化の妨げになっていました。
  2. componentgenの汎用性向上: 幅チェックを削除することで、componentgenはより広範な複合型やサイズに対して適用可能になりました。これにより、コンパイラはより多くのケースでcomponentgenによる最適化されたコピーロジックを利用できるようになります。
  3. 「Avoid taking the address for simple enough types」: このコメントは、componentgenが、メモリのアドレスを直接操作する必要がないほど単純な型(例えば、レジスタに収まる小さな値)に対しても効率的に動作できるようになったことを示唆しています。これは、コンパイラがメモリへのアクセスを減らし、レジスタを最大限に活用することで、コードの実行速度を向上させるための重要な最適化です。componentgenがこのような最適化を内部で行えるようになったため、呼び出し側で特定の幅に限定する必要がなくなった、と解釈できます。

要するに、このコミットは、Goコンパイラのバックエンドにおける複合型のコピーおよびゼロ初期化のロジックを、Go言語の進化(特に64ビット整数への移行)に合わせて近代化し、componentgenの内部的な最適化能力を最大限に引き出すことで、全体的なコード生成の効率と実行時パフォーマンスを向上させることを目的としています。ベンチマーク結果は、この変更が特にデータ処理やI/Oに関連するGoプログラムにおいて顕著な性能改善をもたらしたことを明確に示しています。

関連リンク

参考にした情報源リンク