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

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

このコミットは、Go言語の標準ライブラリのAPIを検証するためのツールである cmd/api に、標準ライブラリ全体を対象としたベンチマークを追加するものです。具体的には、goapi_test.go ファイルに BenchmarkAll という新しいベンチマーク関数が追加され、Goの標準パッケージを走査し、そのAPIをエクスポートする処理のパフォーマンスを測定します。

コミット

cmd/api: 標準ライブラリに対するベンチマークを追加

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

https://github.com/golang/go/commit/b379b32f31b13381308887fcf02ca52b937a0f07

元コミット内容

commit b379b32f31b13381308887fcf02ca52b937a0f07
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Thu Aug 8 15:25:15 2013 -0700

    cmd/api: add a benchmark over the standard library
    
    R=golang-dev, gri
    CC=golang-dev
    https://golang.org/cl/12603045
---
 src/cmd/api/goapi_test.go | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/src/cmd/api/goapi_test.go b/src/cmd/api/goapi_test.go
index a1e762bafc..b909c32b34 100644
--- a/src/cmd/api/goapi_test.go
+++ b/src/cmd/api/goapi_test.go
@@ -10,8 +10,10 @@ import (
 	"bytes"
 	"flag"
 	"fmt"
+"go/build"
 	"io/ioutil"
 	"os"
+"os/exec"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -139,3 +141,28 @@ func TestCompareAPI(t *testing.T) {
 		}
 	}\n}\n+\n+func BenchmarkAll(b *testing.B) {\n+\tstds, err := exec.Command("go", "list", "std").Output()\n+\tif err != nil {\n+\t\tb.Fatal(err)\n+\t}\n+\tb.ResetTimer()\n+\tpkgNames := strings.Fields(string(stds))\n+\n+\tfor _, c := range contexts {\n+\t\tc.Compiler = build.Default.Compiler\n+\t}\n+\n+\tfor i := 0; i < b.N; i++ {\n+\t\tfor _, context := range contexts {\n+\t\t\tw := NewWalker(context, filepath.Join(build.Default.GOROOT, "src/pkg"))\n+\t\t\tfor _, name := range pkgNames {\n+\t\t\t\tif name != "unsafe" && !strings.HasPrefix(name, "cmd/") {\n+\t\t\t\t\tw.export(w.Import(name))\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tw.Features()\n+\t\t}\n+\t}\n+}\n```

## 変更の背景

`cmd/api` ツールは、Go言語のAPIの互換性をチェックするために使用されます。このツールは、Goのリリース間でAPIが意図せず変更されていないことを確認する上で重要です。APIの解析処理は、Goのコンパイラやビルドシステムと密接に関連しており、そのパフォーマンスはツールの実用性に直結します。

このコミットが導入された背景には、`cmd/api` が標準ライブラリ全体を処理する際のパフォーマンスを測定し、将来的な最適化の基準とすることが挙げられます。API解析の効率は、開発者がAPIの変更を迅速に検証できるかどうかに影響するため、このベンチマークはツールの健全性を維持するために不可欠です。

## 前提知識の解説

### Go言語の `testing` パッケージとベンチマーク

Go言語には、標準ライブラリとして `testing` パッケージが提供されており、ユニットテストだけでなくベンチマークテストもサポートしています。

*   **ベンチマーク関数**: `func BenchmarkXxx(b *testing.B)` というシグネチャを持つ関数がベンチマーク関数として認識されます。
*   **`*testing.B`**: ベンチマークのコンテキストを提供する構造体です。
    *   `b.N`: ベンチマーク関数が実行される回数を示します。Goのテストフレームワークが自動的に調整し、安定した測定結果が得られるようにします。
    *   `b.ResetTimer()`: タイマーをリセットします。通常、ベンチマーク対象の処理の直前に呼び出され、セットアップにかかる時間を測定から除外します。
    *   `b.Fatal(err)`: エラーが発生した場合にベンチマークを停止し、エラーメッセージを出力します。

### `cmd/api` ツール

`cmd/api` は、Go言語の標準ライブラリやその他のGoパッケージの公開API(エクスポートされた型、関数、変数など)を抽出・比較するためのコマンドラインツールです。主に、Goの新しいバージョンがリリースされる際に、既存のコードとの互換性を維持するためにAPIの変更を追跡するために使用されます。このツールは、Goのソースコードを解析し、その構造からAPI情報を導き出します。

### `go/build` パッケージ

`go/build` パッケージは、Goのパッケージのビルドプロセスに関する情報を提供します。
*   `build.Default`: 現在のシステム環境におけるデフォルトのビルドコンテキストを表します。
*   `build.Default.GOROOT`: Goのインストールルートディレクトリのパスを提供します。
*   `build.Default.Compiler`: デフォルトで使用されるGoコンパイラの名前(例: "gc")を提供します。

### `os/exec` パッケージ

`os/exec` パッケージは、外部コマンドを実行するための機能を提供します。
*   `exec.Command(name, arg...)`: 実行するコマンドと引数を指定して `Cmd` 構造体を作成します。
*   `Cmd.Output()`: コマンドを実行し、その標準出力をバイトスライスとして返します。

## 技術的詳細

このコミットで追加された `BenchmarkAll` 関数は、`cmd/api` ツールがGoの標準ライブラリ全体を解析する際のパフォーマンスを測定します。

1.  **標準パッケージリストの取得**:
    `exec.Command("go", "list", "std").Output()` を実行することで、Goの標準ライブラリに含まれるすべてのパッケージのリストを取得します。これは、`go list std` コマンドが標準パッケージの名前を改行区切りで出力することを利用しています。
2.  **タイマーのリセット**:
    `b.ResetTimer()` が呼び出され、これ以前のセットアップ処理(`go list std` の実行など)がベンチマークの測定時間から除外されます。
3.  **ベンチマークループ**:
    `for i := 0; i < b.N; i++` ループは、ベンチマークの実行回数を制御します。このループ内で、`cmd/api` の主要なAPI解析ロジックが実行されます。
4.  **コンテキストの設定**:
    `contexts` は `cmd/api` 内部で定義されている解析コンテキストのリストと考えられます。各コンテキストに対して、`Compiler` フィールドが `build.Default.Compiler` に設定されます。これにより、ベンチマークが実行される環境のデフォルトコンパイラが使用されることが保証されます。
5.  **APIウォーカーの初期化と実行**:
    各ベンチマークイテレーションと各コンテキスト内で、`NewWalker` が呼び出され、API解析のためのウォーカーが初期化されます。ウォーカーは `filepath.Join(build.Default.GOROOT, "src/pkg")` をベースパスとして使用し、Goの標準ライブラリのソースコードが配置されている場所を指します。
6.  **パッケージのエクスポート**:
    取得した `pkgNames` リストを反復処理し、各パッケージに対して `w.Import(name)` でパッケージをインポートし、`w.export(...)` でそのAPIをエクスポートします。ただし、`"unsafe"` パッケージと `"cmd/"` で始まるパッケージは、API解析の対象から除外されます。これは、これらのパッケージが特殊な性質を持つか、API互換性チェックの対象外であるためと考えられます。
7.  **機能の抽出**:
    すべてのパッケージのエクスポートが完了した後、`w.Features()` が呼び出されます。これは、エクスポートされたAPIから特定の機能や特性を抽出する処理を表していると考えられます。

このベンチマークは、`cmd/api` がGoの標準ライブラリ全体を効率的に解析できることを保証し、将来的なパフォーマンス改善のためのベースラインを提供します。

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

`src/cmd/api/goapi_test.go` ファイルに以下の変更が加えられました。

1.  **新しいインポート**:
    *   `"go/build"`
    *   `"os/exec"`
    が追加されました。

2.  **`BenchmarkAll` 関数の追加**:
    ファイルの末尾に `BenchmarkAll` という新しいベンチマーク関数が追加されました。この関数は、Goの標準ライブラリ全体を対象にAPI解析のベンチマークを実行します。

## コアとなるコードの解説

```go
func BenchmarkAll(b *testing.B) {
	// Goの標準パッケージのリストを取得
	stds, err := exec.Command("go", "list", "std").Output()
	if err != nil {
		b.Fatal(err) // エラーが発生した場合はベンチマークを停止
	}
	b.ResetTimer() // セットアップ時間を測定から除外するためにタイマーをリセット
	pkgNames := strings.Fields(string(stds)) // パッケージ名をスペースで分割してスライスに変換

	// 各コンテキストのコンパイラを設定
	for _, c := range contexts {
		c.Compiler = build.Default.Compiler
	}

	// b.N回ベンチマークを実行
	for i := 0; i < b.N; i++ {
		// 各解析コンテキストに対して処理を実行
		for _, context := range contexts {
			// 新しいAPIウォーカーを初期化
			// GOROOT/src/pkg をベースパスとして使用
			w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src/pkg"))
			// 各標準パッケージを反復処理
			for _, name := range pkgNames {
				// "unsafe" パッケージと "cmd/" で始まるパッケージはスキップ
				if name != "unsafe" && !strings.HasPrefix(name, "cmd/") {
					// パッケージをインポートし、そのAPIをエクスポート
					w.export(w.Import(name))
				}
			}
			// エクスポートされたAPIから機能を抽出
			w.Features()
		}
	}
}

このコードは、Goの標準ライブラリのAPI解析処理のパフォーマンスを測定するためのものです。

  • exec.Command("go", "list", "std").Output(): Goコマンドを実行して、Goの標準ライブラリに含まれるすべてのパッケージの名前を取得します。これにより、ベンチマークの対象となるパッケージのリストが動的に生成されます。
  • b.ResetTimer(): この行は非常に重要で、go list std の実行にかかる時間など、ベンチマーク対象の処理以外のセットアップ時間を測定から除外します。これにより、純粋なAPI解析処理のパフォーマンスが測定されます。
  • pkgNames := strings.Fields(string(stds)): 取得したパッケージ名の文字列を、スペースで区切って個々のパッケージ名に分割し、文字列スライスに格納します。
  • for _, c := range contexts { c.Compiler = build.Default.Compiler }: contextscmd/api ツールがAPIを解析する際に使用する様々なビルドコンテキストを表していると考えられます。各コンテキストに対して、デフォルトのGoコンパイラが設定されます。
  • for i := 0; i < b.N; i++: これはGoのベンチマーク関数の標準的なループ構造です。b.N はベンチマークフレームワークによって自動的に調整され、統計的に有意な結果が得られるように十分な回数だけループが実行されます。
  • w := NewWalker(context, filepath.Join(build.Default.GOROOT, "src/pkg")): NewWalkercmd/api の内部関数で、APIを走査・解析するためのオブジェクト(ウォーカー)を生成します。filepath.Join(build.Default.GOROOT, "src/pkg") は、Goの標準ライブラリのソースコードが配置されているディレクトリのパスを構築します。
  • if name != "unsafe" && !strings.HasPrefix(name, "cmd/"): unsafe パッケージは特殊な性質を持ち、通常のAPI解析の対象外となることが多いため除外されます。また、cmd/ で始まるパッケージ(例: cmd/go, cmd/api など)も、それ自体がコマンドであるため、API互換性チェックの主要な対象ではないため除外されます。
  • w.export(w.Import(name)): これはAPI解析の核心部分です。w.Import(name) で指定されたパッケージをインポートし、その結果得られたパッケージ情報から公開APIを抽出(エクスポート)します。
  • w.Features(): エクスポートされたAPIから、特定の機能や特性を最終的に集計する処理を実行します。

このベンチマークは、cmd/api がGoの標準ライブラリ全体を解析する際のボトルネックを特定し、将来的なパフォーマンス改善の指針となるデータを提供することを目的としています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/cmd/api ディレクトリ)
  • Go言語の testing パッケージのドキュメント
  • Go言語の go/build パッケージのドキュメント
  • Go言語の os/exec パッケージのドキュメント
  • Go言語のベンチマークに関するブログ記事
  • Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/12603045
  • GitHubのコミットページ: https://github.com/golang/go/commit/b379b32f31b13381308887fcf02ca52b937a0f07