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

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

このコミットは、Go言語のリポジトリにおける意図しないコミットの取り消し(revert)に関するものです。具体的には、test/bench/perf ディレクトリ内の3つのファイル(bench1.go, bench2.go, driver.go)が削除されています。コミットメッセージから、コミッターが誤って別のリポジトリで作業していると勘違いし、本来コミットすべきではないファイルをGoリポジトリにコミットしてしまったことが伺えます。

コミット

commit b3d400c35e10beb2e85c0b00d61b44792b6d8457
Author: dvyukov <dvyukov@google.com>
Date:   Tue Nov 19 15:36:13 2013 +0400

    test: revert unintentional commits
    I thought I am in a different repo...

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

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

元コミット内容

このコミット自体は、以前のコミットで誤って追加されたファイルを削除するものです。したがって、「元コミット内容」とは、このコミットによって削除されたファイルの内容を指します。削除されたファイルは以下の通りです。

  • test/bench/perf/bench1.go
  • test/bench/perf/bench2.go
  • test/bench/perf/driver.go

これらのファイルは、Goのパフォーマンスベンチマークに関連するコードを含んでいました。

bench1.go の内容

package main

import (
	"time"
)

func main() {
	PerfBenchmark(SleepBenchmark)
}

func SleepBenchmark(N int64) (metrics []PerfMetric, err error) {
	time.Sleep(time.Duration(N) * time.Millisecond)
	metrics = append(metrics, PerfMetric{"foo", 42})
	return
}

bench2.go の内容

package main

func Benchmark(N int64) error {
	return nil
}

driver.go の内容

package main

import (
	"flag"
	"fmt"
	"log"
	"time"
	"runtime"
)

var (
	benchNum  = flag.Int("benchnum", 3, "run each benchmark that many times")
	benchTime = flag.Duration("benchtime", 10*time.Second, "benchmarking time for a single run")
	benchMem  = flag.Int("benchmem", 64, "approx RSS value to aim at in benchmarks, in MB")
)

type PerfResult struct {
	N       int64
	RunTime time.Duration
	Metrics []PerfMetric
}

type PerfMetric struct {
	Type  string
	Val   int64
}

type BenchFunc func(N int64) ([]PerfMetric, error)

func PerfBenchmark(f BenchFunc) {
	if !flag.Parsed() {
		flag.Parse()
	}
	var res PerfResult
	for i := 0; i < *benchNum; i++ {
		res1 := RunBenchmark(f)
		if res.RunTime == 0 || res.RunTime > res1.RunTime {
			res = res1
		}
	}
	fmt.Printf("GOPERF-METRIC:runtime=%v\n", int64(res.RunTime)/res.N)
	for _, m := range res.Metrics {
		fmt.Printf("GOPERF-METRIC:%v=%v\n", m.Type, m.Val)
	}
}

func RunBenchmark(f BenchFunc) PerfResult {
	var res PerfResult
	for ChooseN(&res) {
		log.Printf("Benchmarking %v iterations\\n", res.N)
		res = RunOnce(f, res.N)
		log.Printf("Done: %+v\\n", res)
	}
	return res
}

func RunOnce(f BenchFunc, N int64) PerfResult {
	runtime.GC()
	mstats0 := new(runtime.MemStats)
	runtime.ReadMemStats(mstats0)
	res := PerfResult{N: N}

	t0 := time.Now()
	var err error
	res.Metrics, err = f(N)
	res.RunTime = time.Since(t0)

	if err != nil {
		log.Fatalf("Benchmark function failed: %v\\n", err)
	}

	mstats1 := new(runtime.MemStats)
	runtime.ReadMemStats(mstats1)
	fmt.Printf("%+v\\n", *mstats1)
	return res
}

func ChooseN(res *PerfResult) bool {
	const MaxN = 1e12
	last := res.N
	if last == 0 {
		res.N = 1
		return true
	} else if res.RunTime >= *benchTime || last >= MaxN {
		return false
	}
	nsPerOp := max(1, int64(res.RunTime)/last)
	res.N = int64(*benchTime) / nsPerOp
	res.N = max(min(res.N+res.N/2, 100*last), last+1)
	res.N = roundUp(res.N)
	return true
}

func roundUp(n int64) int64 {
	tmp := n
	base := int64(1)
	for tmp >= 10 {
		tmp /= 10
		base *= 10
	}
	switch {
	case n <= base:
		return base
	case n <= (2 * base):
		return 2 * base
	case n <= (5 * base):
		return 5 * base
	default:
		return 10 * base
	}
	panic("unreachable")
	return 0
}

func min(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}

func max(a, b int64) int64 {
	if a > b {
		return a
	}
	return b
}

変更の背景

