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

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

本コミットは、Go言語のランタイムにおいて、ゼロサイズ型(zero-sized types)のための新しい型アルゴリズムを追加するものです。これにより、ゼロサイズ型の値の比較やコピーといった操作が最適化され、特にチャネル操作におけるパフォーマンスが大幅に向上しました。具体的には、BenchmarkChanSemのベンチマーク結果が127nsから78.6nsへと改善されており、これは約38%の高速化に相当します。

コミット

  • コミットハッシュ: 1ff1405cc72ad79da50de0339569a61b0132672a
  • 作者: Dmitriy Vyukov (dvyukov@google.com)
  • コミット日時: 2012年1月20日 金曜日 10:32:55 +0400

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

https://github.com/golang/go/commit/1ff1405cc72ad79da50de0339569a61b0132672a

元コミット内容

    runtime: add type algorithms for zero-sized types
    BenchmarkChanSem old=127ns new=78.6ns
    
    R=golang-dev, bradfitz, sameer, rsc
    CC=golang-dev
    https://golang.org/cl/5558049

変更の背景

Go言語において、struct{}のようなゼロサイズ型は、メモリを一切消費しない特殊な型です。これらは、セマフォの実装や、マップのキーとして値の存在のみを示す場合など、様々な場面で利用されます。しかし、Goのランタイムは、これらのゼロサイズ型に対しても、他の通常の型と同様に、値の比較(memequal)やコピー(memcopy)といった操作を汎用的なアルゴリズムで行っていました。

ゼロサイズ型はメモリを持たないため、これらの操作は実際には何もする必要がありません。しかし、汎用的なアルゴリズムを使用すると、不要なメモリアクセスや比較ロジックが実行され、オーバーヘッドが発生していました。特に、チャネルをセマフォとして利用する際(ゼロサイズ型をチャネルに送受信する場合)には、このオーバーヘッドが顕著になり、パフォーマンスのボトルネックとなっていました。

本コミットの目的は、ゼロサイズ型に特化した最適化されたアルゴリズムを導入することで、これらの不要な処理を排除し、ランタイムの効率を向上させることにありました。これにより、ゼロサイズ型を多用するアプリケーション、特にチャネルを介した同期処理のパフォーマンスが改善されることが期待されました。

前提知識の解説

ゼロサイズ型 (Zero-Sized Types)

Go言語におけるゼロサイズ型とは、メモリを一切消費しない型のことです。最も一般的な例は空の構造体struct{}です。他にも、要素数が0の配列[0]intなどもゼロサイズ型とみなされます。

ゼロサイズ型の特徴は以下の通りです。

  • メモリ消費がない: 変数を宣言しても、ヒープやスタックにメモリが割り当てられません。
  • アドレスが同じ: 複数のゼロサイズ型の変数が存在しても、それらはすべて同じアドレスを指します。これは、メモリを消費しないため、区別する必要がないからです。
  • 用途:
    • セマフォ: チャネルにstruct{}を送信することで、リソースの利用を制限するセマフォとして利用できます。
    • セット: map[T]struct{}のように、マップの値にstruct{}を使用することで、キーの存在のみを管理するセット(集合)を効率的に実装できます。
    • イベント通知: chan struct{}を使って、イベントの発生を通知するシグナルとして利用できます。

Goランタイムの型アルゴリズム

Goのランタイムは、様々な型の操作(比較、ハッシュ計算、コピーなど)を効率的に行うために、型ごとに異なるアルゴリズムを内部的に持っています。これらのアルゴリズムは、src/pkg/runtime/alg.cのようなファイルで定義されており、コンパイラが生成するコードから呼び出されます。

例えば、整数型やポインタ型のような固定サイズの型に対しては、そのサイズに応じた効率的なメモリ操作(memequal8, memcopy16など)が提供されます。構造体や配列のような複合型に対しては、その要素の型に応じた再帰的なアルゴリズムが適用されます。

これらのアルゴリズムは、Goプログラムの実行効率に直結するため、非常に最適化されています。

Goチャネルとゼロサイズ型

Goのチャネルは、ゴルーチン間の通信と同期のための強力なプリミティブです。チャネルに値を送信したり受信したりする際には、その値の型に応じたメモリ操作(コピーなど)が発生します。

ゼロサイズ型をチャネルに送受信する場合、実際には値のコピーは不要です。しかし、最適化されていないランタイムでは、ゼロサイズ型であっても、他の型と同様にコピー操作のロジックが実行されてしまい、これがオーバーヘッドとなります。

