[インデックス 11208] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージ内のメモリ割り当て(malloc)テストに関する修正と改善を目的としています。具体的には、fmt_test.go
ファイル内のテストケースを調整し、特に32ビット環境での%g
フォーマット指定子使用時の予期せぬ追加のメモリ割り当てに対応しています。これにより、テストが再び合格するようになり、-short
フラグが指定された場合でもテストが実行されるように変更されました。
コミット
commit 45d739748ebec720fbf459001b480ca0b8821542
Author: Rob Pike <r@golang.org>
Date: Tue Jan 17 10:45:36 2012 -0800
fmt: enable and fix malloc test
On 32-bit machines, %g takes an extra malloc. I don't know why yet,
but this makes the test pass again, and enables it even for -short.
Fixes #2653.
R=golang-dev, bradfitz, r
CC=golang-dev
https://golang.org/cl/5542055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/45d739748ebec720fbf459001b480ca0b8821542
元コミット内容
fmt: enable and fix malloc test
On 32-bit machines, %g takes an extra malloc. I don't know why yet,
but this makes the test pass again, and enables it even for -short.
Fixes #2653.
R=golang-dev, bradfitz, r
CC=golang-dev
https://golang.org/cl/5542055
変更の背景
このコミットの主な背景は、Go言語のfmt
パッケージにおけるメモリ割り当てテストが、特定の環境(特に32ビットマシン)で失敗していた問題に対処することです。コミットメッセージによると、%g
フォーマット指定子を使用して浮動小数点数をフォーマットする際に、32ビットマシンで予期せぬ追加のメモリ割り当てが発生していました。これにより、既存のメモリ割り当てテストが期待値と異なり、失敗していたと考えられます。
また、テストが-short
フラグ(テストを短時間で実行するためのフラグ)が指定された場合にスキップされていたため、この問題が発見されにくかった可能性があります。このコミットは、テストを修正して合格させるだけでなく、-short
フラグが指定された場合でもテストが実行されるようにすることで、テストカバレッジと信頼性を向上させることを目的としています。
コミットメッセージに記載されているFixes #2653
は、この問題がGoのIssueトラッカーで報告されていたことを示唆しています。また、// TODO: should be 1. See Issue 2722.
というコメントは、%g
のメモリ割り当てが将来的にはさらに最適化されるべきであるという認識があったことを示しています。
前提知識の解説
Go言語のfmt
パッケージ
fmt
パッケージは、Go言語におけるフォーマットI/O(入力/出力)を実装するためのパッケージです。C言語のprintf
やscanf
に似た機能を提供し、文字列、数値、構造体などの様々なデータ型を整形して出力したり、文字列からデータを解析したりすることができます。
- フォーマット指定子:
%d
(整数)、%s
(文字列)、%f
(浮動小数点数)、%g
(浮動小数点数を簡潔な形式で)など、様々なフォーマット指定子があります。 Sprintf
関数: データをフォーマットして新しい文字列を生成します。Fprintf
関数: データをフォーマットして指定されたio.Writer
に書き込みます。
メモリ割り当て(malloc)
プログラムが実行時に動的にメモリを要求する操作をメモリ割り当てと呼びます。Go言語では、ガベージコレクションによってメモリ管理が自動化されていますが、内部的にはmalloc
のようなシステムコールやそれに準ずるメカニズムが使用されています。メモリ割り当ての回数や量は、プログラムのパフォーマンスに影響を与える可能性があります。特に、頻繁な小さなメモリ割り当ては、ガベージコレクションのオーバーヘッドを増加させ、パフォーマンスの低下を招くことがあります。
testing
パッケージとベンチマークテスト
Go言語の標準ライブラリには、テストを記述するためのtesting
パッケージが含まれています。
TestXxx
関数: 通常のテスト関数。BenchmarkXxx
関数: パフォーマンスを測定するためのベンチマークテスト関数。runtime.MemStats
: Goランタイムのメモリ統計情報を提供する構造体です。これには、メモリ割り当ての回数(Mallocs
)などの情報が含まれます。runtime.UpdateMemStats()
:MemStats
構造体を最新の情報に更新します。-short
フラグ:go test
コマンドに-short
フラグを付けると、時間がかかるテストをスキップすることができます。これは、開発中の迅速なテスト実行に役立ちます。
32ビットと64ビット環境
コンピュータのアーキテクチャには、32ビットと64ビットがあります。これは、CPUが一度に処理できるデータの量や、メモリのアドレス空間の大きさに影響します。32ビットシステムでは、最大約4GBのメモリしか直接アドレス指定できません。64ビットシステムでは、より大きなメモリ空間を扱えます。
この違いは、特定のデータ型(特にポインタや一部の数値型)のサイズや、メモリ管理の挙動に影響を与えることがあります。今回のコミットでは、32ビット環境で%g
フォーマットが追加のメモリ割り当てを引き起こすという、アーキテクチャ固有の挙動が問題となっていました。
技術的詳細
このコミットは、fmt
パッケージのfmt_test.go
ファイル内のmallocTest
というテストデータと、TestCountMallocs
関数のロジックを変更しています。
mallocTest
構造体の変更
元のmallocTest
構造体は、期待されるメモリ割り当て回数をcount
フィールドで保持していました。しかし、このコミットではcount
をmax
に変更し、期待されるメモリ割り当ての「最大値」を表現するようにしました。これは、%g
のケースで厳密なcount
が期待できない(または一時的に増える)状況に対応するためと考えられます。
- count int
- desc string
- fn func()
+ max int
+ desc string
+ fn func()
%g
フォーマットのメモリ割り当て数の変更
最も重要な変更点は、Sprintf("%g", 3.14159)
のテストケースにおける期待されるメモリ割り当て数を1
から2
に変更したことです。
- {1, `Sprintf("%g")`, func() { Sprintf("%g", 3.14159) }},\
+ {2, `Sprintf("%g")`, func() { Sprintf("%g", 3.14159) }}, // TODO: should be 1. See Issue 2722.
この変更は、32ビットマシンで%g
が追加のメモリ割り当てを行うという発見に基づいています。コメントにある// TODO: should be 1. See Issue 2722.
は、この2
という値が理想的ではなく、将来的には1
に最適化されるべきであるという開発者の意図を示しています。Issue 2722は、この最適化の追跡のために作成されたものと推測されます。
TestCountMallocs
関数の変更
TestCountMallocs
関数は、各テストケースで実際に発生したメモリ割り当て回数を計測し、期待値と比較する役割を担っています。
-
-short
フラグによるスキップの削除: 元のコードでは、testing.Short()
が真の場合(つまり-short
フラグが指定された場合)にテスト全体がスキップされていました。このコミットでは、この条件分岐が削除され、-short
フラグが指定されてもTestCountMallocs
が実行されるようになりました。これにより、より広範なテスト環境でメモリ割り当ての回数がチェックされるようになります。- if testing.Short() { - return - }
-
アサーションロジックの変更: メモリ割り当て回数の検証ロジックが、厳密な一致(
!= uint64(mt.count)
)から、最大値以下であること(> uint64(mt.max)
)に変更されました。これは、mallocTest
構造体のcount
フィールドがmax
に変更されたことに対応しています。これにより、テストがより柔軟になり、予期せぬ追加のメモリ割り当てが発生した場合でも、それが許容範囲内であればテストが失敗しないようになります。- if mallocs/N != uint64(mt.count) { - t.Errorf("%s: expected %d mallocs, got %d", mt.desc, mt.count, mallocs/N) + if mallocs/N > uint64(mt.max) { + t.Errorf("%s: expected at most %d mallocs, got %d", mt.desc, mt.max, mallocs/N)
これらの変更により、32ビット環境での%g
フォーマットの挙動を考慮しつつ、メモリ割り当てテストの信頼性とカバレッジが向上しました。
コアとなるコードの変更箇所
diff --git a/src/pkg/fmt/fmt_test.go b/src/pkg/fmt/fmt_test.go
index beb410fa11..d733721aff 100644
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -509,16 +509,16 @@ func BenchmarkSprintfFloat(b *testing.B) {
var mallocBuf bytes.Buffer
var mallocTest = []struct {
- count int
- desc string
- fn func()
+ max int
+ desc string
+ fn func()
}{
{0, `Sprintf("")`, func() { Sprintf("") }},\
{1, `Sprintf("xxx")`, func() { Sprintf("xxx") }},\
{1, `Sprintf("%x")`, func() { Sprintf("%x", 7) }},\
{2, `Sprintf("%s")`, func() { Sprintf("%s", "hello") }},\
{1, `Sprintf("%x %x")`, func() { Sprintf("%x %x", 7, 112) }},\
- {1, `Sprintf("%g")`, func() { Sprintf("%g", 3.14159) }},\
+ {2, `Sprintf("%g")`, func() { Sprintf("%g", 3.14159) }}, // TODO: should be 1. See Issue 2722.\
{0, `Fprintf(buf, "%x %x %x")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%x %x %x", 7, 8, 9) }},\
{1, `Fprintf(buf, "%s")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%s", "hello") }},\
}\
@@ -526,9 +526,6 @@ var _ bytes.Buffer
func TestCountMallocs(t *testing.T) {
- if testing.Short() {
- return
- }
for _, mt := range mallocTest {
const N = 100
runtime.UpdateMemStats()
@@ -538,8 +535,8 @@ func TestCountMallocs(t *testing.T) {
}
runtime.UpdateMemStats()
mallocs += runtime.MemStats.Mallocs
- if mallocs/N != uint64(mt.count) {
- t.Errorf("%s: expected %d mallocs, got %d", mt.desc, mt.count, mallocs/N)
+ if mallocs/N > uint64(mt.max) {
+ t.Errorf("%s: expected at most %d mallocs, got %d", mt.desc, mt.max, mallocs/N)
}
}
}
コアとなるコードの解説
mallocTest
構造体の変更
-
count int
からmax int
への変更: これは、テストの期待値を「正確な割り当て回数」から「最大許容割り当て回数」へと変更したことを意味します。これにより、特定の環境や状況で発生する可能性のある、許容範囲内の追加のメモリ割り当てをテストが許容できるようになります。 -
{1, Sprintf("%g") ...}
から{2, Sprintf("%g") ...}
への変更:%g
フォーマット指定子を使用した場合のメモリ割り当ての期待値が1から2に増加しました。これは、32ビット環境で%g
が追加のメモリ割り当てを行うという発見に対応するためのものです。コメント// TODO: should be 1. See Issue 2722.
は、この2という値が一時的なものであり、将来的には1に最適化されるべきであるという開発者の意図を示しています。Issue 2722は、この最適化の課題を追跡するためのものと推測されます。
TestCountMallocs
関数の変更
-
if testing.Short() { return }
の削除: この行が削除されたことにより、TestCountMallocs
関数は-short
フラグが指定された場合でも実行されるようになりました。これにより、メモリ割り当てテストが常に実行され、より早期に問題が検出される可能性が高まります。 -
if mallocs/N != uint64(mt.count)
からif mallocs/N > uint64(mt.max)
への変更: テストのアサーションロジックが変更されました。以前は、計測されたメモリ割り当て回数が期待値と厳密に一致しない場合にエラーとなっていましたが、変更後は、計測された回数がmt.max
(最大許容割り当て回数)を超えた場合にのみエラーとなります。この変更は、mallocTest
構造体のcount
フィールドがmax
に変更されたことと連動しており、テストの柔軟性を高め、許容範囲内の変動を許容するようにしています。
これらの変更は、Go言語のfmt
パッケージの堅牢性を高め、特に異なるアーキテクチャでの挙動の差異を考慮に入れたテストの改善に貢献しています。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
- Go言語の
fmt
パッケージドキュメント: https://pkg.go.dev/fmt - Go言語の
testing
パッケージドキュメント: https://pkg.go.dev/testing - Go言語の
runtime
パッケージドキュメント: https://pkg.go.dev/runtime
(注: コミットメッセージに記載されているIssue #2653およびIssue #2722は、Goの古いIssueトラッカーのものであり、現在のGitHub上のIssueとは直接リンクしていません。そのため、正確なリンクを特定することは困難です。)
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/fmt/fmt_test.go
) - Go言語のコミット履歴
- Go言語のIssueトラッカー(過去の情報を参照)