このコミットの背景は、コミットメッセージに明確に示されています。「I thought I am in a different repo...」(別のリポジトリにいると勘違いしていた…)というメッセージから、コミッターがGo言語の公式リポジトリではない、別の個人プロジェクトやテスト用のリポジトリで作業していた際に、誤ってGoリポジトリにこれらのベンチマーク関連ファイルをコミットしてしまったことが分かります。

このような「意図しないコミット」は、開発者が複数のプロジェクトやブランチを並行して作業している際によく発生するヒューマンエラーです。特に、Gitの作業ディレクトリやブランチの切り替えを頻繁に行う環境では、誤ったリポジトリでgit addgit commitを実行してしまうリスクがあります。このコミットは、その誤りを修正し、Goリポジトリのクリーンな状態を維持するためのものです。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

1. Gitの基本的な操作

  • コミット (Commit): Gitにおける変更履歴の単位。一連の変更をまとめて記録します。
  • リバート (Revert): 既存のコミットの変更内容を打ち消す新しいコミットを作成する操作。履歴を消すのではなく、変更を元に戻すための新しい履歴を追加します。今回のケースでは、誤って追加されたファイルを削除する変更が新しいコミットとして記録されています。
  • リポジトリ (Repository): Gitが管理するプロジェクトの全てのファイルと変更履歴が保存されている場所。

2. Go言語のテストとベンチマーク

Go言語には、標準でテストとベンチマークの機能が組み込まれています。

  • go test コマンド: Goのテストを実行するためのコマンド。_test.go で終わるファイルに記述されたテスト関数やベンチマーク関数を実行します。
  • ベンチマーク関数: BenchmarkXxx(*testing.B) の形式で記述される関数で、コードのパフォーマンスを測定するために使用されます。testing.B 型の引数を通じて、ベンチマークの実行回数や時間を制御できます。
  • test/bench/perf ディレクトリ: Go言語の公式リポジトリにおいて、test/bench ディレクトリは通常、Goのランタイムや標準ライブラリのパフォーマンスを測定するためのベンチマークコードが格納される場所です。perf サブディレクトリは、特定のパフォーマンス測定に関連するベンチマークをまとめるために使用されることがあります。

3. パフォーマンス測定の概念

  • ベンチマーク (Benchmark): ソフトウェアやシステムの性能を測定するためのテスト。特定のタスクを実行し、その完了にかかる時間やリソース消費量を計測します。
  • メトリクス (Metrics): 測定された性能指標。例えば、実行時間、メモリ使用量、操作あたりのナノ秒など。
  • RSS (Resident Set Size): プロセスが物理メモリにロードしているメモリの量。ベンチマークにおいて、メモリ使用量の指標として用いられることがあります。

4. flag パッケージ

Goの標準ライブラリである flag パッケージは、コマンドライン引数を解析するために使用されます。driver.go では、ベンチマークの実行回数 (-benchnum)、実行時間 (-benchtime)、目標RSS値 (-benchmem) などのパラメータをコマンドラインから受け取るために利用されていました。

5. runtime パッケージ

Goの標準ライブラリである runtime パッケージは、Goランタイムとの相互作用を提供します。driver.go では、runtime.GC()(ガベージコレクションの強制実行)や runtime.ReadMemStats()(メモリ統計の読み取り)が使用されており、ベンチマーク実行時のメモリ状態を制御・監視するために利用されていました。

技術的詳細

