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

[インデックス 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に追記して返します。bnilの場合、新しいバイトスライスを割り当てて返します。
  • 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)に自動的に書き換えるためのロジックを追加します。

  1. gofixの拡張: src/cmd/gofix/Makefilehashsum.goが追加され、ビルドプロセスに組み込まれます。これにより、gofixコマンドが新しい修正ルールを認識するようになります。
  2. 新しい修正ルールの定義: src/cmd/gofix/hashsum.goが新規作成され、hashSumFixというfix構造体が定義されます。この構造体は、修正の名前("hashsum")、適用日("2011-11-30")、修正ロジックを実装する関数(hashSumFn)、および修正の説明を含みます。
  3. 型チェック設定: hashSumTypeConfigというTypeConfig構造体が定義されています。これは、gofixの型チェッカーが、cryptoパッケージ内の様々なハッシュ定数(例: crypto.MD5)や、adler32, crc32, fnv, hmac, md4, md5, ripemd160, sha1, sha256, sha512などのパッケージから返されるhash.Hashインターフェースを正しく認識するための情報を提供します。これにより、gofixhash.Hash型のインスタンスに対してSum()メソッドが呼び出されているかを正確に判断できます。
  4. 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.Argsast.NewIdent("nil")を追加し、Sum()呼び出しをSum(nil)に書き換えます。
    • 修正が行われた場合、fixedフラグをtrueに設定し、最終的にその値を返します。

この一連のプロセスにより、開発者は手動でコードを修正することなく、既存のhash.Sum()の呼び出しを新しいAPIに自動的に移行できるようになります。

コアとなるコードの変更箇所

このコミットでは、主に以下の3つのファイルが変更されています。

  1. src/cmd/gofix/Makefile:

    • hashsum.goGOFILESリストに追加され、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\
    
  2. 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
    }
    
  3. 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に伝えます。これにより、gofixSum()メソッドがhash.Hashインターフェースのインスタンスに対して呼び出されているかを正確に識別できます。
  • hashSumFn(f *ast.File) bool: この関数が、Goのソースコードの抽象構文木(AST)を走査し、必要な修正を適用するメインロジックです。
    1. typecheck(hashSumTypeConfig, f): まず、現在のファイルの型情報を取得します。これは、Sumメソッドが実際にhash.Hashインターフェースのメソッドであるかを判断するために不可欠です。
    2. walk(f, func(n interface{}) { ... }): ASTの各ノードを再帰的に走査します。
    3. call, ok := n.(*ast.CallExpr): 現在のノードが関数呼び出し(CallExpr)であるかをチェックします。
    4. ok && len(call.Args) == 0: 関数呼び出しであり、かつ引数が一つも渡されていない(つまりSum()の形式)ことを確認します。
    5. sel, ok := call.Fun.(*ast.SelectorExpr): 呼び出されている関数がセレクタ式(例: object.Method())であるかをチェックします。
    6. ok && sel.Sel.Name == "Sum": セレクタの選択された名前が"Sum"であることを確認します。
    7. typeof[sel.X] == "hash.Hash": ここが最も重要な部分で、typeofマップを使って、Sumメソッドが呼び出されているオブジェクト(sel.X)の型が"hash.Hash"であることを確認します。これにより、他のSumという名前のメソッド(例えば、ユーザー定義の構造体のメソッドなど)が誤って修正されるのを防ぎます。
    8. call.Args = append(call.Args, ast.NewIdent("nil")): 上記の条件がすべて満たされた場合、ast.NewIdent("nil")nilという識別子を表すASTノード)をcall.Argsスライスに追加します。これにより、Sum()Sum(nil)に書き換えられます。
    9. 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)に関する一般的な情報