[インデックス 10567] ファイルの概要
このコミットは、Go言語のgofix
ツールにhash.Sum
関数の修正を追加するものです。具体的には、hash.Sum
の呼び出しにnil
引数を追加することで、将来的なメモリ割り当ての最適化を可能にするための変更です。
コミット
gofix
: hash.Sum
に対する修正を追加。
この修正は、hash.Sum
に出力引数を追加します。
ツリーの変更は https://golang.org/cl/5448065 にあります。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2308aefc845d16c44882cff5590903b74aab42bf
元コミット内容
commit 2308aefc845d16c44882cff5590903b74aab42bf
Author: Adam Langley <agl@golang.org>
Date: Thu Dec 1 12:25:09 2011 -0500
gofix: add a fix for hash.Sum.
This fix adds an output argument to hash.Sum.
Tree changes in https://golang.org/cl/5448065
R=rsc
CC=golang-dev
https://golang.org/cl/5450051
変更の背景
このコミットの背景には、Go言語の標準ライブラリにおけるハッシュ関数の設計変更があります。特に、hash.Hash
インターフェースのSum
メソッドのシグネチャが変更され、既存のコードベースを新しいAPIに適合させる必要がありました。
元のhash.Sum()
メソッドは、ハッシュ値のバイトスライスを新しく割り当てて返していました。しかし、パフォーマンス最適化の観点から、呼び出し元が既存のバイトスライスを再利用できるように、Sum
メソッドに引数としてバイトスライスを渡せるように変更されました。これにより、不要なメモリ割り当てを削減し、ガベージコレクションの負荷を軽減することが可能になります。
この変更は、Go言語の進化における一般的なパターンの一部であり、APIの改善やパフォーマンスの最適化のために、既存のコードを自動的に更新するgofix
のようなツールが不可欠となります。このコミットは、そのgofix
ツールに、このhash.Sum
のAPI変更に対応するための自動修正機能を追加するものです。
前提知識の解説
gofix
ツール
gofix
は、Go言語のコードベースを新しいAPIや言語の変更に合わせて自動的に書き換えるためのコマンドラインツールです。Go言語は後方互換性を非常に重視していますが、言語や標準ライブラリの進化に伴い、ごく稀にAPIの変更や非推奨化が行われることがあります。このような場合、開発者が手動でコードを修正するのは非常に手間がかかるため、gofix
がその作業を自動化します。
gofix
は、Goのソースコードを抽象構文木(AST: Abstract Syntax Tree)として解析し、定義された「修正(fix)」ルールに基づいてASTを変換し、修正されたコードを再生成します。これにより、大規模なコードベースでも効率的にAPIの移行を行うことができます。
Go言語のhash
パッケージとSum
メソッド
Go言語の標準ライブラリには、暗号学的ハッシュ関数やチェックサム関数を提供するcrypto
パッケージ群(例: crypto/sha256
, crypto/md5
)や、hash
パッケージがあります。
hash.Hash
インターフェースは、すべてのハッシュ関数が実装すべき共通のインターフェースを定義しています。このインターフェースには、以下の主要なメソッドが含まれます。
Write(p []byte) (n int, err error)
: ハッシュ計算のためにデータを書き込みます。Sum(b []byte) []byte
: 現在のハッシュ値を計算し、b
に追記して返します。b
がnil
の場合、新しいバイトスライスを割り当てて返します。Reset()
: ハッシュの状態を初期化します。Size() int
: ハッシュ値のバイト長を返します。BlockSize() int
: ハッシュ関数のブロックサイズを返します。
このコミットで修正の対象となっているのはSum
メソッドです。元々は引数なしのSum()
でしたが、この変更によりSum([]byte)
という形で引数を取れるようになりました。これにより、呼び出し元が事前に確保したバイトスライスを渡すことで、メモリ割り当てを避けることが可能になります。
nil
とメモリ割り当て
Go言語におけるnil
は、ポインタ、スライス、マップ、チャネル、インターフェースなどのゼロ値を表します。スライスの場合、nil
スライスは長さも容量も0であり、基底配列を持たない状態を指します。
メモリ割り当て(アロケーション)は、プログラムが実行時にメモリを要求する操作です。Goでは、新しいスライスやマップ、構造体などを生成する際にメモリ割り当てが発生します。メモリ割り当ては、特に頻繁に行われる場合、パフォーマンスに影響を与える可能性があります。なぜなら、メモリ割り当てにはCPU時間が必要であり、また、割り当てられたメモリが不要になった際にはガベージコレクタがそのメモリを解放する必要があるためです。ガベージコレクションは、プログラムの実行を一時停止させる可能性があり、レイテンシに影響を与えることがあります。
hash.Sum(nil)
のようにnil
を渡すことで、Sum
メソッドの内部で新しいバイトスライスを割り当てるのではなく、既存の(または将来的に渡される)バッファにハッシュ値を書き込むことができるようになり、メモリ割り当てのオーバーヘッドを削減できるというメリットがあります。
技術的詳細
このコミットは、gofix
ツールがhash.Sum()
の呼び出しをhash.Sum(nil)
に自動的に書き換えるためのロジックを追加します。
gofix
の拡張:src/cmd/gofix/Makefile
にhashsum.go
が追加され、ビルドプロセスに組み込まれます。これにより、gofix
コマンドが新しい修正ルールを認識するようになります。- 新しい修正ルールの定義:
src/cmd/gofix/hashsum.go
が新規作成され、hashSumFix
というfix
構造体が定義されます。この構造体は、修正の名前("hashsum"
)、適用日("2011-11-30"
)、修正ロジックを実装する関数(hashSumFn
)、および修正の説明を含みます。 - 型チェック設定:
hashSumTypeConfig
というTypeConfig
構造体が定義されています。これは、gofix
の型チェッカーが、crypto
パッケージ内の様々なハッシュ定数(例:crypto.MD5
)や、adler32
,crc32
,fnv
,hmac
,md4
,md5
,ripemd160
,sha1
,sha256
,sha512
などのパッケージから返されるhash.Hash
インターフェースを正しく認識するための情報を提供します。これにより、gofix
はhash.Hash
型のインスタンスに対してSum()
メソッドが呼び出されているかを正確に判断できます。 - AST変換ロジック:
hashSumFn
関数が、実際のAST変換ロジックを実装しています。- この関数は、Goのソースファイル(
*ast.File
)を受け取ります。 typecheck
関数を使用して、ファイルの型情報を取得します。walk
関数を使ってASTを走査します。- 各ノードが
ast.CallExpr
(関数呼び出し)であり、かつ引数が0個である場合をチェックします。 - その関数呼び出しが
ast.SelectorExpr
(セレクタ式、例:h.Sum
)であるかをチェックします。 - セレクタの選択された名前が
"Sum"
であり、かつセレクタのレシーバ(例:h
)の型が"hash.Hash"
であるかを、型情報(typeof[sel.X] == "hash.Hash"
)を用いて厳密に確認します。 - これらの条件がすべて満たされた場合、
call.Args
にast.NewIdent("nil")
を追加し、Sum()
呼び出しをSum(nil)
に書き換えます。 - 修正が行われた場合、
fixed
フラグをtrue
に設定し、最終的にその値を返します。
- この関数は、Goのソースファイル(
この一連のプロセスにより、開発者は手動でコードを修正することなく、既存のhash.Sum()
の呼び出しを新しいAPIに自動的に移行できるようになります。
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
-
src/cmd/gofix/Makefile
:hashsum.go
がGOFILES
リストに追加され、gofix
ツールのビルド対象となります。
--- a/src/cmd/gofix/Makefile +++ b/src/cmd/gofix/Makefile @@ -10,6 +10,7 @@ GOFILES=\ filepath.go\ fix.go\ go1pkgrename.go\ +\thashsum.go\ htmlerr.go\ httpfinalurl.go\ httpfs.go\
-
src/cmd/gofix/hashsum.go
: (新規ファイル)hash.Sum
の修正ロジックを実装するGoファイルです。hashSumFix
構造体で修正ルールを定義し、hashSumFn
関数でASTを走査し、hash.Hash
型のSum()
メソッド呼び出しにnil
引数を追加します。
// Copyright 2011 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 main import ( "go/ast" ) func init() { register(hashSumFix) } var hashSumFix = fix{ "hashsum", "2011-11-30", hashSumFn, `Pass a nil argument to calls to hash.Sum This fix rewrites code so that it passes a nil argument to hash.Sum. The additional argument will allow callers to avoid an allocation in the future. http://codereview.appspot.com/5448065 `, } // Type-checking configuration: tell the type-checker this basic // information about types, functions, and variables in external packages. var hashSumTypeConfig = &TypeConfig{ Var: map[string]string{ "crypto.MD4": "crypto.Hash", "crypto.MD5": "crypto.Hash", // ... (他のハッシュ定数) }, Func: map[string]string{ "adler32.New": "hash.Hash", // ... (他のハッシュ生成関数) }, Type: map[string]*Type{ "crypto.Hash": &Type{ Method: map[string]string{ "New": "func() hash.Hash", }, }, }, } func hashSumFn(f *ast.File) bool { typeof, _ := typecheck(hashSumTypeConfig, f) fixed := false walk(f, func(n interface{}) { call, ok := n.(*ast.CallExpr) if ok && len(call.Args) == 0 { // 引数がない関数呼び出しをチェック sel, ok := call.Fun.(*ast.SelectorExpr) if ok && sel.Sel.Name == "Sum" && typeof[sel.X] == "hash.Hash" { // `Sum`メソッドかつ`hash.Hash`型の場合 call.Args = append(call.Args, ast.NewIdent("nil")) // `nil`引数を追加 fixed = true } } }) return fixed }
-
src/cmd/gofix/hashsum_test.go
: (新規ファイル)hashsum.go
で実装された修正ロジックのテストケースです。- 様々な
hash.Sum()
の呼び出しパターン(sha256.New().Sum()
,h.Sum()
など)に対して、正しくSum(nil)
に変換されることを確認します。
// Copyright 2011 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 main func init() { addTestCases(hashSumTests, hashSumFn) } var hashSumTests = []testCase{ { Name: "hashsum.0", In: `package main import "crypto/sha256" func f() []byte { h := sha256.New() return h.Sum() } `, Out: `package main import "crypto/sha256" func f() []byte { h := sha256.New() return h.Sum(nil) } `, }, // ... (他のテストケース) }
コアとなるコードの解説
src/cmd/gofix/hashsum.go
がこのコミットの核心部分です。
hashSumFix
: これはgofix
ツールに登録される新しい修正ルールを定義する構造体です。hashSumFn
が実際にコードを修正する関数として指定されています。hashSumTypeConfig
:gofix
がコードの型情報を正確に理解するために使用される設定です。crypto
パッケージのハッシュ定数や、様々なハッシュ生成関数がhash.Hash
インターフェースを返すことをgofix
に伝えます。これにより、gofix
はSum()
メソッドがhash.Hash
インターフェースのインスタンスに対して呼び出されているかを正確に識別できます。hashSumFn(f *ast.File) bool
: この関数が、Goのソースコードの抽象構文木(AST)を走査し、必要な修正を適用するメインロジックです。typecheck(hashSumTypeConfig, f)
: まず、現在のファイルの型情報を取得します。これは、Sum
メソッドが実際にhash.Hash
インターフェースのメソッドであるかを判断するために不可欠です。walk(f, func(n interface{}) { ... })
: ASTの各ノードを再帰的に走査します。call, ok := n.(*ast.CallExpr)
: 現在のノードが関数呼び出し(CallExpr
)であるかをチェックします。ok && len(call.Args) == 0
: 関数呼び出しであり、かつ引数が一つも渡されていない(つまりSum()
の形式)ことを確認します。sel, ok := call.Fun.(*ast.SelectorExpr)
: 呼び出されている関数がセレクタ式(例:object.Method()
)であるかをチェックします。ok && sel.Sel.Name == "Sum"
: セレクタの選択された名前が"Sum"
であることを確認します。typeof[sel.X] == "hash.Hash"
: ここが最も重要な部分で、typeof
マップを使って、Sum
メソッドが呼び出されているオブジェクト(sel.X
)の型が"hash.Hash"
であることを確認します。これにより、他のSum
という名前のメソッド(例えば、ユーザー定義の構造体のメソッドなど)が誤って修正されるのを防ぎます。call.Args = append(call.Args, ast.NewIdent("nil"))
: 上記の条件がすべて満たされた場合、ast.NewIdent("nil")
(nil
という識別子を表すASTノード)をcall.Args
スライスに追加します。これにより、Sum()
がSum(nil)
に書き換えられます。fixed = true
: 修正が行われたことを示すフラグを設定します。
このロジックにより、gofix
は既存のGoコードベースを安全かつ正確に、新しいhash.Sum
のAPIシグネチャに適合させることができます。
関連リンク
- Go言語のコードレビューシステム:
- https://golang.org/cl/5448065
- https://golang.org/cl/5450051
- http://codereview.appspot.com/5448065 (これは
golang.org/cl/5448065
と同じ内容を指している可能性が高いです。Goの初期のコードレビューシステムはApp Engine上でホストされていました。)
参考にした情報源リンク
- Go言語の公式ドキュメント(
hash
パッケージ、gofix
ツールに関する情報) - Go言語のソースコード(特に
src/cmd/gofix
ディレクトリ内の他の修正ファイル) - Go言語のブログや設計ドキュメント(
hash.Sum
のAPI変更に関する議論や背景情報) - Go言語の抽象構文木(AST)に関する一般的な情報