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

[インデックス 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言語のprintfscanfに似た機能を提供し、文字列、数値、構造体などの様々なデータ型を整形して出力したり、文字列からデータを解析したりすることができます。

  • フォーマット指定子: %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フィールドで保持していました。しかし、このコミットではcountmaxに変更し、期待されるメモリ割り当ての「最大値」を表現するようにしました。これは、%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関数は、各テストケースで実際に発生したメモリ割り当て回数を計測し、期待値と比較する役割を担っています。

  1. -shortフラグによるスキップの削除: 元のコードでは、testing.Short()が真の場合(つまり-shortフラグが指定された場合)にテスト全体がスキップされていました。このコミットでは、この条件分岐が削除され、-shortフラグが指定されてもTestCountMallocsが実行されるようになりました。これにより、より広範なテスト環境でメモリ割り当ての回数がチェックされるようになります。

    -	if testing.Short() {
    -		return
    -	}
    
  2. アサーションロジックの変更: メモリ割り当て回数の検証ロジックが、厳密な一致(!= 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パッケージの堅牢性を高め、特に異なるアーキテクチャでの挙動の差異を考慮に入れたテストの改善に貢献しています。

関連リンク

(注: コミットメッセージに記載されているIssue #2653およびIssue #2722は、Goの古いIssueトラッカーのものであり、現在のGitHub上のIssueとは直接リンクしていません。そのため、正確なリンクを特定することは困難です。)

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/pkg/fmt/fmt_test.go
  • Go言語のコミット履歴
  • Go言語のIssueトラッカー(過去の情報を参照)