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

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

このコミットは、Go言語の標準ライブラリのベンチマークスイートに、fmtパッケージ(printfスタイルのフォーマット)とtimeパッケージ(時刻の解析とフォーマット)に関する新しいベンチマークテストを追加し、既存のパーサーベンチマークの名称を変更するものです。これにより、Go言語のパフォーマンス特性をより詳細に測定し、将来の最適化のための基盤を強化することを目的としています。

コミット

commit 05403fa8fbfe1c6e44e1b015a6e990af11c81dfd
Author: Rob Pike <r@golang.org>
Date:   Mon Mar 11 17:17:25 2013 -0700

    go/test/bench/go1: add printf and time format tests
    Also rename the go parser test to GoParse so it doesn't grab the globally useful Parse name.
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/7732044

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

https://github.com/golang/go/commit/05403fa8fbfe1c6e44e1b015a6e990af11c81dfd

元コミット内容

go/test/bench/go1: add printf and time format tests Also rename the go parser test to GoParse so it doesn't grab the globally useful Parse name.

このコミットは、fmtパッケージとtimeパッケージのベンチマークテストを追加し、Goパーサーのテスト名をGoParseに変更して、グローバルに有用なParseという名前を占有しないようにします。

変更の背景

このコミットの背景には、主に以下の2つの目的があります。

  1. パフォーマンス測定の拡充: Go言語の標準ライブラリ、特にfmtパッケージとtimeパッケージは、多くのGoアプリケーションで頻繁に使用されます。これらのパッケージのパフォーマンスは、アプリケーション全体の速度に大きな影響を与える可能性があります。既存のベンチマークスイートにこれらの機能のテストを追加することで、開発者はこれらのパッケージのパフォーマンス特性をより詳細に把握し、将来のバージョンでの最適化の機会を特定できるようになります。特に、fmt.FprintfのようなI/O関連の操作は、バッファリングやフォーマット処理の効率が重要であり、様々なシナリオでのベンチマークは非常に価値があります。
  2. 命名規則の改善と衝突回避: Go言語のテストフレームワークでは、Benchmarkプレフィックスを持つ関数がベンチマークとして認識されます。既存のパーサーベンチマーク関数がBenchmarkParseという汎用的な名前を持っていたため、他のパッケージやテストファイルでParseという名前が使われている場合に、意図しない名前の衝突や混乱を招く可能性がありました。これをBenchmarkGoParseにリネームすることで、Goパーサーに特化したベンチマークであることが明確になり、名前空間の衝突を回避し、コードの可読性と保守性を向上させます。

これらの変更は、Go言語の継続的な品質向上とパフォーマンス最適化の取り組みの一環として行われました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とパッケージに関する知識が必要です。

  • Go言語のベンチマークテスト: Go言語には、標準ライブラリのtestingパッケージにベンチマークテストの機能が組み込まれています。testing.B型の引数を受け取るBenchmarkプレフィックスの関数を定義することで、コードのパフォーマンスを測定できます。b.Nはテストの繰り返し回数を示し、b.ResetTimer()b.StopTimer()b.SetBytes()などのメソッドを使って、より正確な測定を行うことができます。ベンチマークはgo test -bench .コマンドで実行されます。
  • fmtパッケージ: fmtパッケージは、Go言語におけるフォーマットI/Oを実装します。C言語のprintfscanfに似た機能を提供し、文字列、数値、構造体などを整形して出力したり、入力から解析したりすることができます。特にfmt.Fprintf関数は、指定されたio.Writer(この場合はbytes.Buffer)にフォーマットされた文字列を書き込むために使用されます。
  • timeパッケージ: timeパッケージは、時刻の表現、測定、表示に関する機能を提供します。time.Parse関数は、指定されたレイアウトと文字列からtime.Timeオブジェクトを解析し、time.Format関数はtime.Timeオブジェクトを指定されたレイアウトで文字列にフォーマットします。
  • bytes.Buffer: bytesパッケージのBuffer型は、可変長のバイトバッファを実装します。これは、文字列の構築やI/O操作のバッファとして非常に効率的です。fmt.Fprintfのような関数で出力先として使用されることが多く、実際のファイルI/Oを伴わないメモリ上での高速な操作が可能です。ベンチマークでは、実際のI/Oのオーバーヘッドを排除し、フォーマット処理自体のパフォーマンスを測定するためにbytes.Bufferがよく利用されます。
  • go/parserパッケージ: go/parserパッケージは、Go言語のソースコードを解析し、抽象構文木(AST)を構築するための機能を提供します。このパッケージは、Goのツールチェインや静的解析ツールなどで利用されます。

技術的詳細

このコミットは、Go言語のベンチマークスイートに具体的なパフォーマンス測定のシナリオを追加しています。

fmtパッケージのベンチマーク (fmt_test.go)

