[インデックス 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言語のコンパイラとランタイムに関する一般的な知識