[インデックス 18548] ファイルの概要
コミット
このコミット 5c604f844a058223e9b13d602e53e14d5f199ad6
は、Go言語の標準ライブラリ crypto/md5
パッケージにおける ExampleSum
関数の例を削除するものです。具体的には、以前追加された CL 64820044
(ハッシュ 4f9dee8402af
) の変更を取り消しています。
コミットの目的は、md5.Sum
の呼び出し元がメモリ割り当て(アロケーション)を避けるべきであるという意図を明確にすることです。削除された例は、この重要な特性を適切に示していなかったため、誤解を招く可能性がありました。
コミットハッシュ: 5c604f844a058223e9b13d602e53e14d5f199ad6
Author: Dave Cheney dave@cheney.net
Date: Tue Feb 18 08:04:01 2014 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5c604f844a058223e9b13d602e53e14d5f199ad6
元コミット内容
undo CL 64820044 / 4f9dee8402af
Callers of md5.Sum should do so to avoid allocations, the example did not demonstate this property.
««« original CL description
crypto/md5: add example for Sum
LGTM=dave
R=golang-codereviews, dave
CC=golang-codereviews
https://golang.org/cl/64820044
»»»
LGTM=minux.ma
R=r, minux.ma
CC=golang-codereviews
https://golang.org/cl/65180043
変更の背景
この変更の背景には、Go言語におけるメモリ管理とパフォーマンスに関する設計思想があります。特に、ハッシュ関数のような計算集約的な操作においては、不要なメモリ割り当てを避けることが非常に重要です。
crypto/md5
パッケージの Sum
関数は、入力バイトスライスからMD5ハッシュを計算し、[md5.Size]byte
型の配列として結果を返します。この関数は、内部で一時的なハッシュコンテキストを生成し、入力データを処理します。
以前の CL 64820044
で追加された ExampleSum
は、md5.Sum([]byte(input))
のように、文字列リテラルを []byte
に変換して直接 Sum
関数に渡していました。一見すると問題ないように見えますが、この []byte(input)
の部分が、Goのコンパイラによってはヒープ上に新しいバイトスライスを割り当てる可能性があります。
md5.Sum
の設計意図は、呼び出し元が既に存在するバッファ(例えば、スタック上に確保された配列)を利用して、メモリ割り当てを最小限に抑えることです。しかし、削除された例は、この「アロケーションを避ける」という Sum
関数の重要な利用パターンを明確に示していませんでした。むしろ、暗黙的にアロケーションを発生させる可能性のあるコードを示していたため、ユーザーが md5.Sum
を誤った方法で利用し、パフォーマンス上の問題を引き起こすことを懸念したため、この例が削除されることになりました。
Goの標準ライブラリの例は、単に機能を示すだけでなく、その機能の「正しい使い方」や「推奨される使い方」を示す役割も担っています。この例がその役割を果たしていなかったため、削除という判断が下されました。
前提知識の解説
Go言語のメモリ管理とアロケーション
Go言語はガベージコレクション(GC)を持つ言語であり、開発者が明示的にメモリを解放する必要はありません。しかし、GCのオーバーヘッドを減らし、アプリケーションのパフォーマンスを向上させるためには、不必要なメモリ割り当て(アロケーション)を避けることが重要です。
- スタックとヒープ: Goの変数は、その寿命とサイズによってスタックまたはヒープに割り当てられます。
- スタック: 関数呼び出しごとに確保され、関数が終了すると自動的に解放されるメモリ領域です。通常、サイズが既知で寿命が短いローカル変数が割り当てられます。スタックのアロケーションと解放は非常に高速です。
- ヒープ: プログラムの実行中に動的に確保されるメモリ領域です。ガベージコレクタによって管理され、不要になったメモリはGCによって回収されます。ヒープのアロケーションはスタックに比べてコストが高く、GCの実行もパフォーマンスに影響を与えます。
- エスケープ解析: Goコンパイラは、変数がスタックに割り当てられるべきか、ヒープに「エスケープ」して割り当てられるべきかを決定する「エスケープ解析」を行います。例えば、関数の外に参照が渡される変数や、サイズが大きすぎる変数はヒープにエスケープする傾向があります。
- バイトスライスと配列:
- 配列 (
[N]T
): 固定長の値のシーケンスです。配列は値型であり、通常はスタックに割り当てられます(ただし、エスケープ解析の結果ヒープに割り当てられることもあります)。 - スライス (
[]T
): 配列の一部を参照する動的なビューです。スライス自体はポインタ、長さ、容量の3つの要素からなる小さな構造体ですが、その参照先のデータはヒープに割り当てられることが多いです。[]byte(input)
のように文字列をバイトスライスに変換する場合、新しいバイトスライスが作成され、その基盤となる配列がヒープに割り当てられる可能性があります。
- 配列 (
crypto/md5
パッケージとハッシュ関数
crypto/md5
パッケージは、MD5(Message-Digest Algorithm 5)ハッシュ関数を実装したGoの標準ライブラリです。MD5は、任意の長さの入力データから128ビット(16バイト)の固定長ハッシュ値を生成する一方向ハッシュ関数です。
- ハッシュ関数: 入力データ(メッセージ)から固定長の短い値(ハッシュ値、メッセージダイジェスト、フィンガープリントなどと呼ばれる)を生成する関数です。
md5.New()
:hash.Hash
インターフェースを実装した新しいMD5ハッシュコンテキストを返します。これを使ってWrite
メソッドでデータを incrementally に追加し、Sum
メソッドで最終的なハッシュ値を取得します。md5.Sum(data []byte) [md5.Size]byte
: 入力data
全体からMD5ハッシュを計算し、[md5.Size]byte
型の配列として結果を返します。この関数は、md5.New()
とWrite()
を内部で呼び出す糖衣構文(シンタックスシュガー)のようなものです。
GoのExample関数
Goの標準ライブラリやパッケージには、Example
関数という特殊な関数があります。これらは、パッケージのドキュメントにコード例として表示され、go test
コマンドで自動的にテストされます。// Output:
コメントと一致するかどうかでテストの合否が判定されます。Example関数は、その機能の正しい使い方をユーザーに示すための重要な役割を担っています。
技術的詳細
このコミットは、src/pkg/crypto/md5/example_test.go
ファイルから ExampleSum
関数を削除するという非常にシンプルな変更です。しかし、その背後にはGo言語のパフォーマンスとメモリ管理に関する深い考慮があります。
md5.Sum
関数は、以下のようなシグネチャを持っています。
func Sum(data []byte) [Size]byte
この関数は、入力として []byte
スライスを受け取り、[Size]byte
型の配列を返します。ここで重要なのは、戻り値がスライスではなく「配列」である点です。配列は値型であり、通常はスタックに割り当てられます。これにより、Sum
関数の呼び出し元は、ヒープアロケーションなしでハッシュ結果を受け取ることができます。
削除された ExampleSum
関数は以下のようでした。
func ExampleSum() {
input := "The quick brown fox jumps over the lazy dog."
fmt.Printf("%x", md5.Sum([]byte(input)))
// Output: e4d909c290d0fb1ca068ffaddf22cbd0
}
このコードの []byte(input)
の部分が問題となります。Goの文字列はイミュータブルなバイトのシーケンスであり、通常は読み取り専用のデータセグメントに格納されます。[]byte(input)
のように文字列をバイトスライスに変換すると、Goコンパイラは新しい可変なバイトスライスを作成し、文字列の内容をそこにコピーする必要があります。この新しいバイトスライスは、エスケープ解析の結果、ヒープに割り当てられる可能性が高いです。
もし md5.Sum
が頻繁に呼び出されるようなパフォーマンスクリティカルなコードパスで使用される場合、この []byte(input)
によるヒープアロケーションは、ガベージコレクションの頻度を増やし、アプリケーション全体のパフォーマンスに悪影響を与える可能性があります。
md5.Sum
の本来の意図は、呼び出し元が既に []byte
を持っている場合や、スタック上に確保された固定サイズのバッファ(例: var buf [md5.Size]byte
)に直接書き込むようなシナリオで、アロケーションを避けることです。
このコミットは、ExampleSum
が md5.Sum
の「アロケーションを避ける」という重要な特性を適切に示していなかったため、誤解を招く可能性を排除するために削除されました。これにより、開発者は md5.Sum
を使用する際に、メモリ割り当ての側面をより意識するよう促されます。
コアとなるコードの変更箇所
src/pkg/crypto/md5/example_test.go
ファイルから以下の ExampleSum
関数が削除されました。
--- a/src/pkg/crypto/md5/example_test.go
+++ b/src/pkg/crypto/md5/example_test.go
@@ -17,9 +17,3 @@ func ExampleNew() {
fmt.Printf("%x", h.Sum(nil))\n // Output: e2c569be17396eca2a2e3c11578123ed\n }\n-\n-func ExampleSum() {\n-\tinput := \"The quick brown fox jumps over the lazy dog.\"\n-\tfmt.Printf("%x", md5.Sum([]byte(input)))\n-\t// Output: e4d909c290d0fb1ca068ffaddf22cbd0\n-}\n
コアとなるコードの解説
変更は、example_test.go
ファイルから ExampleSum
関数を完全に削除することです。これにより、md5.Sum
の使用例として、文字列リテラルを直接バイトスライスに変換して渡すパターンが公式ドキュメントからなくなります。
この削除は、md5.Sum
の利用者が、その関数が「アロケーションを避ける」という特性を持つことを理解し、その意図に沿った使い方をすることを促すためのものです。例えば、既に []byte
データがある場合や、md5.New()
を使ってストリーミングでハッシュを計算し、h.Sum(nil)
で結果を得る(この場合も Sum
は新しいスライスを返すためアロケーションが発生するが、h.Sum(b []byte)
のように既存のバッファに書き込むことも可能)といった、よりパフォーマンス効率の良いパターンに焦点を当てることを示唆しています。
この変更は、Go言語の標準ライブラリが、単に機能を提供するだけでなく、その機能の最適な利用方法をコード例を通じて示すという哲学を反映しています。
関連リンク
- 元の変更セット (CL 64820044): https://golang.org/cl/64820044
- この変更セット (CL 65180043): https://golang.org/cl/65180043
- Go言語の
crypto/md5
パッケージのドキュメント: https://pkg.go.dev/crypto/md5
参考にした情報源リンク
- Go言語のメモリ管理に関する一般的な情報 (Stack vs Heap, Escape Analysis):
- Go言語のExample関数に関する情報:
- MD5ハッシュ関数に関する一般的な情報:
- Go言語における文字列とバイトスライスの変換に関する情報:
- Dave Cheney氏のブログ (Go言語のパフォーマンスに関する記事が多い):
- https://dave.cheney.net/ (特にアロケーションに関する記事)
- Goのコードレビューシステム (Gerrit) のCL (Change List) について: