[インデックス 1847] ファイルの概要
このコミットは、Go言語の標準ライブラリに含まれるhash/adler32
パッケージにおけるAdler-32チェックサム計算ロジックの改善を目的としています。具体的には、Digest
構造体から不要なフィールドを削除し、チェックサム計算中のモジュロ演算の適用条件をより効率的かつ正確に修正することで、コードの「クリーンさ」と堅牢性を向上させています。
コミット
commit 658ca49898b024753603f8f0a9194312469d466e
Author: Tom Szymanski <tgs@google.com>
Date: Wed Mar 18 14:57:55 2009 -0700
Make adler32 cleaner.
R=rsc
APPROVED=rsc
DELTA=22 (9 added, 6 deleted, 7 changed)
OCL=26498
CL=26500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/658ca49898b024753603f8f0a9194312469d466e
元コミット内容
Make adler32 cleaner.
R=rsc
APPROVED=rsc
DELTA=22 (9 added, 6 deleted, 7 changed)
OCL=26498
CL=26500
変更の背景
Adler-32チェックサムは、データの整合性を確認するための高速なハッシュ関数です。その計算は、2つの32ビットの和(a
とb
)を維持し、各バイトが処理されるたびにこれらを更新し、特定のモジュロ(mod
)で剰余を取ることで行われます。
このコミット以前のadler32
の実装では、Digest
構造体にn
というフィールドがあり、これはモジュロ演算を適用するまでのバイト数をカウントするために使用されていました。また、maxIter
という定数があり、これはa
とb
がオーバーフローする前にモジュロ演算なしで処理できる最大バイト数を示していました。
しかし、Adler-32のアルゴリズムの特性上、a
とb
の和が32ビット符号なし整数の最大値(0xffffffff
)を超える可能性がある場合にのみモジュロ演算を適用すれば十分です。n
とmaxIter
を使ったアプローチは、このオーバーフロー条件を間接的に管理しようとするものでしたが、より直接的かつ効率的な方法が存在します。
このコミットの背景には、コードの簡素化、不要な状態の削除、そしてAdler-32アルゴリズムの数学的特性に基づいたより堅牢なオーバーフローチェックの実装という意図があります。これにより、コードの可読性と保守性が向上し、潜在的なバグのリスクが低減されます。
前提知識の解説
Adler-32 チェックサム
Adler-32は、Zlibで広く使用されているチェックサムアルゴリズムで、CRC32よりも高速に計算できることで知られています。これは、以下の2つの32ビットの和を維持することで計算されます。
a
: データの各バイトを累積的に加算し、mod
で剰余を取ったもの。初期値は1。b
:a
の累積和をさらに累積的に加算し、mod
で剰余を取ったもの。初期値は0。
各バイトbyte
が処理されるたびに、以下の計算が行われます。
a = (a + byte) % mod
b = (b + a) % mod
最終的なAdler-32チェックサムは、(b << 16) | a
として計算されます。
モジュロ演算とオーバーフロー
Adler-32の計算では、a
とb
がmod
(65521、最大の素数)を超えないようにモジュロ演算が適用されます。しかし、毎バイトごとにモジュロ演算を行うのは計算コストが高いため、通常はa
とb
が32ビット符号なし整数の範囲内でオーバーフローする可能性が出てきた場合にのみモジュロ演算を適用する最適化が行われます。
a
とb
はそれぞれ最大でmod - 1
の値を取ります。新しいバイトが追加されると、a
は最大でmod - 1 + 255
(バイトの最大値)まで増加し、b
は最大でmod - 1 + (mod - 1 + 255)
まで増加します。これらの値が32ビット符号なし整数の最大値0xffffffff
を超える可能性がある場合に、モジュロ演算を適用する必要があります。
Go言語のuint32
型
Go言語のuint32
型は、32ビットの符号なし整数を表します。その範囲は0から2^32 - 1
(0xffffffff
)までです。この型で計算を行う際、加算結果が0xffffffff
を超えるとオーバーフローが発生し、結果はモジュロ2^32
でラップアラウンドされます。Adler-32の計算では、このラップアラウンドを避けるために、明示的にmod
でのモジュロ演算を行う必要があります。
技術的詳細
このコミットの主要な変更点は以下の通りです。
-
Digest
構造体からのn
フィールドの削除:- 変更前:
type Digest struct { a, b uint32; n int; }
- 変更後:
type Digest struct { a, b uint32; }
n
フィールドは、モジュロ演算を適用するまでのバイト数をカウントするために使用されていましたが、これは不要になりました。
- 変更前:
-
maxIter
定数の削除:maxIter
は、n
フィールドと組み合わせてオーバーフローを管理するための定数でしたが、n
の削除に伴いこれも不要になりました。
-
NewDigest
関数の簡素化:NewDigest
関数は、n
フィールドの初期化が不要になったため、return &Digest{1, 0, 0};
からreturn &Digest{1, 0};
へと簡素化されました。
-
Write
メソッドにおけるモジュロ演算の条件変更:- 変更前は、
n == maxIter
という条件でモジュロ演算が適用されていました。これは、maxIter
バイト処理するごとにa
とb
がオーバーフローする可能性があるという仮定に基づいていました。 - 変更後:
if b > (0xffffffff - 255) / 2 { ... }
- この新しい条件は、
a
とb
の和が32ビット符号なし整数の最大値に近づき、次の加算でオーバーフローする可能性を直接チェックします。 a
は最大でmod - 1 + 255
(約65775)まで増加します。b
は最大でb_current + a_new
まで増加します。- 最悪の場合、
a
とb
がそれぞれmod-1
に近い値で、次のバイトが255の場合、a
はa + 255
となり、b
はb + (a + 255)
となります。 a + b + 255 <= 0xffffffff
という不変条件を維持するために、b
が(0xffffffff - 255) / 2
を超える場合にモジュロ演算を適用します。これは、a
とb
の最大値がそれぞれmod-1
であることを考慮すると、a + b + 255
が0xffffffff
を超える可能性があるかを効率的にチェックする条件です。- この条件により、
a
とb
の合計が0xffffffff
に近づいたときにのみモジュロ演算が実行され、不要なモジュロ演算が削減されます。
- この新しい条件は、
- 変更前は、
-
Sum32
メソッドにおけるモジュロ演算の条件変更:- 変更前:
if a >= mod || b >= mod { ... }
- 変更後:
if b >= mod { ... }
a
は常にb
よりも小さいか等しい(a <= b
)という不変条件がWrite
メソッド内で維持されるため、b
がmod
を超えていればa
もmod
を超える可能性があるか、あるいは最終的なa
とb
の組み合わせでオーバーフローが発生する可能性があるため、b >= mod
のチェックだけで十分です。これにより、条件が簡素化されました。
- 変更前:
これらの変更により、Adler-32の実装は、アルゴリズムの数学的特性により忠実になり、不要な状態管理が排除され、より効率的で「クリーン」なコードになっています。
コアとなるコードの変更箇所
src/lib/hash/adler32.go
--- a/src/lib/hash/adler32.go
+++ b/src/lib/hash/adler32.go
@@ -13,44 +13,46 @@ package adler32
import "os"
+const (
+ mod = 65521;
+)
+
// Digest represents the partial evaluation of a checksum.
type Digest struct {
+\t// invariant: (a < mod && b < mod) || a <= b
+\t// invariant: a + b + 255 <= 0xffffffff
\ta, b uint32;
-\tn int;
}\
-const (
- mod = 65521;
- maxIter = 5552; // max mod-free iterations before would overflow uint32
-)
-\
// NewDigest creates a new Digest.
func NewDigest() *Digest {\
-\treturn &Digest{1, 0, 0};\
+\treturn &Digest{1, 0};\
}\
// Write updates the Digest with the incremental checksum generated by p.\
// It returns the number of bytes written; err is always nil.\
func (d *Digest) Write(p []byte) (nn int, err *os.Error) {\
-\ta, b, n := d.a, d.b, d.n;\
+\ta, b := d.a, d.b;\
\tfor i := 0; i < len(p); i++ {\
\t\ta += uint32(p[i]);
\t\tb += a;
-\t\tn++;
-\t\tif n == maxIter {\
+\t\t// invariant: a <= b
+\t\tif b > (0xffffffff - 255) / 2 {\
\t\t\ta %= mod;
\t\t\tb %= mod;
-\t\t\tn = 0;\
+\t\t\t// invariant: a < mod && b < mod
+\t\t} else {\
+\t\t\t// invariant: a + b + 255 <= 2 * b + 255 <= 0xffffffff
\t\t}\
\t}\
-\td.a, d.b, d.n = a, b, n;\
+\td.a, d.b = a, b;\
\treturn len(p), nil
}\
// Sum32 returns the 32-bit Adler-32 checksum of the data written to the Digest.\
func (d *Digest) Sum32() uint32 {\
\ta, b := d.a, d.b;\
-\tif a >= mod || b >= mod {\
+\tif b >= mod {\
\t\ta %= mod;\
\t\tb %= mod;\
\t}\
src/lib/hash/adler32_test.go
--- a/src/lib/hash/adler32_test.go
+++ b/src/lib/hash/adler32_test.go
@@ -47,6 +47,7 @@ var golden = []_Adler32Test {
_Adler32Test{ 0x39111dd0, "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley" },
_Adler32Test{ 0x91dd304f, "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule" },
_Adler32Test{ 0x2e5d1316, "How can you write a big system without C++? -Paul Glick" },
+\t_Adler32Test{ 0xd0201df6, "'Invariant assertions' is the most elegant programming technique! -Tom Szymanski" },
}\
func TestGolden(t *testing.T) {\
コアとなるコードの解説
src/lib/hash/adler32.go
-
Digest
構造体:n int;
フィールドが削除されました。これにより、Digest
構造体はa
とb
の2つのuint32
フィールドのみを持つようになり、状態がよりシンプルになりました。- 新しいコメント
// invariant: (a < mod && b < mod) || a <= b
と// invariant: a + b + 255 <= 0xffffffff
が追加され、a
とb
の間の関係性およびオーバーフローに関する不変条件が明示されました。これは、コードの意図を明確にし、将来の保守性を高めます。
-
NewDigest
関数:n
フィールドの初期化が不要になったため、return &Digest{1, 0, 0};
からreturn &Digest{1, 0};
に変更されました。
-
Write
メソッド:a, b, n := d.a, d.b, d.n;
からa, b := d.a, d.b;
に変更され、n
変数の使用がなくなりました。- モジュロ演算の条件が
if n == maxIter { ... }
からif b > (0xffffffff - 255) / 2 { ... }
に変更されました。- この新しい条件は、
b
の値が特定の閾値を超えた場合にモジュロ演算を実行します。この閾値は、a
とb
の合計が次のバイトの加算によってuint32
の最大値0xffffffff
を超える可能性を考慮して設定されています。具体的には、a + b + 255
が0xffffffff
を超えないようにするために、b
が(0xffffffff - 255) / 2
を超えたらモジュロ演算を行うというロジックです。これにより、オーバーフローをより正確に検出し、必要な場合にのみモジュロ演算を実行することで効率を向上させています。 - 新しいコメント
// invariant: a <= b
と// invariant: a < mod && b < mod
が追加され、ループ内の不変条件が明確にされました。
- この新しい条件は、
-
Sum32
メソッド:- モジュロ演算の条件が
if a >= mod || b >= mod { ... }
からif b >= mod { ... }
に変更されました。 Write
メソッド内でa <= b
という不変条件が維持されているため、b
がmod
を超えていれば、a
もmod
を超えるか、あるいは最終的なチェックサムの計算に影響を与える可能性があるため、b
のチェックだけで十分であると判断されました。これにより、条件が簡素化されました。
- モジュロ演算の条件が
src/lib/hash/adler32_test.go
- 新しいテストケースが
golden
スライスに追加されました。_Adler32Test{ 0xd0201df6, "'Invariant assertions' is the most elegant programming technique! -Tom Szymanski" },
- このテストケースは、変更されたロジックが正しく機能することを確認するためのものです。特に、
Write
メソッド内の不変条件の導入と関連している可能性があります。
これらの変更は、Adler-32チェックサムの実装をより堅牢で、効率的で、理解しやすいものにすることを目的としています。不要な状態を削除し、オーバーフローチェックをより直接的に行うことで、コードの品質が向上しています。
関連リンク
- Adler-32 checksum - Wikipedia: https://en.wikipedia.org/wiki/Adler-32
- Go言語の
hash/adler32
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/hash/adler32
参考にした情報源リンク
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Adler-32アルゴリズムに関する一般的な情報源 (例: Zlibドキュメントなど)
- Go言語の
uint32
型の挙動に関する情報