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

[インデックス 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)に直接書き込むようなシナリオで、アロケーションを避けることです。

このコミットは、ExampleSummd5.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言語の標準ライブラリが、単に機能を提供するだけでなく、その機能の最適な利用方法をコード例を通じて示すという哲学を反映しています。

関連リンク

参考にした情報源リンク