このコミットは、Goリポジトリから3つのGoソースファイルを削除しています。これらのファイルは、Goのベンチマークシステムを模倣した、あるいは拡張しようとしたと思われるカスタムのベンチマークフレームワークの一部でした。

  1. test/bench/perf/bench1.go:

    • main パッケージに属し、PerfBenchmark 関数を呼び出して SleepBenchmark を実行しています。
    • SleepBenchmark は、指定されたミリ秒数だけスリープし、PerfMetric 型のメトリクス({"foo", 42})を返すシンプルなベンチマーク関数です。これは、ベンチマークの基本的な構造を示すための例、またはテスト用のダミーベンチマークとして機能していたと考えられます。
  2. test/bench/perf/bench2.go:

    • main パッケージに属し、Benchmark という名前の関数を定義していますが、これは常に nil エラーを返すだけの非常にシンプルなものです。これもまた、ベンチマーク関数のプレースホルダー、または最小限のベンチマーク例として存在していた可能性があります。
  3. test/bench/perf/driver.go:

    • このファイルは、カスタムベンチマークフレームワークの「ドライバー」または「ハーネス」として機能していました。
    • コマンドライン引数: flag パッケージを使用して、ベンチマークの実行回数 (-benchnum)、各ベンチマークの実行時間 (-benchtime)、および目標とするRSS値 (-benchmem) を設定できるようになっていました。
    • PerfResultPerfMetric 構造体: ベンチマークの結果(実行回数 N、実行時間 RunTime、カスタムメトリクス Metrics)を格納するための構造体が定義されています。PerfMetric は、型と値を持つ汎用的なメトリクスを表現します。
    • PerfBenchmark 関数: 複数のベンチマーク実行を管理し、最も短い実行時間の結果を選択するロジックを含んでいます。
    • RunBenchmarkRunOnce 関数: ベンチマークの実際の実行ロジックをカプセル化しています。RunOnce では、runtime.GC() でガベージコレクションを強制実行し、runtime.ReadMemStats() でメモリ統計を収集するなど、ベンチマークの精度を高めるための一般的なプラクティスが取り入れられています。
    • ChooseN 関数: ベンチマークの実行回数 N を動的に調整するロジックが含まれています。これは、Goの標準ベンチマークツール (testing パッケージ) が行うような、指定された時間内に安定した結果を得るために必要な実行回数を自動的に決定するアプローチに似ています。
    • GOPERF-METRIC 出力: fmt.Printf("GOPERF-METRIC:...") という形式で標準出力にメトリクスを出力しています。これは、Goのベンチマークツールが結果をパースしやすい形式で出力するのと同様のパターンです。この形式は、自動化されたテストシステムやCI/CDパイプラインでベンチマーク結果を収集・分析する際に利用されることが一般的です。
    • ユーティリティ関数: roundUp, min, max といったヘルパー関数も含まれており、ChooseN 関数での計算に使用されていました。

これらのファイルがGoの公式リポジトリに存在していた場合、Goの標準ベンチマークシステム (testing パッケージ) とは異なる、独自のベンチマークフレームワークが導入されることになります。コミッターの意図しないコミットであったため、これらのファイルはGoリポジトリの標準的な開発プロセスやツールセットとは整合性がなく、削除されるのが適切でした。

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

このコミットのコアとなる変更は、以下の3つのファイルの削除です。

  1. test/bench/perf/bench1.go
  2. test/bench/perf/bench2.go
  3. test/bench/perf/driver.go

これらのファイルは、Goリポジトリの test/bench/perf ディレクトリから完全に削除されました。Gitの差分表示では、これらのファイルの内容がすべて削除行(- で始まる行)として表示されています。

コアとなるコードの解説

削除された各ファイルは、Goのパフォーマンスベンチマークに関連する独自のコードを含んでいました。

  • bench1.go:

    • SleepBenchmark という関数を定義しており、これは time.Sleep を使って意図的に遅延を発生させるシンプルなベンチマークです。
    • PerfBenchmark という関数を呼び出しており、これは driver.go で定義されているカスタムベンチマークドライバーの一部です。
    • このファイルは、カスタムベンチマークフレームワークを使って特定のベンチマークを実行するエントリポイントとして機能していました。
  • bench2.go:

    • Benchmark という名前の関数を定義していますが、これは中身が空で、常に nil を返すだけのプレースホルダーです。
    • おそらく、新しいベンチマークを追加する際のテンプレートとして、あるいは最小限のベンチマークの例として用意されていたと考えられます。
  • driver.go:

    • このファイルは、削除されたファイルの中で最も重要であり、カスタムベンチマークフレームワークの主要なロジックを含んでいました。
    • ベンチマーク実行の制御: コマンドライン引数 (-benchnum, -benchtime, -benchmem) を通じて、ベンチマークの実行回数、時間、メモリ使用量の目標を設定できます。
    • 結果の集計と選択: 複数のベンチマーク実行の中から最適な結果(最も短い実行時間)を選択するロジックが含まれています。
    • メモリ統計の取得: runtime.GC()runtime.ReadMemStats() を使用して、ベンチマーク実行前後のメモリ使用量を測定し、パフォーマンス分析に役立てようとしていました。
    • 動的な実行回数調整: ChooseN 関数は、目標とするベンチマーク時間に基づいて、適切な実行回数 N を動的に決定します。これにより、安定したベンチマーク結果を得るための試行回数を最適化します。
    • GOPERF-METRIC 形式での出力: ベンチマーク結果を GOPERF-METRIC:key=value という特定の形式で標準出力に出力します。これは、自動化されたシステムがベンチマーク結果をパースしやすくするための一般的な手法です。

これらのファイルは、Goの標準的なベンチマークツール (go test -bench) とは異なるアプローチでベンチマークを実行・管理しようとしていたものです。Goの公式リポジトリの文脈では、このようなカスタムツールは通常、標準ツールでカバーできない特定のニーズがある場合にのみ導入されますが、今回の場合はコミッターの誤りによるものであったため、削除されました。

関連リンク

参考にした情報源リンク