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

[インデックス 16649] ファイルの概要

このコミットは、Go言語の標準ライブラリ crypto/md5 パッケージに対する変更です。具体的には、MD5ハッシュ計算をより簡単に行えるようにトップレベルの Sum 関数が追加され、また、block 関数に //go:noescape ディレクティブが追加されています。

コミット

commit 4850f5d5ea5913af57c9ae37121ad88aba2d612c
Author: Rob Pike <r@golang.org>
Date:   Wed Jun 26 11:29:30 2013 -0700

    crypto/md5: provide a top-level Sum function
    Makes it easy to ask the simple question, what is the hash of this data?
    Also mark block as non-escaping.
    
    R=golang-dev, agl
    CC=golang-dev
    https://golang.org/cl/10624044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4850f5d5ea5913af57c9ae37121ad88aba2d612c

元コミット内容

crypto/md5: provide a top-level Sum function
Makes it easy to ask the simple question, what is the hash of this data?
Also mark block as non-escaping.

変更の背景

このコミットには主に二つの背景があります。

  1. MD5ハッシュ計算の簡素化: 既存の crypto/md5 パッケージでは、MD5ハッシュを計算するために md5.New() でハッシュオブジェクトを作成し、Write() メソッドでデータを書き込み、最後に Sum() メソッドを呼び出すという一連の手順が必要でした。これは柔軟性を提供しますが、単にバイト列のMD5ハッシュ値が欲しいだけのシンプルなユースケースでは冗長でした。このコミットは、このようなシンプルな要求に応えるため、トップレベルの Sum 関数を提供し、より直感的なAPIを提供することを目的としています。

  2. パフォーマンス最適化 (//go:noescape): Go言語のコンパイラは、関数の引数や戻り値がヒープに割り当てられるべきか(エスケープ解析)を自動的に判断します。ヒープ割り当てはガベージコレクションの対象となり、パフォーマンスに影響を与える可能性があります。//go:noescape ディレクティブは、特定の関数がポインタをヒープにエスケープさせないことをコンパイラに明示的に伝えるためのものです。これにより、コンパイラはより積極的な最適化(例えば、スタック割り当ての維持)を行うことができ、特に頻繁に呼び出される低レベルの関数においてパフォーマンスの向上が期待されます。crypto/md5 パッケージの block 関数は、MD5計算のコアとなる部分であり、頻繁に呼び出されるため、この最適化の恩恵を受けることができます。

前提知識の解説

MD5 (Message-Digest Algorithm 5)

MD5は、128ビット(16バイト)のハッシュ値を生成する広く使われている暗号学的ハッシュ関数です。任意の長さの入力データから、固定長のハッシュ値を生成します。主な用途は、データの完全性検証(データが改ざんされていないことの確認)や、パスワードのハッシュ化などです。 しかし、MD5は衝突耐性(異なる入力から同じハッシュ値が生成されること)に脆弱性が見つかっており、セキュリティが非常に重要な場面(デジタル署名など)ではSHA-256などのより強力なハッシュ関数が推奨されています。それでも、データの完全性チェックや非暗号学的な用途では依然として広く利用されています。

Go言語の crypto/md5 パッケージ

Go言語の標準ライブラリ crypto/md5 は、MD5ハッシュ関数を実装しています。 基本的な使用方法は以下のようになります。

package main

import (
	"crypto/md5"
	"fmt"
	"io"
)

func main() {
	h := md5.New() // ハッシュオブジェクトの作成
	io.WriteString(h, "Hello, world!") // データの書き込み
	fmt.Printf("%x\n", h.Sum(nil)) // ハッシュ値の取得
}

Go言語のエスケープ解析と //go:noescape ディレクティブ

Go言語のコンパイラは、プログラムの実行時に変数がどこに割り当てられるべきか(スタックかヒープか)を決定する「エスケープ解析」を行います。

  • スタック割り当て: 関数内で宣言された変数が、その関数が終了するまでに不要になる場合、通常はスタックに割り当てられます。スタック割り当ては高速で、ガベージコレクションのオーバーヘッドがありません。
  • ヒープ割り当て: 変数が関数のスコープを超えて参照される可能性がある場合(例えば、関数の戻り値としてポインタが返される場合や、グローバル変数に代入される場合)、ヒープに割り当てられます。ヒープ割り当てはスタック割り当てよりも遅く、ガベージコレクションの対象となります。

//go:noescape は、Goコンパイラに対する特別なディレクティブ(プラグマ)です。このディレクティブが関数宣言の直前に記述されている場合、コンパイラはその関数が引数として受け取ったポインタをヒープにエスケープさせないことを保証すると仮定します。もし実際にエスケープさせているにもかかわらず //go:noescape を指定すると、実行時エラーや未定義の動作を引き起こす可能性があります。 このディレクティブは、主にアセンブリ言語で書かれた関数や、コンパイラがエスケープ解析を誤る可能性がある非常に低レベルの最適化が必要なGo関数で使用されます。これにより、コンパイラはより積極的な最適化(例えば、スタック割り当ての維持)を行うことができ、ガベージコレクションの負荷を軽減し、パフォーマンスを向上させることができます。

技術的詳細

このコミットは、crypto/md5 パッケージに以下の主要な変更を加えています。

  1. Sum 関数の追加:

    • md5.go にトップレベルの Sum(data []byte) [Size]byte 関数が追加されました。
    • この関数は、入力バイトスライス data を受け取り、そのMD5ハッシュ値を [Size]byte 型(Size はMD5ハッシュのバイト長、つまり16)の配列として直接返します。
    • 内部的には、digest 型の新しいハッシュオブジェクトを作成し、Reset() で初期化、Write() でデータを書き込み、新しく追加された checkSum() メソッドを呼び出してハッシュ値を取得しています。
  2. checkSum メソッドの導入:

    • 既存の (*digest) Sum(in []byte) []byte メソッドから、実際のハッシュ計算とパディングロジックを (*digest) checkSum() [Size]byte という新しいプライベートメソッドに分離しました。
    • これにより、ハッシュ計算のコアロジックが再利用可能になり、トップレベルの Sum 関数と既存の Sum メソッドの両方から呼び出せるようになりました。
    • checkSum メソッドは、ハッシュ値を [Size]byte 型の配列として返します。これは、Goの配列が値型であり、ヒープへのエスケープを避けるのに役立つため、パフォーマンス上の利点があります。
  3. //go:noescape ディレクティブの追加:

    • md5block_decl.go ファイルの block 関数宣言に //go:noescape ディレクティブが追加されました。
    • block 関数は、MD5のコア計算ループでデータを処理する低レベルの関数です。この関数がポインタをヒープにエスケープさせないことをコンパイラに明示することで、コンパイラはより効率的なコードを生成し、ガベージコレクションのオーバーヘッドを削減できます。
  4. テストケースの追加:

    • md5_test.go に、新しく追加されたトップレベルの Sum 関数をテストするためのコードが追加されました。
    • 既存のゴールデンテスト(既知の入力と期待される出力のペア)を利用して、Sum([]byte(g.in)) の結果が期待通りであることを検証しています。

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

src/pkg/crypto/md5/md5.go

--- a/src/pkg/crypto/md5/md5.go
+++ b/src/pkg/crypto/md5/md5.go
@@ -88,7 +88,11 @@ func (d *digest) Write(p []byte) (nn int, err error) {
 func (d0 *digest) Sum(in []byte) []byte {
 	// Make a copy of d0 so that caller can keep writing and summing.
 	d := *d0
+	hash := d.checkSum()
+	return append(in, hash[:]...)
+}
 
+func (d *digest) checkSum() [Size]byte {
 	// Padding.  Add a 1 bit and 0 bits until 56 bytes mod 64.
 	len := d.len
 	var tmp [64]byte
@@ -118,5 +122,13 @@ func (d0 *digest) Sum(in []byte) []byte {
 		digest[i*4+3] = byte(s >> 24)
 	}
 
-	return append(in, digest[:]...)\n+	return digest
+}
+
+// Sum returns the MD5 checksum of the data.
+func Sum(data []byte) [Size]byte {
+	var d digest
+	d.Reset()
+	d.Write(data)
+	return d.checkSum()
 }

src/pkg/crypto/md5/md5_test.go

--- a/src/pkg/crypto/md5/md5_test.go
+++ b/src/pkg/crypto/md5/md5_test.go
@@ -53,6 +53,10 @@ var golden = []md5Test{
 func TestGolden(t *testing.T) {
 	for i := 0; i < len(golden); i++ {
 		g := golden[i]
+		s := fmt.Sprintf("%x", Sum([]byte(g.in)))
+		if s != g.out {
+			t.Fatalf("Sum function: md5(%s) = %s want %s", g.in, s, g.out)
+		}
 		c := New()
 		buf := make([]byte, len(g.in)+4)
 		for j := 0; j < 3+4; j++ {

src/pkg/crypto/md5/md5block_decl.go

--- a/src/pkg/crypto/md5/md5block_decl.go
+++ b/src/pkg/crypto/md5/md5block_decl.go
@@ -6,4 +6,6 @@
 
 package md5
 
+//go:noescape
+
 func block(dig *digest, p []byte)

コアとなるコードの解説

md5.go の変更点

  • func (d0 *digest) Sum(in []byte) []byte の変更:

    • このメソッドは、既存の hash.Hash インターフェースの一部として提供される Sum メソッドです。
    • 変更前は、ハッシュ計算と結果の in への追加を直接行っていました。
    • 変更後は、d.checkSum() を呼び出して実際のハッシュ値を計算し、その結果を in スライスに append して返しています。これにより、ハッシュ計算ロジックが checkSum に集約されました。
  • func (d *digest) checkSum() [Size]byte の追加:

    • この新しいプライベートメソッドは、MD5ハッシュの最終計算とパディング処理を担当します。
    • digest 構造体の現在の状態(長さ len とハッシュ値 h)に基づいて、MD5の最終的なハッシュ値を計算し、[Size]byte 型の配列として返します。
    • この分離により、ハッシュ計算のコアロジックがカプセル化され、再利用性が向上しました。
  • func Sum(data []byte) [Size]byte の追加:

    • これがこのコミットの主要な機能追加です。
    • data バイトスライスを直接受け取り、そのMD5ハッシュ値を計算して返します。
    • 内部では、一時的な digest オブジェクトを作成し、Reset() で初期化、Write(data) で入力データを処理し、最後に d.checkSum() を呼び出してハッシュ値を取得しています。
    • この関数により、MD5ハッシュを計算する際のボイラープレートコードが大幅に削減され、より簡潔に記述できるようになりました。

md5_test.go の変更点

  • TestGolden 関数内に、新しく追加されたトップレベルの Sum 関数をテストするコードが追加されました。
  • fmt.Sprintf("%x", Sum([]byte(g.in))) を使用して、Sum 関数が生成するハッシュ値を文字列に変換し、既存のゴールデンテストの期待値 g.out と比較しています。
  • これにより、新しい Sum 関数が正しく機能することを確認しています。

md5block_decl.go の変更点

  • func block(dig *digest, p []byte) の宣言の直前に //go:noescape ディレクティブが追加されました。
  • このディレクティブは、block 関数が引数 digp のポインタをヒープにエスケープさせないことをコンパイラに保証します。
  • これにより、コンパイラは block 関数の呼び出しにおいて、より積極的な最適化(例えば、スタック割り当ての維持)を行うことができ、ガベージコレクションのオーバーヘッドを減らし、パフォーマンスを向上させることが期待されます。

関連リンク

  • Go言語の crypto/md5 パッケージのドキュメント: https://pkg.go.dev/crypto/md5
  • Go言語のエスケープ解析に関する情報: https://go.dev/doc/articles/go_mem.html (Go Memory Model)
  • //go:noescape ディレクティブに関する情報 (Goのソースコードや関連するGoのIssueで詳細が見つかることが多い)

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/cmd/compile/internal/gc/escape.go など、コンパイラのエスケープ解析に関連する部分)
  • MD5に関する一般的な情報源 (例: Wikipedia)
  • Go言語のコミット履歴と関連するコードレビュー (Go CL 10624044)

[インデックス 16649] ファイルの概要

このコミットは、Go言語の標準ライブラリ crypto/md5 パッケージに対する変更です。具体的には、MD5ハッシュ計算をより簡単に行えるようにトップレベルの Sum 関数が追加され、また、block 関数に //go:noescape ディレクティブが追加されています。

コミット

commit 4850f5d5ea5913af57c9ae37121ad88aba2d612c
Author: Rob Pike <r@golang.org>
Date:   Wed Jun 26 11:29:30 2013 -0700

    crypto/md5: provide a top-level Sum function
    Makes it easy to ask the simple question, what is the hash of this data?
    Also mark block as non-escaping.
    
    R=golang-dev, agl
    CC=golang-dev
    https://golang.org/cl/10624044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4850f5d5ea5913af57c9ae37121ad88aba2d612c

元コミット内容

crypto/md5: provide a top-level Sum function
Makes it easy to ask the simple question, what is the hash of this data?
Also mark block as non-escaping.

変更の背景

このコミットには主に二つの背景があります。

  1. MD5ハッシュ計算の簡素化: 既存の crypto/md5 パッケージでは、MD5ハッシュを計算するために md5.New() でハッシュオブジェクトを作成し、Write() メソッドでデータを書き込み、最後に Sum() メソッドを呼び出すという一連の手順が必要でした。これは柔軟性を提供しますが、単にバイト列のMD5ハッシュ値が欲しいだけのシンプルなユースケースでは冗長でした。このコミットは、このようなシンプルな要求に応えるため、トップレベルの Sum 関数を提供し、より直感的なAPIを提供することを目的としています。

  2. パフォーマンス最適化 (//go:noescape): Go言語のコンパイラは、関数の引数や戻り値がヒープに割り当てられるべきか(エスケープ解析)を自動的に判断します。ヒープ割り当てはガベージコレクションの対象となり、パフォーマンスに影響を与える可能性があります。//go:noescape ディレクティブは、特定の関数がポインタをヒープにエスケープさせないことをコンパイラに明示的に伝えるためのものです。これにより、コンパイラはより積極的な最適化(例えば、スタック割り当ての維持)を行うことができ、特に頻繁に呼び出される低レベルの関数においてパフォーマンスの向上が期待されます。crypto/md5 パッケージの block 関数は、MD5計算のコアとなる部分であり、頻繁に呼び出されるため、この最適化の恩恵を受けることができます。

前提知識の解説

MD5 (Message-Digest Algorithm 5)

MD5は、128ビット(16バイト)のハッシュ値を生成する広く使われている暗号学的ハッシュ関数です。任意の長さの入力データから、固定長のハッシュ値を生成します。主な用途は、データの完全性検証(データが改ざんされていないことの確認)や、パスワードのハッシュ化などです。 しかし、MD5は衝突耐性(異なる入力から同じハッシュ値が生成されること)に脆弱性が見つかっており、セキュリティが非常に重要な場面(デジタル署名など)ではSHA-256などのより強力なハッシュ関数が推奨されています。それでも、データの完全性チェックや非暗号学的な用途では依然として広く利用されています。

Go言語の crypto/md5 パッケージ

Go言語の標準ライブラリ crypto/md5 は、MD5ハッシュ関数を実装しています。 基本的な使用方法は以下のようになります。

package main

import (
	"crypto/md5"
	"fmt"
	"io"
)

func main() {
	h := md5.New() // ハッシュオブジェクトの作成
	io.WriteString(h, "Hello, world!") // データの書き込み
	fmt.Printf("%x\n", h.Sum(nil)) // ハッシュ値の取得
}

Go言語のエスケープ解析と //go:noescape ディレクティブ

Go言語のコンパイラは、プログラムの実行時に変数がどこに割り当てられるべきか(スタックかヒープか)を決定する「エスケープ解析」を行います。

  • スタック割り当て: 関数内で宣言された変数が、その関数が終了するまでに不要になる場合、通常はスタックに割り当てられます。スタック割り当ては高速で、ガベージコレクションのオーバーヘッドがありません。
  • ヒープ割り当て: 変数が関数のスコープを超えて参照される可能性がある場合(例えば、関数の戻り値としてポインタが返される場合や、グローバル変数に代入される場合)、ヒープに割り当てられます。ヒープ割り当てはスタック割り当てよりも遅く、ガベージコレクションの対象となります。

//go:noescape は、Goコンパイラに対する特別なディレクティブ(プラグマ)です。このディレクティブが関数宣言の直前に記述されている場合、コンパイラはその関数が引数として受け取ったポインタをヒープにエスケープさせないことを保証すると仮定します。もし実際にエスケープさせているにもかかわらず //go:noescape を指定すると、実行時エラーや未定義の動作を引き起こす可能性があります。 このディレクティブは、主にアセンブリ言語で書かれた関数や、コンパイラがエスケープ解析を誤る可能性がある非常に低レベルの最適化が必要なGo関数で使用されます。これにより、コンパイラはより積極的な最適化(例えば、スタック割り当ての維持)を行うことができ、ガベージコレクションの負荷を軽減し、パフォーマンスを向上させることができます。

技術的詳細

このコミットは、crypto/md5 パッケージに以下の主要な変更を加えています。

  1. Sum 関数の追加:

    • md5.go にトップレベルの Sum(data []byte) [Size]byte 関数が追加されました。
    • この関数は、入力バイトスライス data を受け取り、そのMD5ハッシュ値を [Size]byte 型(Size はMD5ハッシュのバイト長、つまり16)の配列として直接返します。
    • 内部的には、digest 型の新しいハッシュオブジェクトを作成し、Reset() で初期化、Write() でデータを書き込み、新しく追加された checkSum() メソッドを呼び出してハッシュ値を取得しています。
  2. checkSum メソッドの導入:

    • 既存の (*digest) Sum(in []byte) []byte メソッドから、実際のハッシュ計算とパディングロジックを (*digest) checkSum() [Size]byte という新しいプライベートメソッドに分離しました。
    • これにより、ハッシュ計算のコアロジックが再利用可能になり、トップレベルの Sum 関数と既存の Sum メソッドの両方から呼び出せるようになりました。
    • checkSum メソッドは、ハッシュ値を [Size]byte 型の配列として返します。これは、Goの配列が値型であり、ヒープへのエスケープを避けるのに役立つため、パフォーマンス上の利点があります。
  3. //go:noescape ディレクティブの追加:

    • md5block_decl.go ファイルの block 関数宣言に //go:noescape ディレクティブが追加されました。
    • block 関数は、MD5のコア計算ループでデータを処理する低レベルの関数です。この関数がポインタをヒープにエスケープさせないことをコンパイラに明示することで、コンパイラはより効率的なコードを生成し、ガベージコレクションのオーバーヘッドを削減できます。
  4. テストケースの追加:

    • md5_test.go に、新しく追加されたトップレベルの Sum 関数をテストするためのコードが追加されました。
    • 既存のゴールデンテスト(既知の入力と期待される出力のペア)を利用して、Sum([]byte(g.in)) の結果が期待通りであることを検証しています。

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

src/pkg/crypto/md5/md5.go

--- a/src/pkg/crypto/md5/md5.go
+++ b/src/pkg/crypto/md5/md5.go
@@ -88,7 +88,11 @@ func (d *digest) Write(p []byte) (nn int, err error) {
 func (d0 *digest) Sum(in []byte) []byte {
 	// Make a copy of d0 so that caller can keep writing and summing.
 	d := *d0
+	hash := d.checkSum()
+	return append(in, hash[:]...)
+}
 
+func (d *digest) checkSum() [Size]byte {
 	// Padding.  Add a 1 bit and 0 bits until 56 bytes mod 64.
 	len := d.len
 	var tmp [64]byte
@@ -118,5 +122,13 @@ func (d0 *digest) Sum(in []byte) []byte {
 		digest[i*4+3] = byte(s >> 24)
 	}
 
-	return append(in, digest[:]...)\n+	return digest
+}
+
+// Sum returns the MD5 checksum of the data.
+func Sum(data []byte) [Size]byte {
+	var d digest
+	d.Reset()
+	d.Write(data)
+	return d.checkSum()
 }

src/pkg/crypto/md5/md5_test.go

--- a/src/pkg/crypto/md5/md5_test.go
+++ b/src/pkg/crypto/md5/md5_test.go
@@ -53,6 +53,10 @@ var golden = []md5Test{
 func TestGolden(t *testing.T) {
 	for i := 0; i < len(golden); i++ {
 		g := golden[i]
+		s := fmt.Sprintf("%x", Sum([]byte(g.in)))
+		if s != g.out {
+			t.Fatalf("Sum function: md5(%s) = %s want %s", g.in, s, g.out)
+		}
 		c := New()
 		buf := make([]byte, len(g.in)+4)
 		for j := 0; j < 3+4; j++ {

src/pkg/crypto/md5/md5block_decl.go

--- a/src/pkg/crypto/md5/md5block_decl.go
+++ b/src/pkg/crypto/md5/md5block_decl.go
@@ -6,4 +6,6 @@
 
 package md5
 
+//go:noescape
+
 func block(dig *digest, p []byte)

コアとなるコードの解説

md5.go の変更点

  • func (d0 *digest) Sum(in []byte) []byte の変更:

    • このメソッドは、既存の hash.Hash インターフェースの一部として提供される Sum メソッドです。
    • 変更前は、ハッシュ計算と結果の in への追加を直接行っていました。
    • 変更後は、d.checkSum() を呼び出して実際のハッシュ値を計算し、その結果を in スライスに append して返しています。これにより、ハッシュ計算ロジックが checkSum に集約されました。
  • func (d *digest) checkSum() [Size]byte の追加:

    • この新しいプライベートメソッドは、MD5ハッシュの最終計算とパディング処理を担当します。
    • digest 構造体の現在の状態(長さ len とハッシュ値 h)に基づいて、MD5の最終的なハッシュ値を計算し、[Size]byte 型の配列として返します。
    • この分離により、ハッシュ計算のコアロジックがカプセル化され、再利用性が向上しました。
  • func Sum(data []byte) [Size]byte の追加:

    • これがこのコミットの主要な機能追加です。
    • data バイトスライスを直接受け取り、そのMD5ハッシュ値を計算して返します。
    • 内部では、一時的な digest オブジェクトを作成し、Reset() で初期化、Write(data) で入力データを処理し、最後に d.checkSum() を呼び出してハッシュ値を取得しています。
    • この関数により、MD5ハッシュを計算する際のボイラープレートコードが大幅に削減され、より簡潔に記述できるようになりました。

md5_test.go の変更点

  • TestGolden 関数内に、新しく追加されたトップレベルの Sum 関数をテストするコードが追加されました。
  • fmt.Sprintf("%x", Sum([]byte(g.in))) を使用して、Sum 関数が生成するハッシュ値を文字列に変換し、既存のゴールデンテストの期待値 g.out と比較しています。
  • これにより、新しい Sum 関数が正しく機能することを確認しています。

md5block_decl.go の変更点

  • func block(dig *digest, p []byte) の宣言の直前に //go:noescape ディレクティブが追加されました。
  • このディレクティブは、block 関数が引数 digp のポインタをヒープにエスケープさせないことをコンパイラに保証します。
  • これにより、コンパイラは block 関数の呼び出しにおいて、より積極的な最適化(例えば、スタック割り当ての維持)を行うことができ、ガベージコレクションのオーバーヘッドを減らし、パフォーマンスを向上させることが期待されます。

関連リンク

  • Go言語の crypto/md5 パッケージのドキュメント: https://pkg.go.dev/crypto/md5
  • Go言語のエスケープ解析に関する情報: https://go.dev/doc/articles/go_mem.html (Go Memory Model)
  • //go:noescape ディレクティブに関する情報 (Goのソースコードや関連するGoのIssueで詳細が見つかることが多い)

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特に src/cmd/compile/internal/gc/escape.go など、コンパイラのエスケープ解析に関連する部分)
  • MD5に関する一般的な情報源 (例: Wikipedia)
  • Go言語のコミット履歴と関連するコードレビュー (Go CL 10624044)