BenchmarkChanSemは、ゼロサイズ型をチャネルに送受信することでセマフォのように利用するシナリオをベンチマークしています。このベンチマークの改善は、ゼロサイズ型のチャネル操作がより効率的になったことを示しています。

技術的詳細

本コミットの技術的詳細は、主にGoコンパイラ(gc)とランタイム(runtime)の連携、およびゼロサイズ型に特化した新しいアルゴリズムの導入にあります。

  1. コンパイラ側の変更 (src/cmd/gc/go.h, src/cmd/gc/subr.c):

    • src/cmd/gc/go.h: コンパイラが型を識別するために使用する列挙型algtypeに、ゼロサイズ型のための新しい定数AMEM0ANOEQ0が追加されました。
      • AMEM0: メモリ比較・コピーが可能で、サイズが0の型。
      • ANOEQ0: 等価性比較が不要で、サイズが0の型。 これらの定数は、既存のAMEM(汎用メモリ操作)およびANOEQ(等価性比較不要)のバリアントとして追加され、サイズに応じた最適化のパスを導入するための準備となります。
    • src/cmd/gc/subr.c: algtype関数が変更され、型のサイズが0の場合にAMEM0またはANOEQ0を返すようになりました。この関数は、コンパイラが特定の型に対してどのランタイムアルゴリズムを使用すべきかを決定する際に呼び出されます。これにより、コンパイラはゼロサイズ型に対して、後述するランタイムの新しいアルゴリズムを指し示すことができるようになります。
  2. ランタイム側の変更 (src/pkg/runtime/alg.c, src/pkg/runtime/runtime.h):

    • src/pkg/runtime/runtime.h: コンパイラ側の変更と同様に、ランタイムが型アルゴリズムを識別するための列挙型にAMEM0ANOEQ0が追加されました。
    • src/pkg/runtime/alg.c:
      • runtime·memequal0関数の追加: この関数は、ゼロサイズ型の等価性比較を担当します。ゼロサイズ型は常に等価であるため、この関数は単に*eq = true;を設定するだけで、実際のメモリ比較は行いません。
      • runtime·memcopy0関数の追加: この関数は、ゼロサイズ型のコピーを担当します。ゼロサイズ型はメモリを持たないため、この関数は何もせず、実際のメモリコピーは行いません。
      • runtime·algarrayの更新: runtime·algarrayは、Goの各型アルゴリズムのテーブルです。このテーブルに、新しく追加されたAMEM0ANOEQ0に対応するエントリが追加されました。
        • [AMEM0]のエントリには、ハッシュ関数としてruntime·memhash(ゼロサイズ型の場合、ハッシュ値は常に同じになる)、等価性比較関数としてruntime·memequal0、コピー関数としてruntime·memcopy0が設定されます。
        • [ANOEQ0]のエントリには、ハッシュ関数としてruntime·nohash(ハッシュ計算不要)、等価性比較関数としてruntime·noequal(等価性比較不要)、コピー関数としてruntime·memcopy0が設定されます。

これらの変更により、コンパイラはゼロサイズ型を検出すると、ランタイムのmemequal0memcopy0といった最適化された関数を呼び出すようになります。これにより、ゼロサイズ型の操作における不要な処理が完全にスキップされ、パフォーマンスが向上します。

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

