[インデックス 13188] ファイルの概要
このコミットは、Go言語の標準ライブラリ crypto
パッケージ内のハッシュ関数(MD5, SHA-1, SHA-256, SHA-512)に関するハウスキーピング(整理整頓)とパフォーマンス測定の改善を目的としています。具体的には、内部関数の命名規則の統一、ブロック処理関数の責務の明確化、およびベンチマークの追加が行われています。
影響を受けるファイルは以下の通りです。
src/pkg/crypto/md5/gen.go
src/pkg/crypto/md5/md5.go
src/pkg/crypto/md5/md5block.go
src/pkg/crypto/sha1/sha1.go
src/pkg/crypto/sha1/sha1_test.go
src/pkg/crypto/sha1/sha1block.go
src/pkg/crypto/sha256/sha256.go
src/pkg/crypto/sha256/sha256_test.go
src/pkg/crypto/sha256/sha256block.go
src/pkg/crypto/sha512/sha512.go
src/pkg/crypto/sha512/sha512_test.go
src/pkg/crypto/sha512/sha512block.go
これらのファイル全体で、内部的な定数名や関数名の変更、そしてハッシュ計算のパフォーマンスを測定するためのベンチマークコードの追加が行われています。
コミット
commit 992a11b88b5cf28d651fd5834852ed36f326c528
Author: Russ Cox <rsc@golang.org>
Date: Tue May 29 12:45:40 2012 -0400
crypto: housekeeping
Rename _Block to block, don't bother making it compute count.
Add benchmarks.
R=agl, agl
CC=golang-dev
https://golang.org/cl/6243053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/992a11b88b5cf28d651fd5834852ed36f326c528
元コミット内容
crypto: housekeeping
Rename _Block to block, don't bother making it compute count.
Add benchmarks.
変更の背景
このコミットの背景には、Go言語の標準ライブラリにおけるコードの品質向上とパフォーマンスの可視化という二つの主要な目的があります。
-
コードの整理と命名規則の統一 (Housekeeping): Go言語では、識別子(変数名、関数名など)の最初の文字が大文字か小文字かによって、その識別子がエクスポートされる(パッケージ外からアクセス可能)か、アンエクスポートされる(パッケージ内でのみアクセス可能)かが決まります。慣習として、内部的な関数や定数にはアンエクスポートされた名前(小文字で始まる名前)が使われます。このコミット以前は、内部的なブロック処理関数が
_Block
のようにアンダースコアで始まっていましたが、これはGoの命名規則において特別な意味を持たず、単にアンエクスポートされていることを示すために_
を接頭辞として使用する慣習は一般的ではありませんでした。この変更は、よりGoらしい命名規則に準拠し、_
を削除してblock
とすることで、コードの可読性と一貫性を向上させることを目的としています。 また、_Chunk
や_InitX
といった定数も同様にchunk
やinitX
に変更されており、これも命名規則の統一の一環です。 -
ブロック処理関数の責務の明確化: 以前の
_Block
関数は、ブロック処理を行うだけでなく、処理したバイト数を返すという二つの責務を持っていました。コミットメッセージにある「don't bother making it compute count」という記述は、この関数から処理バイト数を計算して返す責務を取り除くことを意味します。これにより、block
関数は純粋にハッシュ計算のブロック処理に特化し、単一責務の原則に近づきます。処理されたバイト数の管理は、Write
メソッドのような呼び出し元に委ねられることになります。これにより、コードの理解が容易になり、将来的な変更や最適化がしやすくなります。 -
パフォーマンスベンチマークの追加: ハッシュ関数は、データの整合性チェックやセキュリティなど、多くの場面で利用される重要なコンポーネントです。これらの関数のパフォーマンスは、アプリケーション全体の性能に直接影響を与えます。ベンチマークを追加することで、ハッシュ関数の処理速度を定量的に測定できるようになります。これにより、将来のコード変更がパフォーマンスに与える影響を評価したり、異なる実装間の性能比較を行ったりすることが可能になります。特に、
BenchmarkHash1K
(1KBのデータ) とBenchmarkHash8K
(8KBのデータ) のベンチマークは、一般的なデータサイズでのハッシュ計算性能を評価するために役立ちます。
これらの変更は、Go言語の標準ライブラリの品質、保守性、および性能を継続的に改善するための一般的な取り組みの一環として行われました。
前提知識の解説
このコミットを理解するためには、以下の前提知識があると役立ちます。
1. 暗号学的ハッシュ関数 (Cryptographic Hash Functions)
暗号学的ハッシュ関数は、任意の長さの入力データ(メッセージ)を受け取り、固定長の短い出力(ハッシュ値、メッセージダイジェスト、または単にハッシュ)を生成する数学的なアルゴリズムです。主な特性として以下が挙げられます。
- 一方向性 (One-way function): ハッシュ値から元の入力データを効率的に復元することは非常に困難です。
- 衝突耐性 (Collision resistance): 異なる入力データから同じハッシュ値が生成されること(衝突)が非常に困難です。
- 原像計算困難性 (Preimage resistance): 特定のハッシュ値を持つ入力データを効率的に見つけることは非常に困難です。
- 第二原像計算困難性 (Second preimage resistance): 特定の入力データと同じハッシュ値を持つ別の入力データを効率的に見つけることは非常に困難です。
このコミットで扱われているMD5、SHA-1、SHA-256、SHA-512は、これら暗号学的ハッシュ関数の具体的なアルゴリズムです。
- MD5 (Message-Digest Algorithm 5): 128ビットのハッシュ値を生成します。現在ではセキュリティ上の脆弱性が指摘されており、データの完全性チェックには使用されますが、セキュリティ目的での利用は推奨されません。
- SHA-1 (Secure Hash Algorithm 1): 160ビットのハッシュ値を生成します。MD5と同様に、衝突攻撃の可能性が指摘されており、セキュリティ目的での利用は非推奨となっています。
- SHA-256 (Secure Hash Algorithm 256): 256ビットのハッシュ値を生成します。SHA-2ファミリーの一つで、現在でも広く利用されている安全なハッシュ関数です。
- SHA-512 (Secure Hash Algorithm 512): 512ビットのハッシュ値を生成します。SHA-2ファミリーの一つで、SHA-256よりも長いハッシュ値を生成し、より高いセキュリティレベルを提供します。
これらのハッシュ関数は、内部的に入力データを固定長の「ブロック」に分割し、各ブロックに対して一連の複雑な数学的演算(圧縮関数)を繰り返し適用することでハッシュ値を計算します。
2. Go言語の命名規則
Go言語では、識別子の可視性(スコープ)は、その識別子の最初の文字が大文字か小文字かによって決まります。
- 大文字で始まる識別子: パッケージ外からアクセス可能です(エクスポートされる)。
- 小文字で始まる識別子: パッケージ内でのみアクセス可能です(アンエクスポートされる)。
このコミットでは、_Block
や _Chunk
のようにアンダースコアで始まる識別子が、Goの慣習に沿って block
や chunk
のように小文字で始まる識別子に変更されています。これは、これらの識別子がパッケージ内部でのみ使用されることを明確にし、Goの標準的な命名規則に準拠するためです。
3. Go言語のベンチマーク (Benchmarking)
Go言語には、標準ライブラリ testing
パッケージにベンチマーク機能が組み込まれています。これにより、コードのパフォーマンスを簡単に測定できます。
- ベンチマーク関数:
Benchmark
というプレフィックスで始まり、*testing.B
型の引数を取る関数として定義されます(例:func BenchmarkXxx(b *testing.B)
)。 b.N
: ベンチマーク関数内でループを回す回数を示します。Goのテストフレームワークが、適切な実行時間を確保するために自動的に調整します。b.SetBytes(n int64)
: 1回の操作で処理されるバイト数を指定します。これにより、ベンチマーク結果が「操作あたりのns」だけでなく、「バイトあたりのns」や「MB/s」といった形で表示され、より意味のあるパフォーマンス指標が得られます。b.ResetTimer()
: ベンチマークの計測を開始する前に、セットアップコードの実行時間をリセットします。go test -bench=.
: ベンチマークを実行するためのコマンドです。
このコミットで追加されたベンチマークは、ハッシュ関数の Write
メソッドが特定のサイズのデータ(1KBと8KB)を処理するのにかかる時間を測定し、そのスループットを評価するために使用されます。
技術的詳細
このコミットで行われた技術的な変更は、主に以下の3つのカテゴリに分類できます。
1. 内部関数 _Block
の block
へのリネームとシグネチャ変更
- リネーム:
src/pkg/crypto/md5/gen.go
,src/pkg/crypto/md5/md5block.go
,src/pkg/crypto/sha1/sha1block.go
,src/pkg/crypto/sha256/sha256block.go
,src/pkg/crypto/sha512/sha512block.go
内の_Block
関数がblock
にリネームされました。これはGoの命名規則に準拠し、内部関数であることを明確にするための変更です。 - シグネチャ変更: 以前の
_Block
関数はfunc _Block(dig *digest, p []byte) int
のように、処理したバイト数をint
で返していました。このコミットにより、func block(dig *digest, p []byte)
のように、戻り値が削除されました。- 変更前:
_Block
関数はp
スライスから_Chunk
サイズのデータを処理し、p
を_Chunk
分進め、処理したバイト数_Chunk
をn
に加算していました。そして最終的にn
を返していました。 - 変更後:
block
関数はp
スライスからchunk
サイズのデータを処理し、p
をchunk
分進める内部ロジックは変わりませんが、処理したバイト数を返すことはなくなりました。これにより、block
関数は純粋にハッシュ計算のブロック処理に専念し、入力データの消費管理は呼び出し元(digest
型のWrite
メソッド)の責務となりました。
- 変更前:
2. 内部定数名の変更
src/pkg/crypto/md5/md5.go
,src/pkg/crypto/sha1/sha1.go
,src/pkg/crypto/sha256/sha256.go
,src/pkg/crypto/sha512/sha512.go
内で定義されている内部定数名が変更されました。_Chunk
がchunk
に変更されました。これはハッシュ関数が一度に処理するブロックサイズ(例: MD5とSHA-1は64バイト、SHA-256は64バイト、SHA-512は128バイト)を表す定数です。_InitX
(例:_Init0
,_Init1
など) がinitX
に変更されました。これらはハッシュ計算の初期値(IV: Initialization Vector)を表す定数です。 これらの変更も、Goの命名規則に準拠し、コードの可読性を向上させるためのものです。
3. digest.Write
メソッドの修正
_Block
関数のシグネチャ変更に伴い、src/pkg/crypto/md5/md5.go
,src/pkg/crypto/sha1/sha1.go
,src/pkg/crypto/sha256/sha256.go
,src/pkg/crypto/sha512/sha512.go
内のdigest.Write
メソッドが修正されました。- 変更前は
n := _Block(d, p)
のように_Block
の戻り値を受け取り、p = p[n:]
で残りのデータを処理していました。 - 変更後は
block
関数が戻り値を返さないため、if len(p) >= chunk { n := len(p) &^ (chunk - 1); block(d, p[:n]); p = p[n:] }
のように、Write
メソッド自身がp
スライスをchunk
サイズで繰り返し処理し、残りのデータを管理するようになりました。len(p) &^ (chunk - 1)
は、p
の長さからchunk
の倍数部分を計算するためのビット演算です。これにより、p
の先頭からchunk
の倍数分のデータがblock
関数に渡され、処理された後にp
がその分だけ進められます。
- 変更前は
4. ベンチマークの追加
src/pkg/crypto/sha1/sha1_test.go
,src/pkg/crypto/sha256/sha256_test.go
,src/pkg/crypto/sha512/sha512_test.go
にベンチマーク関数が追加されました。makeBuf()
: 8KBのバイトスライスを生成し、ベンチマーク用のデータとして使用します。BenchmarkHash1K(b *testing.B)
: 1KBのデータをハッシュ化するパフォーマンスを測定します。b.SetBytes(1024)
で1回の操作で1024バイトが処理されることを示します。BenchmarkHash8K(b *testing.B)
: 8KBのデータをハッシュ化するパフォーマンスを測定します。b.SetBytes(int64(len(buf)))
で8KBのデータが処理されることを示します。 これらのベンチマークは、go test -bench=.
コマンドで実行でき、ハッシュ関数のスループット(例: MB/s)を評価するために使用されます。
これらの変更は、Go言語のコードベース全体で一貫性を保ち、パフォーマンスの測定と最適化を容易にするための重要なステップです。
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更箇所は、主に以下の3点です。
-
_Block
関数のリネームとシグネチャ変更: 例:src/pkg/crypto/md5/gen.go
--- a/src/pkg/crypto/md5/gen.go +++ b/src/pkg/crypto/md5/gen.go @@ -186,15 +186,14 @@ import ( } {{end}} -func _Block(dig *digest, p []byte) int { +func block(dig *digest, p []byte) { a := dig.s[0] b := dig.s[1] c := dig.s[2] d := dig.s[3] - n := 0 var X *[16]uint32 var xbuf [16]uint32 - for len(p) >= _Chunk { + for len(p) >= chunk { aa, bb, cc, dd := a, b, c, d // This is a constant condition - it is not evaluated on each iteration. @@ -288,14 +287,12 @@ func _Block(dig *digest, p []byte) int { c += cc d += dd - p = p[_Chunk:] - n += _Chunk + p = p[chunk:] } dig.s[0] = a dig.s[1] = b dig.s[2] = c dig.s[3] = d - return n }
この変更は、
md5block.go
,sha1block.go
,sha256block.go
,sha512block.go
でも同様に行われています。 -
内部定数名の変更: 例:
src/pkg/crypto/md5/md5.go
--- a/src/pkg/crypto/md5/md5.go +++ b/src/pkg/crypto/md5/md5.go @@ -21,12 +21,12 @@ const Size = 16 const BlockSize = 64 const ( - _Chunk = 64 - _Init0 = 0x67452301 - _Init1 = 0xEFCDAB89 - _Init2 = 0x98BADCFE - _Init3 = 0x10325476 + chunk = 64 + init0 = 0x67452301 + init1 = 0xEFCDAB89 + init2 = 0x98BADCFE + init3 = 0x10325476 )
この変更は、
sha1.go
,sha256.go
,sha512.go
でも同様に行われています。 -
digest.Write
メソッドのブロック処理ロジックの修正: 例:src/pkg/crypto/md5/md5.go
--- a/src/pkg/crypto/md5/md5.go +++ b/src/pkg/crypto/md5/md5.go @@ -61,9 +61,12 @@ func (d *digest) Write(p []byte) (nn int, err error) { if d.nx > 0 { n := len(p) - if n > _Chunk-d.nx { - n = _Chunk - d.nx + if n > chunk-d.nx { + n = chunk - d.nx } for i := 0; i < n; i++ { d.x[d.nx+i] = p[i] } d.nx += n - if d.nx == _Chunk { - _Block(d, d.x[0:]) + if d.nx == chunk { + block(d, d.x[0:chunk]) d.nx = 0 } p = p[n:] } - n := _Block(d, p) - p = p[n:] + if len(p) >= chunk { + n := len(p) &^ (chunk - 1) + block(d, p[:n]) + p = p[n:] + } if len(p) > 0 { d.nx = copy(d.x[:], p) }
この変更は、
sha1.go
,sha256.go
,sha512.go
でも同様に行われています。 -
ベンチマークの追加: 例:
src/pkg/crypto/sha1/sha1_test.go
--- a/src/pkg/crypto/sha1/sha1_test.go +++ b/src/pkg/crypto/sha1/sha1_test.go @@ -79,3 +79,28 @@ func ExampleNew() { fmt.Printf("% x", h.Sum(nil)) // Output: 59 7f 6a 54 00 10 f9 4c 15 d7 18 06 a9 9a 2c 87 10 e7 47 bd } + +var bench = sha1.New() +var buf = makeBuf() + +func makeBuf() []byte { + b := make([]byte, 8<<10) + for i := range b { + b[i] = byte(i) + } + return b +} + +func BenchmarkHash1K(b *testing.B) { + b.SetBytes(1024) + for i := 0; i < b.N; i++ { + bench.Write(buf[:1024]) + } +} + +func BenchmarkHash8K(b *testing.B) { + b.SetBytes(int64(len(buf))) + for i := 0; i < b.N; i++ { + bench.Write(buf) + } +}
この変更は、
sha256_test.go
,sha512_test.go
でも同様に行われています。
コアとなるコードの解説
1. _Block
から block
への変更と戻り値の削除
以前の _Block
関数は、ハッシュ計算のブロック処理を行い、さらに処理したバイト数(常に _Chunk
の倍数)を int
型で返していました。このコミットでは、この関数が block
とリネームされ、戻り値が削除されました。
変更の意図:
- 単一責務の原則:
block
関数は、純粋にハッシュ計算のコアロジック(ブロックの圧縮)に集中するようになりました。処理されたバイト数の管理という二次的な責務が取り除かれ、関数の役割が明確になりました。 - Goの慣習: Goでは、内部的な関数は小文字で始まる名前を使用します。
_
を接頭辞として使用する慣習は一般的ではなく、block
という名前はよりGoらしい命名です。
コードへの影響:
block
関数内部では、n := 0
の初期化と n += _Chunk
の加算が削除され、最終的な return n
もなくなりました。これにより、block
関数は入力 p
の内容を直接変更し、dig
(ダイジェスト構造体) の状態を更新する副作用を持つ関数となりました。
2. 内部定数名の変更 (_Chunk
-> chunk
, _InitX
-> initX
)
_Chunk
や _InitX
といった定数も、Goの命名規則に沿って chunk
や initX
に変更されました。
変更の意図:
- 一貫性:
block
関数名の変更と同様に、パッケージ内部でのみ使用される定数であることを明確にし、コードベース全体での命名規則の一貫性を保ちます。 - 可読性: アンダースコアの接頭辞はGoの慣習では特別な意味を持たないため、削除することでコードがより簡潔になります。
3. digest.Write
メソッドのブロック処理ロジックの修正
block
関数のシグネチャ変更に伴い、digest.Write
メソッド内のブロック処理ロジックが変更されました。
変更前:
n := _Block(d, p)
p = p[n:]
_Block
が処理したバイト数を返し、その戻り値を使って p
スライスを進めていました。
変更後:
if len(p) >= chunk {
n := len(p) &^ (chunk - 1)
block(d, p[:n])
p = p[n:]
}
block
関数が戻り値を返さないため、Write
メソッド自身が、入力 p
のうち chunk
サイズの倍数となる部分を計算し (len(p) &^ (chunk - 1)
)、その部分を block
関数に渡して処理させ、その後 p
スライスを適切に進めるようになりました。
len(p) &^ (chunk - 1)
: これは、p
の長さからchunk
の倍数部分を効率的に計算するビット演算です。例えばchunk
が64の場合、chunk - 1
は63 (バイナリで00111111
) となります。&^
(ビットクリア) 演算子を使うことで、p
の長さの下位6ビットをクリアし、chunk
の倍数に切り捨てた値を得ることができます。これにより、block
関数には常に完全なブロックのデータが渡されることが保証されます。
変更の意図:
- 責務の分離:
block
関数が純粋なブロック処理に特化したことで、入力データのバッファリングとブロック単位での供給はWrite
メソッドの責務となりました。これにより、各関数の役割がより明確になりました。 - 効率性: この変更は、ハッシュ計算のコアロジックの効率性には直接影響しませんが、コードの構造を改善し、将来的な最適化の余地を広げます。
4. ベンチマークの追加
sha1
, sha256
, sha512
のテストファイルに、ハッシュ計算のパフォーマンスを測定するためのベンチマーク関数が追加されました。
追加されたベンチマーク:
makeBuf()
: ベンチマーク用のデータとして、8KBのバイトスライスを生成します。BenchmarkHash1K(b *testing.B)
: 1KBのデータをハッシュ化する際の性能を測定します。b.SetBytes(1024)
を呼び出すことで、ベンチマーク結果が「1024バイトあたりのns」や「MB/s」として表示されるようになります。BenchmarkHash8K(b *testing.B)
: 8KBのデータをハッシュ化する際の性能を測定します。b.SetBytes(int64(len(buf)))
を呼び出すことで、ベンチマーク結果が「8KBあたりのns」や「MB/s」として表示されるようになります。
追加の意図:
- パフォーマンスの可視化: ハッシュ関数の実際の実行速度を定量的に把握できるようになります。
- 回帰テスト: 将来のコード変更がハッシュ関数のパフォーマンスに悪影響を与えないかを確認するための基準となります。
- 最適化の指針: パフォーマンスのボトルネックを特定し、最適化の方向性を決定するためのデータを提供します。
これらの変更は、Go言語の暗号ライブラリの内部構造を整理し、よりGoらしいコードベースにするとともに、パフォーマンスの測定と改善のための基盤を強化するものです。
関連リンク
- Go CL 6243053: https://golang.org/cl/6243053
参考にした情報源リンク
- Go言語のドキュメント (testingパッケージ): https://pkg.go.dev/testing
- Go言語の命名規則に関する一般的な情報 (Effective Goなど): https://go.dev/doc/effective_go#names
- MD5, SHA-1, SHA-256, SHA-512に関する一般的な情報 (Wikipediaなど)
- ビット演算子
&^
(bit clear) について: https://go.dev/ref/spec#Operators - Go言語のベンチマークの書き方に関する記事 (例: Goの公式ブログや技術ブログ)