fmt_test.goでは、fmt.Fprintf関数の様々な使用パターンにおけるパフォーマンスを測定するためのベンチマークが追加されています。fmt.Fprintfは、フォーマットされたデータをio.Writerに書き込む関数であり、ここではbytes.Bufferを書き込み先として使用しています。これにより、実際のディスクI/OやネットワークI/Oのオーバーヘッドを除外し、フォーマット処理自体の効率を評価できます。

  • BenchmarkFmtFprintfEmpty: 空の文字列をフォーマットする場合のベンチマーク。最も基本的なケースで、フォーマットエンジンの最小限のオーバーヘッドを測定します。
  • BenchmarkFmtFprintfString: 単一の文字列をフォーマットする場合のベンチマーク。%sフォーマット指定子を使用します。
  • BenchmarkFmtFprintfInt: 単一の整数をフォーマットする場合のベンチマーク。%dフォーマット指定子を使用します。
  • BenchmarkFmtFprintfIntInt: 2つの整数をフォーマットする場合のベンチマーク。複数の引数とフォーマット指定子を処理する能力を評価します。
  • BenchmarkFmtFprintfPrefixedInt: プレフィックス付きのテキストと整数をフォーマットする場合のベンチマーク。フォーマット文字列中にリテラルテキストが含まれる場合の処理効率を測定します。これは、フォーマットエンジンがフォーマット指定子をスキャンする際のコストを反映します。
  • BenchmarkFmtFprintfFloat: 浮動小数点数をフォーマットする場合のベンチマーク。%gフォーマット指定子を使用します。浮動小数点数のフォーマットは、整数のそれよりも複雑な処理を伴うため、そのパフォーマンス特性を評価します。
  • BenchmarkFmtManyArgs: 多数の異なる型の引数(整数、文字列)を複雑なフォーマット文字列でフォーマットする場合のベンチマーク。これは、実際のアプリケーションでよく見られるような、より複雑なフォーマットシナリオをシミュレートします。

これらのベンチマークは、fmt.Fprintfが様々な入力とフォーマット指定子に対してどれだけ効率的に動作するかを詳細に分析するための基盤を提供します。

timeパッケージのベンチマーク (time_test.go)

time_test.goでは、timeパッケージの主要な機能である時刻の解析とフォーマットに関するベンチマークが追加されています。

  • BenchmarkTimeParse: time.Parse関数を使用して、標準的な時刻文字列(time.ANSICレイアウト)をtime.Timeオブジェクトに解析するパフォーマンスを測定します。時刻文字列の解析は、文字列処理と日付/時刻の変換ロジックを含むため、その効率は重要です。
  • BenchmarkTimeFormat: time.Format関数を使用して、time.Timeオブジェクトを特定のレイアウト("Mon Jan 2 15:04:05 2006")で文字列にフォーマットするパフォーマンスを測定します。時刻のフォーマットも、数値から文字列への変換や、曜日/月名のルックアップなどを含むため、その効率が評価されます。

これらのベンチマークは、Goアプリケーションで頻繁に行われる時刻のシリアライズ/デシリアライズ操作のパフォーマンス特性を理解するのに役立ちます。

パーサーベンチマークのリネーム (parser_test.go)

parser_test.goでは、既存のBenchmarkParse関数がBenchmarkGoParseにリネームされています。この変更は、機能的なパフォーマンス測定には影響を与えませんが、命名規則の観点から重要です。Goのテストフレームワークでは、Benchmarkプレフィックスを持つ関数が自動的にベンチマークとして認識されます。Parseという名前は非常に一般的であり、他のGoパッケージ(例: strconv.ParseInt, url.Parseなど)でも広く使用されています。BenchmarkParseという名前は、Goパーサーに特化したベンチマークであるにもかかわらず、その汎用性から誤解を招く可能性がありました。BenchmarkGoParseとすることで、このベンチマークがGo言語のパーサーに特化したものであることが明確になり、名前の衝突を避け、コードベース全体の整合性を高めます。

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

test/bench/go1/fmt_test.go (新規ファイル)

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package go1

// benchmark based on fmt/fmt_test.go

import (
	"bytes"
	"fmt"
	"testing"
)

func BenchmarkFmtFprintfEmpty(b *testing.B) {
	var buf bytes.Buffer
	for i := 0; i < b.N; i++ {
		fmt.Fprintf(&buf, "")
	}
}

func BenchmarkFmtFprintfString(b *testing.B) {
	var buf bytes.Buffer
	for i := 0; i < b.N; i++ {
		buf.Reset()
		fmt.Fprintf(&buf, "%s", "hello")
	}
}

func BenchmarkFmtFprintfInt(b *testing.B) {
	var buf bytes.Buffer
	for i := 0; i < b.N; i++ {
		buf.Reset()
		fmt.Fprintf(&buf, "%d", 5)
	}
}

func BenchmarkFmtFprintfIntInt(b *testing.B) {
	var buf bytes.Buffer
	for i := 0; i < b.N; i++ {
		buf.Reset()
		fmt.Fprintf(&buf, "%d %d", 5, 6)
	}
}