本コミットにおける主要なコード変更は以下のファイルに集中しています。

  • src/cmd/gc/go.h:

    • enumブロックにAMEM0ANOEQ0が追加されました。
      --- a/src/cmd/gc/go.h
      +++ b/src/cmd/gc/go.h
      @@ -40,12 +40,14 @@ enum
       	// These values are known by runtime.
       	// The MEMx and NOEQx values must run in parallel.  See algtype.
       	AMEM		= 0,
      +	AMEM0,
       	AMEM8,
       	AMEM16,
       	AMEM32,
       	AMEM64,
       	AMEM128,
       	ANOEQ,
      +	ANOEQ0,
       	ANOEQ8,
       	ANOEQ16,
       	ANOEQ32,
      
  • src/cmd/gc/subr.c:

    • algtype関数内で、t->widthが0の場合にAMEM0を返すロジックが追加されました。
      --- a/src/cmd/gc/subr.c
      +++ b/src/cmd/gc/subr.c
      @@ -586,6 +586,8 @@ algtype(Type *t)
       		if(isslice(t))
       			return ASLICE;
       		switch(t->width) {
      +		case 0:
      +			return a + AMEM0 - AMEM;
       		case 1:
       			return a + AMEM8 - AMEM;
       		case 2:
      
  • src/pkg/runtime/alg.c:

    • runtime·memequal0runtime·memcopy0関数が新しく追加されました。
    • runtime·algarrayテーブルにAMEM0ANOEQ0のエントリが追加され、それぞれruntime·memequal0runtime·memcopy0が関連付けられました。
      --- a/src/pkg/runtime/alg.c
      +++ b/src/pkg/runtime/alg.c
      @@ -88,6 +88,23 @@ runtime·memcopy(uintptr s, void *a, void *b)\n \truntime·memmove(a, b, s);\n }\n \n+void\n+runtime·memequal0(bool *eq, uintptr s, void *a, void *b)\n+{\n+\tUSED(s);\n+\tUSED(a);\n+\tUSED(b);\n+\t*eq = true;\n+}\n+\n+void\n+runtime·memcopy0(uintptr s, void *a, void *b)\n+{\n+\tUSED(s);\n+\tUSED(a);\n+\tUSED(b);\n+}\n+\n void\n runtime·memequal8(bool *eq, uintptr s, void *a, void *b)\n {\n@@ -332,11 +349,13 @@ runtime·algarray[] =\n [AINTER]\t{ runtime·interhash, runtime·interequal, runtime·interprint, runtime·intercopy },\n [ANILINTER]\t{ runtime·nilinterhash, runtime·nilinterequal, runtime·nilinterprint, runtime·nilintercopy },\n [ASLICE]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·slicecopy },\n+[AMEM0]\t\t{ runtime·memhash, runtime·memequal0, runtime·memprint, runtime·memcopy0 },\n [AMEM8]\t\t{ runtime·memhash, runtime·memequal8, runtime·memprint, runtime·memcopy8 },\n [AMEM16]\t{ runtime·memhash, runtime·memequal16, runtime·memprint, runtime·memcopy16 },\n [AMEM32]\t{ runtime·memhash, runtime·memequal32, runtime·memprint, runtime·memcopy32 },\n [AMEM64]\t{ runtime·memhash, runtime·memequal64, runtime·memprint, runtime·memcopy64 },\n [AMEM128]\t{ runtime·memhash, runtime·memequal128, runtime·memprint, runtime·memcopy128 },\n+[ANOEQ0]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy0 },\n [ANOEQ8]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy8 },\n [ANOEQ16]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy16 },\n [ANOEQ32]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy32 },
      
  • src/pkg/runtime/chan_test.go:

    • BenchmarkChanSemという新しいベンチマーク関数が追加されました。これは、ゼロサイズ型Empty struct{}をチャネルに送受信する際のパフォーマンスを測定します。
      --- a/src/pkg/runtime/chan_test.go
      +++ b/src/pkg/runtime/chan_test.go
      @@ -371,3 +371,12 @@ func BenchmarkChanCreation(b *testing.B) {\n         	<-c
       	}\n         }\n+\n+func BenchmarkChanSem(b *testing.B) {\n+\ttype Empty struct{}\n+\tc := make(chan Empty, 1)\n+\tfor i := 0; i < b.N; i++ {\n+\t\tc <- Empty{}\n+\t\t<-c\n+\t}\n+}\
      
  • src/pkg/runtime/runtime.h:

    • enumブロックにAMEM0ANOEQ0が追加されました(src/cmd/gc/go.hと同様)。
      --- a/src/pkg/runtime/runtime.h
      +++ b/src/pkg/runtime/runtime.h
      @@ -358,12 +358,14 @@ enum {\n enum\n {\n         AMEM,\n+	AMEM0,\n         AMEM8,\n         AMEM16,\n         AMEM32,\n         AMEM64,\n         AMEM128,\n         ANOEQ,\n+	ANOEQ0,\n         ANOEQ8,\n         ANOEQ16,\n         ANOEQ32,\
      

コアとなるコードの解説

src/cmd/gc/go.h および src/pkg/runtime/runtime.h の変更

これらのヘッダーファイルでは、Goコンパイラとランタイムが型アルゴリズムを識別するために使用する列挙型に、AMEM0ANOEQ0という新しい定数が追加されました。これらは、それぞれ「メモリ操作が可能でサイズが0の型」と「等価性比較が不要でサイズが0の型」を意味します。これらの定数の追加により、Goの型システムがゼロサイズ型を特別に扱うための基盤が構築されました。

src/cmd/gc/subr.calgtype 関数の変更

algtype関数は、Goコンパイラの一部であり、与えられた型tに対して、ランタイムが使用すべき適切なアルゴリズムの種類(algtype)を決定します。このコミットでは、t->width(型のサイズ)が0である場合に、新しく定義されたAMEM0を返すように変更されました。

		switch(t->width) {
		case 0:
			return a + AMEM0 - AMEM;
		// ... (既存のケース)
		}

この変更により、コンパイラはゼロサイズ型を検出した際に、汎用的なメモリ操作アルゴリズム(AMEM)ではなく、ゼロサイズ型に特化したAMEM0アルゴリズムを使用するように指示するようになります。これは、コンパイラがランタイムの最適化されたパスを呼び出すための重要なリンクとなります。

src/pkg/runtime/alg.c の変更

このファイルはGoランタイムの型アルゴリズムの実装を含んでいます。

  1. runtime·memequal0 関数の追加:

    void
    runtime·memequal0(bool *eq, uintptr s, void *a, void *b)
    {
    	USED(s);
    	USED(a);
    	USED(b);
    	*eq = true;
    }
    

    この関数は、ゼロサイズ型の等価性比較を行います。ゼロサイズ型はメモリを持たないため、比較するデータがありません。したがって、常に等価であるとみなされます。USEDマクロは、引数が使用されていないことによるコンパイラの警告を抑制するためのものです。この関数は、実際の比較ロジックを一切持たず、単に*eq = trueを設定するだけで、非常に効率的です。

  2. runtime·memcopy0 関数の追加:

    void
    runtime·memcopy0(uintptr s, void *a, void *b)
    {
    	USED(s);
    	USED(a);
    	USED(b);
    }
    

    この関数は、ゼロサイズ型のコピー操作を行います。ゼロサイズ型はメモリを消費しないため、コピーすべきデータもありません。したがって、この関数は何も処理を行いません。これにより、ゼロサイズ型のコピー操作におけるオーバーヘッドが完全に排除されます。

  3. runtime·algarray テーブルの更新: runtime·algarrayは、Goの各型アルゴリズムの関数ポインタを格納するテーブルです。このテーブルに、新しく追加されたAMEM0ANOEQ0に対応するエントリが追加されました。

    [AMEM0]		{ runtime·memhash, runtime·memequal0, runtime·memprint, runtime·memcopy0 },
    // ...
    [ANOEQ0]	{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy0 },
    
    • [AMEM0]のエントリでは、等価性比較にruntime·memequal0、コピーにruntime·memcopy0が割り当てられています。ハッシュ関数にはruntime·memhashが使われますが、ゼロサイズ型の場合、ハッシュ値は常に同じになります。
    • [ANOEQ0]のエントリでは、等価性比較が不要な型(noequal)とハッシュ計算が不要な型(nohash)に対して、コピーにruntime·memcopy0が割り当てられています。

これらの変更により、Goランタイムはゼロサイズ型を特別に扱い、その特性を最大限に活かした最適化された操作を実行できるようになりました。

src/pkg/runtime/chan_test.goBenchmarkChanSem

このベンチマークは、ゼロサイズ型struct{}をチャネルに送受信する際のパフォーマンスを測定するために追加されました。

func BenchmarkChanSem(b *testing.B) {
	type Empty struct{}
	c := make(chan Empty, 1)
	for i := 0; i < b.N; i++ {
		c <- Empty{}
		<-c
	}
}

このベンチマークは、バッファ付きチャネルをセマフォとして使用する一般的なパターンをシミュレートしています。Empty{}はゼロサイズ型であるため、このベンチマークの改善は、ゼロサイズ型のチャネル操作が効率化されたことの直接的な証拠となります。コミットメッセージにあるBenchmarkChanSem old=127ns new=78.6nsという結果は、この最適化が約38%のパフォーマンス向上をもたらしたことを示しています。

関連リンク

参考にした情報源リンク

  • Go言語のゼロサイズ型に関する一般的な情報源 (例: Go言語の公式ドキュメント、Goに関する技術ブログなど)
  • Goランタイムの内部構造に関する情報源 (例: Goのソースコード、Goランタイムに関する論文や解説記事など)
  • Goのチャネルに関する詳細な情報源 (例: Go言語の公式ドキュメント、Goに関する技術ブログなど)
  • Goのベンチマークに関する情報源 (例: testingパッケージのドキュメント、Goのベンチマークに関する記事など)
  • Goのコンパイラ(gc)の内部構造に関する情報源 (例: Goのソースコード、Goコンパイラに関する論文や解説記事など)```markdown

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

本コミットは、Go言語のランタイムにおいて、ゼロサイズ型(zero-sized types)のための新しい型アルゴリズムを追加するものです。これにより、ゼロサイズ型の値の比較やコピーといった操作が最適化され、特にチャネル操作におけるパフォーマンスが大幅に向上しました。具体的には、BenchmarkChanSemのベンチマーク結果が127nsから78.6nsへと改善されており、これは約38%の高速化に相当します。

コミット

  • コミットハッシュ: 1ff1405cc72ad79da50de0339569a61b0132672a
  • 作者: Dmitriy Vyukov (dvyukov@google.com)
  • コミット日時: 2012年1月20日 金曜日 10:32:55 +0400

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

https://github.com/golang/go/commit/1ff1405cc72ad79da50de0339569a61b0132672a

元コミット内容

    runtime: add type algorithms for zero-sized types
    BenchmarkChanSem old=127ns new=78.6ns
    
    R=golang-dev, bradfitz, sameer, rsc
    CC=golang-dev
    https://golang.org/cl/5558049

変更の背景

Go言語において、struct{}のようなゼロサイズ型は、メモリを一切消費しない特殊な型です。これらは、セマフォの実装や、マップのキーとして値の存在のみを示す場合など、様々な場面で利用されます。しかし、Goのランタイムは、これらのゼロサイズ型に対しても、他の通常の型と同様に、値の比較(memequal)やコピー(memcopy)といった操作を汎用的なアルゴリズムで行っていました。

ゼロサイズ型はメモリを持たないため、これらの操作は実際には何もする必要がありません。しかし、汎用的なアルゴリズムを使用すると、不要なメモリアクセスや比較ロジックが実行され、オーバーヘッドが発生していました。特に、チャネルをセマフォとして利用する際(ゼロサイズ型をチャネルに送受信する場合)には、このオーバーヘッドが顕著になり、パフォーマンスのボトルネックとなっていました。

本コミットの目的は、ゼロサイズ型に特化した最適化されたアルゴリズムを導入することで、これらの不要な処理を排除し、ランタイムの効率を向上させることにありました。これにより、ゼロサイズ型を多用するアプリケーション、特にチャネルを介した同期処理のパフォーマンスが改善されることが期待されました。

前提知識の解説

ゼロサイズ型 (Zero-Sized Types)

Go言語におけるゼロサイズ型とは、メモリを一切消費しない型のことです。最も一般的な例は空の構造体struct{}です。他にも、要素数が0の配列[0]intなどもゼロサイズ型とみなされます。

ゼロサイズ型の特徴は以下の通りです。

  • メモリ消費がない: 変数を宣言しても、ヒープやスタックにメモリが割り当てられません。
  • アドレスが同じ: 複数のゼロサイズ型の変数が存在しても、それらはすべて同じアドレスを指します。これは、メモリを消費しないため、区別する必要がないからです。
  • 用途:
    • セマフォ: チャネルにstruct{}を送信することで、リソースの利用を制限するセマフォとして利用できます。
    • セット: map[T]struct{}のように、マップの値にstruct{}を使用することで、キーの存在のみを管理するセット(集合)を効率的に実装できます。
    • イベント通知: chan struct{}を使って、イベントの発生を通知するシグナルとして利用できます。

Goランタイムの型アルゴリズム

Goのランタイムは、様々な型の操作(比較、ハッシュ計算、コピーなど)を効率的に行うために、型ごとに異なるアルゴリズムを内部的に持っています。これらのアルゴリズムは、src/pkg/runtime/alg.cのようなファイルで定義されており、コンパイラが生成するコードから呼び出されます。

例えば、整数型やポインタ型のような固定サイズの型に対しては、そのサイズに応じた効率的なメモリ操作(memequal8, memcopy16など)が提供されます。構造体や配列のような複合型に対しては、その要素の型に応じた再帰的なアルゴリズムが適用されます。

これらのアルゴリズムは、Goプログラムの実行効率に直結するため、非常に最適化されています。

Goチャネルとゼロサイズ型

Goのチャネルは、ゴルーチン間の通信と同期のための強力なプリミティブです。チャネルに値を送信したり受信したりする際には、その値の型に応じたメモリ操作(コピーなど)が発生します。

ゼロサイズ型をチャネルに送受信する場合、実際には値のコピーは不要です。しかし、最適化されていないランタイムでは、ゼロサイズ型であっても、他の型と同様にコピー操作のロジックが実行されてしまい、これがオーバーヘッドとなります。

BenchmarkChanSemは、ゼロサイズ型をチャネルに送受信することでセマフォのように利用するシナリオをベンチマークしています。このベンチマークの改善は、ゼロサイズ型のチャネル操作がより効率的になったことを示しています。

技術的詳細

本コミットの技術的詳細は、主にGoコンパイラ(gc)とランタイム(runtime)の連携、およびゼロサイズ型に特化した新しいアルゴリズムの導入にあります。

  1. コンパイラ側の変更 (src/cmd/gc/go.h, src/cmd/gc/subr.c):

    • src/cmd/gc/go.h: コンパイラが型を識別するために使用する列挙型algtypeに、ゼロサイズ型のための新しい定数AMEM0ANOEQ0が追加されました。
      • AMEM0: メモリ比較・コピーが可能で、サイズが0の型。
      • ANOEQ0: 等価性比較が不要で、サイズが0の型。 これらの定数は、既存のAMEM(汎用メモリ操作)およびANOEQ(等価性比較不要)のバリアントとして追加され、サイズに応じた最適化のパスを導入するための準備となります。
    • src/cmd/gc/subr.c: algtype関数が変更され、型のサイズが0の場合にAMEM0またはANOEQ0を返すようになりました。この関数は、コンパイラが特定の型に対してどのランタイムアルゴリズムを使用すべきかを決定する際に呼び出されます。これにより、コンパイラはゼロサイズ型に対して、後述するランタイムの新しいアルゴリズムを指し示すことができるようになります。
  2. ランタイム側の変更 (src/pkg/runtime/alg.c, src/pkg/runtime/runtime.h):

    • src/pkg/runtime/runtime.h: コンパイラ側の変更と同様に、ランタイムが型アルゴリズムを識別するための列挙型にAMEM0ANOEQ0が追加されました。
    • src/pkg/runtime/alg.c:
      • runtime·memequal0関数の追加: この関数は、ゼロサイズ型の等価性比較を担当します。ゼロサイズ型は常に等価であるため、この関数は単に*eq = true;を設定するだけで、実際のメモリ比較は行いません。
      • runtime·memcopy0関数の追加: この関数は、ゼロサイズ型のコピーを担当します。ゼロサイズ型はメモリを持たないため、この関数は何もせず、実際のメモリコピーは行いません。
      • runtime·algarrayの更新: runtime·algarrayは、Goの各型アルゴリズムのテーブルです。このテーブルに、新しく追加されたAMEM0ANOEQ0に対応するエントリが追加されました。
        • [AMEM0]のエントリには、ハッシュ関数としてruntime·memhash(ゼロサイズ型の場合、ハッシュ値は常に同じになる)、等価性比較関数としてruntime·memequal0、コピー関数としてruntime·memcopy0が設定されます。
        • [ANOEQ0]のエントリには、ハッシュ関数としてruntime·nohash(ハッシュ計算不要)、等価性比較関数としてruntime·noequal(等価性比較不要)、コピー関数としてruntime·memcopy0が設定されます。

これらの変更により、コンパイラはゼロサイズ型を検出すると、ランタイムのmemequal0memcopy0といった最適化された関数を呼び出すようになります。これにより、ゼロサイズ型の操作における不要な処理が完全にスキップされ、パフォーマンスが向上します。

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

本コミットにおける主要なコード変更は以下のファイルに集中しています。

  • src/cmd/gc/go.h:

    • enumブロックにAMEM0ANOEQ0が追加されました。
      --- a/src/cmd/gc/go.h
      +++ b/src/cmd/gc/go.h
      @@ -40,12 +40,14 @@ enum
       	// These values are known by runtime.
       	// The MEMx and NOEQx values must run in parallel.  See algtype.
       	AMEM		= 0,
      +	AMEM0,
       	AMEM8,
       	AMEM16,
       	AMEM32,
       	AMEM64,
       	AMEM128,
       	ANOEQ,
      +	ANOEQ0,
       	ANOEQ8,
       	ANOEQ16,
       	ANOEQ32,
      
  • src/cmd/gc/subr.c:

    • algtype関数内で、t->widthが0の場合にAMEM0を返すロジックが追加されました。
      --- a/src/cmd/gc/subr.c
      +++ b/src/cmd/gc/subr.c
      @@ -586,6 +586,8 @@ algtype(Type *t)
       		if(isslice(t))
       			return ASLICE;
       		switch(t->width) {
      +		case 0:
      +			return a + AMEM0 - AMEM;
       		case 1:
       			return a + AMEM8 - AMEM;
       		case 2:
      
  • src/pkg/runtime/alg.c:

    • runtime·memequal0runtime·memcopy0関数が新しく追加されました。
    • runtime·algarrayテーブルにAMEM0ANOEQ0のエントリが追加され、それぞれruntime·memequal0runtime·memcopy0が関連付けられました。
      --- a/src/pkg/runtime/alg.c
      +++ b/src/pkg/runtime/alg.c
      @@ -88,6 +88,23 @@ runtime·memcopy(uintptr s, void *a, void *b)\n \truntime·memmove(a, b, s);\n }\n \n+void\n+runtime·memequal0(bool *eq, uintptr s, void *a, void *b)\n+{\n+\tUSED(s);\n+\tUSED(a);\n+\tUSED(b);\n+\t*eq = true;\n+}\n+\n+void\n+runtime·memcopy0(uintptr s, void *a, void *b)\n+{\n+\tUSED(s);\n+\tUSED(a);\n+\tUSED(b);\n+}\n+\n void\n runtime·memequal8(bool *eq, uintptr s, void *a, void *b)\n {\n@@ -332,11 +349,13 @@ runtime·algarray[] =\n [AINTER]\t{ runtime·interhash, runtime·interequal, runtime·interprint, runtime·intercopy },\n [ANILINTER]\t{ runtime·nilinterhash, runtime·nilinterequal, runtime·nilinterprint, runtime·nilintercopy },\n [ASLICE]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·slicecopy },\n+[AMEM0]\t\t{ runtime·memhash, runtime·memequal0, runtime·memprint, runtime·memcopy0 },\n [AMEM8]\t\t{ runtime·memhash, runtime·memequal8, runtime·memprint, runtime·memcopy8 },\n [AMEM16]\t{ runtime·memhash, runtime·memequal16, runtime·memprint, runtime·memcopy16 },\n [AMEM32]\t{ runtime·memhash, runtime·memequal32, runtime·memprint, runtime·memcopy32 },\n [AMEM64]\t{ runtime·memhash, runtime·memequal64, runtime·memprint, runtime·memcopy64 },\n [AMEM128]\t{ runtime·memhash, runtime·memequal128, runtime·memprint, runtime·memcopy128 },\n+[ANOEQ0]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy0 },\n [ANOEQ8]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy8 },\n [ANOEQ16]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy16 },\n [ANOEQ32]\t{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy32 },
      
  • src/pkg/runtime/chan_test.go:

    • BenchmarkChanSemという新しいベンチマーク関数が追加されました。これは、ゼロサイズ型Empty struct{}をチャネルに送受信する際のパフォーマンスを測定します。
      --- a/src/pkg/runtime/chan_test.go
      +++ b/src/pkg/runtime/chan_test.go
      @@ -371,3 +371,12 @@ func BenchmarkChanCreation(b *testing.B) {\n         	<-c
       	}\n         }\n+\n+func BenchmarkChanSem(b *testing.B) {\n+\ttype Empty struct{}\n+\tc := make(chan Empty, 1)\n+\tfor i := 0; i < b.N; i++ {\n+\t\tc <- Empty{}\n+\t\t<-c
      

+\t}\n+}
```

  • src/pkg/runtime/runtime.h:
    • enumブロックにAMEM0ANOEQ0が追加されました(src/cmd/gc/go.hと同様)。
      --- a/src/pkg/runtime/runtime.h
      +++ b/src/pkg/runtime/runtime.h
      @@ -358,12 +358,14 @@ enum {\n enum\n {\n         AMEM,\n+	AMEM0,\n         AMEM8,\n         AMEM16,\n         AMEM32,\n         AMEM64,\n         AMEM128,\n         ANOEQ,\n+	ANOEQ0,\n         ANOEQ8,\n         ANOEQ16,\n         ANOEQ32,\
      

コアとなるコードの解説

src/cmd/gc/go.h および src/pkg/runtime/runtime.h の変更

これらのヘッダーファイルでは、Goコンパイラとランタイムが型アルゴリズムを識別するために使用する列挙型に、AMEM0ANOEQ0という新しい定数が追加されました。これらは、それぞれ「メモリ操作が可能でサイズが0の型」と「等価性比較が不要でサイズが0の型」を意味します。これらの定数の追加により、Goの型システムがゼロサイズ型を特別に扱うための基盤が構築されました。

src/cmd/gc/subr.calgtype 関数の変更

algtype関数は、Goコンパイラの一部であり、与えられた型tに対して、ランタイムが使用すべき適切なアルゴリズムの種類(algtype)を決定します。このコミットでは、t->width(型のサイズ)が0である場合に、新しく定義されたAMEM0を返すように変更されました。

		switch(t->width) {
		case 0:
			return a + AMEM0 - AMEM;
		// ... (既存のケース)
		}

この変更により、コンパイラはゼロサイズ型を検出した際に、汎用的なメモリ操作アルゴリズム(AMEM)ではなく、ゼロサイズ型に特化したAMEM0アルゴリズムを使用するように指示するようになります。これは、コンパイラがランタイムの最適化されたパスを呼び出すための重要なリンクとなります。

src/pkg/runtime/alg.c の変更

このファイルはGoランタイムの型アルゴリズムの実装を含んでいます。

  1. runtime·memequal0 関数の追加:

    void
    runtime·memequal0(bool *eq, uintptr s, void *a, void *b)
    {
    	USED(s);
    	USED(a);
    	USED(b);
    	*eq = true;
    }
    

    この関数は、ゼロサイズ型の等価性比較を行います。ゼロサイズ型はメモリを持たないため、比較するデータがありません。したがって、常に等価であるとみなされます。USEDマクロは、引数が使用されていないことによるコンパイラの警告を抑制するためのものです。この関数は、実際の比較ロジックを一切持たず、単に*eq = trueを設定するだけで、非常に効率的です。

  2. runtime·memcopy0 関数の追加:

    void
    runtime·memcopy0(uintptr s, void *a, void *b)
    {
    	USED(s);
    	USED(a);
    	USED(b);
    }
    

    この関数は、ゼロサイズ型のコピー操作を行います。ゼロサイズ型はメモリを消費しないため、コピーすべきデータもありません。したがって、この関数は何も処理を行いません。これにより、ゼロサイズ型のコピー操作におけるオーバーヘッドが完全に排除されます。

  3. runtime·algarray テーブルの更新: runtime·algarrayは、Goの各型アルゴリズムの関数ポインタを格納するテーブルです。このテーブルに、新しく追加されたAMEM0ANOEQ0に対応するエントリが追加されました。

    [AMEM0]		{ runtime·memhash, runtime·memequal0, runtime·memprint, runtime·memcopy0 },
    // ...
    [ANOEQ0]	{ runtime·nohash, runtime·noequal, runtime·memprint, runtime·memcopy0 },
    
    • [AMEM0]のエントリでは、等価性比較にruntime·memequal0、コピーにruntime·memcopy0が割り当てられています。ハッシュ関数にはruntime·memhashが使われますが、ゼロサイズ型の場合、ハッシュ値は常に同じになります。
    • [ANOEQ0]のエントリでは、等価性比較が不要な型(noequal)とハッシュ計算が不要な型(nohash)に対して、コピーにruntime·memcopy0が割り当てられています。

これらの変更により、Goランタイムはゼロサイズ型を特別に扱い、その特性を最大限に活かした最適化された操作を実行できるようになりました。

src/pkg/runtime/chan_test.goBenchmarkChanSem

このベンチマークは、ゼロサイズ型struct{}をチャネルに送受信する際のパフォーマンスを測定するために追加されました。

func BenchmarkChanSem(b *testing.B) {
	type Empty struct{}
	c := make(chan Empty, 1)
	for i := 0; i < b.N; i++ {
		c <- Empty{}
		<-c
	}
}

このベンチマークは、バッファ付きチャネルをセマフォとして使用する一般的なパターンをシミュレートしています。Empty{}はゼロサイズ型であるため、このベンチマークの改善は、ゼロサイズ型のチャネル操作が効率化されたことの直接的な証拠となります。コミットメッセージにあるBenchmarkChanSem old=127ns new=78.6nsという結果は、この最適化が約38%のパフォーマンス向上をもたらしたことを示しています。

関連リンク

参考にした情報源リンク

  • Go言語のゼロサイズ型に関する一般的な情報源 (例: Go言語の公式ドキュメント、Goに関する技術ブログなど)
  • Goランタイムの内部構造に関する情報源 (例: Goのソースコード、Goランタイムに関する論文や解説記事など)
  • Goのチャネルに関する詳細な情報源 (例: Go言語の公式ドキュメント、Goに関する技術ブログなど)
  • Goのベンチマークに関する情報源 (例: testingパッケージのドキュメント、Goのベンチマークに関する記事など)
  • Goのコンパイラ(gc)の内部構造に関する情報源 (例: Goのソースコード、Goコンパイラに関する論文や解説記事など)