[インデックス 18219] ファイルの概要
このコミットは、Go言語の標準ライブラリdatabase/sqlパッケージ内のテストファイルconvert_test.goに対する修正です。具体的には、TestRawBytesAllocsというテスト関数が32-bit環境で失敗する問題を修正しています。
コミット
commit 5f341df90d0c156e4e8af3fc6e6de336faa64aa5
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Jan 10 12:30:23 2014 -0800
database/sql: fix test on 32-bit
R=golang-codereviews
TBR=golang-dev
CC=golang-codereviews
https://golang.org/cl/49920047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5f341df90d0c156e4e8af3fc6e6de336faa64aa5
元コミット内容
このコミットは、database/sqlパッケージのconvert_test.goファイルにおいて、TestRawBytesAllocsテストが32-bit環境で正しく動作しない問題を修正するものです。テストは、特定の条件下でのメモリ割り当て(アロケーション)の数を検証していますが、このアロケーションの挙動が32-bitアーキテクチャやgc以外のコンパイラ(例: gccgo)では異なるため、テストが失敗していました。
変更の背景
Go言語のテストスイートは、様々なアーキテクチャやコンパイラ環境で実行されます。database/sqlパッケージのTestRawBytesAllocsテストは、testing.AllocsPerRun関数を使用して、特定の操作が引き起こすメモリ割り当ての回数を計測していました。しかし、このテストは64-bitアーキテクチャ(特にamd64)とGoの標準コンパイラであるgcを前提としたアロケーション数でアサートを行っていました。
32-bitシステムでは、ポインタやインターフェースの内部表現におけるワードサイズが64-bitシステムとは異なります。これにより、インターフェースへの値の変換(convT2E)など、特定のアロケーションパターンが変化することがあります。また、gccgoのような代替コンパイラも、gcとは異なる最適化やアロケーション戦略を持つ可能性があります。
この差異が原因で、32-bit環境やgccgoコンパイラでTestRawBytesAllocsを実行すると、期待されるアロケーション数と実際の数が一致せず、テストが失敗するという問題が発生していました。このコミットは、テストの堅牢性を高め、異なる環境での誤った失敗を防ぐために導入されました。
前提知識の解説
1. Go言語のdatabase/sqlパッケージ
database/sqlパッケージは、GoプログラムからSQLデータベースにアクセスするための汎用的なインターフェースを提供します。このパッケージ自体はデータベースドライバーを含まず、ドライバーは別途インポートして利用します。database/sqlは、接続プール、トランザクション管理、プリペアドステートメントなどの機能を提供し、データベース操作を抽象化します。
2. testing.AllocsPerRun関数
testing.AllocsPerRunは、Goの標準テストパッケージtestingに含まれる関数です。この関数は、指定された関数(ベンチマーク関数など)を複数回実行し、その実行中に発生したメモリ割り当て(アロケーション)の平均回数を計測します。これは、コードのパフォーマンス特性、特にメモリ使用効率を評価する際に非常に有用です。
3. Goのインターフェースとメモリ割り当て
Goのインターフェースは、内部的に「型情報」と「値」の2つのワードで構成されます。
- 型情報 (Type Word): インターフェースが保持している具体的な値の型を記述します。
- 値 (Value Word): インターフェースが保持している具体的な値そのもの、またはその値へのポインタです。
値がポインタサイズよりも小さい場合(例: int、bool、小さな構造体など)、その値は直接「値ワード」に格納されます。しかし、値がポインタサイズよりも大きい場合(例: 大きな構造体、文字列、スライスなど)、その値はヒープに割り当てられ、「値ワード」にはそのヒープ上の値へのポインタが格納されます。このヒープ割り当てが「アロケーション」としてカウントされます。
32-bitシステムと64-bitシステムでは、ポインタのサイズ(ワードサイズ)が異なります。32-bitシステムではポインタが4バイト、64-bitシステムでは8バイトです。この違いが、インターフェースへの値の格納方法や、それに伴うアロケーションの挙動に影響を与えることがあります。例えば、ある型が32-bitシステムでは直接値ワードに収まるが、64-bitシステムでは収まらずヒープ割り当てが必要になる、あるいはその逆のケースも考えられます。
4. Goのコンパイラ (gc vs gccgo)
Go言語には主に2つの公式コンパイラがあります。
gc(Go Compiler): Goプロジェクトによって開発された標準のコンパイラです。Goのツールチェインに同梱されており、最も一般的に使用されます。gccgo: GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。gcとは異なる最適化戦略やランタイムの挙動を持つことがあります。
これらのコンパイラは、コードのコンパイル方法や、特にメモリ割り当ての最適化において異なるアプローチを取るため、testing.AllocsPerRunのような低レベルな計測では結果に差異が生じることがあります。
技術的詳細
このコミットの核心は、TestRawBytesAllocsテストにおけるアロケーション計測の条件付けです。元のテストでは、testing.AllocsPerRunによって計測されたアロケーション数が、特定の閾値(例: n > 0.5やn > 1.5)を超えないことを期待していました。しかし、この期待値は64-bitアーキテクチャ(amd64)と標準のgcコンパイラに特有のものでした。
コミットでは、以下の変更が加えられました。
runtimeパッケージのインポート:runtimeパッケージは、Goプログラムのランタイム環境に関する情報(例: OS、アーキテクチャ、コンパイラ)を提供する関数を含んでいます。measureAllocs変数の導入:
この行は、現在の実行環境がmeasureAllocs := runtime.GOARCH == "amd64" && runtime.Compiler == "gc"amd64アーキテクチャであり、かつ使用されているコンパイラがgcである場合にのみmeasureAllocsをtrueに設定します。それ以外の環境(例: 32-bitアーキテクチャ、gccgoコンパイラなど)ではfalseになります。- アロケーションチェックの条件化:
元の
t.Fatalfによるアロケーション数のチェックは、measureAllocsがtrueの場合にのみ実行されるように変更されました。
これにより、テストは// 変更前 // if n > 0.5 { // 変更後 if n > 0.5 && measureAllocs {amd64とgcの組み合わせでのみ厳密なアロケーション数を検証し、他の環境ではこのチェックをスキップするようになります。
この修正は、テストが特定の環境に依存する挙動を許容し、より広範な環境でのテストの安定性を確保するためのプラクティカルなアプローチです。アロケーションの挙動は、コンパイラのバージョン、最適化レベル、アーキテクチャ、さらにはGoのバージョンアップによっても変化する可能性があるため、このような環境依存のテストは慎重に扱う必要があります。
コアとなるコードの変更箇所
変更はsrc/pkg/database/sql/convert_test.goファイルに集中しています。
--- a/src/pkg/database/sql/convert_test.go
+++ b/src/pkg/database/sql/convert_test.go
@@ -8,6 +8,7 @@ import (
"database/sql/driver"
"fmt"
"reflect"
+ "runtime" // runtimeパッケージのインポート
"testing"
"time"
)
@@ -315,7 +316,14 @@ func TestRawBytesAllocs(t *testing.T) {
test("float64", float64(64), "64")
test("bool", false, "false")
})
- if n > 0.5 { // 変更前のアロケーションチェック
+
+ // The numbers below are only valid for 64-bit interface word sizes,
+ // and gc. With 32-bit words there are more convT2E allocs, and
+ // with gccgo, only pointers currently go in interface data.
+ // So only care on amd64 gc for now.
+ measureAllocs := runtime.GOARCH == "amd64" && runtime.Compiler == "gc" // measureAllocs変数の導入
+
+ if n > 0.5 && measureAllocs { // アロケーションチェックの条件化
t.Fatalf("allocs = %v; want 0", n)
}
@@ -323,7 +331,7 @@ func TestRawBytesAllocs(t *testing.T) {
n = testing.AllocsPerRun(100, func() {
test("string", "foo", "foo")
})
- if n > 1.5 { // 変更前のアロケーションチェック
+ if n > 1.5 && measureAllocs { // アロケーションチェックの条件化
t.Fatalf("allocs = %v; want max 1", n)
}
}
コアとなるコードの解説
このコミットの主要な変更点は、TestRawBytesAllocs関数内でのメモリ割り当てテストの実行条件を限定したことです。
-
import "runtime": Goのランタイム情報にアクセスするためにruntimeパッケージが追加されました。これにより、現在の実行環境のアーキテクチャ(runtime.GOARCH)と使用されているGoコンパイラ(runtime.Compiler)をプログラムで取得できるようになります。 -
measureAllocs変数の定義:measureAllocs := runtime.GOARCH == "amd64" && runtime.Compiler == "gc"このブール変数は、テストがアロケーション数を厳密にチェックすべきかどうかを決定します。
runtime.GOARCHが"amd64"(64-bit Intel/AMDアーキテクチャ)であり、かつruntime.Compilerが"gc"(標準のGoコンパイラ)である場合にのみtrueとなります。これは、元のテストが意図したアロケーション挙動が保証される環境を明示的に指定しています。 -
アロケーションチェックの条件化:
if n > 0.5 && measureAllocs { t.Fatalf("allocs = %v; want 0", n) } // ... if n > 1.5 && measureAllocs { t.Fatalf("allocs = %v; want max 1", n) }testing.AllocsPerRunによって計測されたアロケーション数nが期待値を超えた場合にテストを失敗させるt.Fatalf呼び出しが、measureAllocsがtrueである場合にのみ実行されるように変更されました。 これにより、32-bitシステムやgccgoコンパイラなど、amd64とgc以外の環境では、アロケーション数の厳密なチェックがスキップされ、テストが不必要に失敗することがなくなります。テストは引き続き実行されますが、アロケーションに関するアサートは特定の環境でのみ有効になります。
この修正は、Goのクロスプラットフォーム対応と、異なるコンパイラ実装の存在を考慮した、堅牢なテストプラクティスの一例と言えます。
関連リンク
- Go
database/sqlパッケージのドキュメント: https://pkg.go.dev/database/sql - Go
testingパッケージのドキュメント: https://pkg.go.dev/testing - Go
runtimeパッケージのドキュメント: https://pkg.go.dev/runtime - Goのインターフェースに関するブログ記事 (例: The Laws of Reflection): https://go.dev/blog/laws-of-reflection (インターフェースの内部構造について理解を深めるのに役立ちます)
参考にした情報源リンク
- コミットのGerritレビューページ: https://golang.org/cl/49920047
- Go言語の公式ドキュメント
- Go言語のソースコード
- Go言語のコンパイラとランタイムに関する一般的な知識