func BenchmarkFmtFprintfPrefixedInt(b *testing.B) {
	var buf bytes.Buffer
	for i := 0; i < b.N; i++ {
		buf.Reset()
		fmt.Fprintf(&buf, "This is some meaningless prefix text that needs to be scanned %d", 6)
	}
}

func BenchmarkFmtFprintfFloat(b *testing.B) {
	var buf bytes.Buffer
	for i := 0; i < b.N; i++ {
		buf.Reset()
		fmt.Fprintf(&buf, "%g", 5.23184)
	}
}

func BenchmarkFmtManyArgs(b *testing.B) {
	var buf bytes.Buffer
	for i := 0; i < b.N; i++ {
		buf.Reset()
		fmt.Fprintf(&buf, "%2d/%2d/%2d %d:%d:%d %s %s\n", 3, 4, 5, 11, 12, 13, "hello", "world")
	}
}

test/bench/go1/parser_test.go (変更箇所)

--- a/test/bench/go1/parser_test.go
+++ b/test/bench/go1/parser_test.go
@@ -33,7 +33,7 @@ func makeParserBytes() []byte {
 	return b
 }
 
-func BenchmarkParse(b *testing.B) {
+func BenchmarkGoParse(b *testing.B) {
 	b.SetBytes(int64(len(parserbytes)))
 	for i := 0; i < b.N; i++ {
 		if _, err := parser.ParseFile(token.NewFileSet(), "", parserbytes, parser.ParseComments); err != nil {

test/bench/go1/time_test.go (新規ファイル)

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package go1

// benchmark based on time/time_test.go

import (
	"testing"
	"time"
)

func BenchmarkTimeParse(b *testing.B) {
	for i := 0; i < b.N; i++ {
		time.Parse(time.ANSIC, "Mon Jan  2 15:04:05 2006")
	}
}

func BenchmarkTimeFormat(b *testing.B) {
	t := time.Unix(1265346057, 0)
	for i := 0; i < b.N; i++ {
		t.Format("Mon Jan  2 15:04:05 2006")
	}
}

コアとなるコードの解説

fmt_test.go の新規ベンチマーク

これらのベンチマーク関数はすべて、testing.B型の引数bを受け取り、b.N回ループを実行して、その間の処理時間を測定します。各ベンチマークは、bytes.Bufferfmt.Fprintfの出力先として使用し、buf.Reset()を呼び出して各イテレーションでバッファをクリアしています。これにより、前のイテレーションのデータが次のイテレーションの測定に影響を与えることを防ぎ、フォーマット処理自体の純粋なパフォーマンスを測定できます。

  • BenchmarkFmtFprintfEmpty, BenchmarkFmtFprintfString, BenchmarkFmtFprintfInt, BenchmarkFmtFprintfIntInt, BenchmarkFmtFprintfPrefixedInt, BenchmarkFmtFprintfFloat, BenchmarkFmtManyArgs: これらは、fmt.Fprintfが様々な引数の型、数、およびフォーマット文字列の複雑さに対してどれだけ効率的に動作するかを評価します。例えば、BenchmarkFmtManyArgsは、複数の異なる型の引数を含む複雑なフォーマット文字列の処理コストを測定し、実際のアプリケーションでの使用パターンをよりよく反映します。

parser_test.go の変更

BenchmarkParse関数がBenchmarkGoParseにリネームされました。

  • func BenchmarkGoParse(b *testing.B): この変更は、Goパーサーのベンチマーク関数名をより具体的にし、Parseという一般的な名前が他のコンテキストで誤って参照されることを防ぎます。機能的な変更はなく、ベンチマークの測定対象や方法は以前と同じです。b.SetBytes(int64(len(parserbytes)))は、ベンチマークの実行ごとに処理されるバイト数を設定し、結果の出力に「ops/s」(1秒あたりの操作数)だけでなく「MB/s」(1秒あたりの処理メガバイト数)も表示されるようにします。

time_test.go の新規ベンチマーク

  • BenchmarkTimeParse(b *testing.B): このベンチマークは、time.Parse関数が特定のフォーマットの文字列をtime.Timeオブジェクトに解析する速度を測定します。ループ内でtime.Parseを繰り返し呼び出すことで、解析処理の平均的なパフォーマンスを評価します。
  • BenchmarkTimeFormat(b *testing.B): このベンチマークは、time.Timeオブジェクトを特定のフォーマットの文字列に変換するt.Format関数の速度を測定します。time.Unix(1265346057, 0)で固定のtime.Timeオブジェクトを作成し、そのフォーマット処理の効率を評価します。

これらの新しいベンチマークは、Go言語の標準ライブラリの重要な部分のパフォーマンス特性を継続的に監視し、将来の最適化のためのデータを提供するために不可欠です。

関連リンク

参考にした情報源リンク