[インデックス 11057] ファイルの概要
このコミットは、Go言語の math/big
パッケージ内の nat.go
ファイルにおける、任意精度数値の文字列変換処理の最適化と簡素化を目的としています。特に、convertWords
関数のロジックが改善され、パフォーマンスが向上しています。
コミット
commit b4be65bc7f56ed7ee19bbe8c18fb2a35e08bedca
Author: Robert Griesemer <gri@golang.org>
Date: Mon Jan 9 11:20:09 2012 -0800
math/big: simplify fast string conversion
- use slice ops for convertWords instead of lo/hi boundaries
- always compute leading zeroes (simplifies logic significantly),
but remove them once, at the end (since leafSize is small, the
worst-case scenario is not adding significant overhead)
- various comment cleanups (specifically, replaced direct -> iterative,
and indirect -> recursive)
- slightly faster overall for -bench=String
(This CL incorporates the changes re: my comments to CL 5418047
https://golang.org/cl/5418047/ )
benchmark old ns/op new ns/op delta
big.BenchmarkString10Base2 519 527 +1.54%
big.BenchmarkString100Base2 2279 2158 -5.31%
big.BenchmarkString1000Base2 18475 17323 -6.24%
big.BenchmarkString10000Base2 178248 166219 -6.75%
big.BenchmarkString100000Base2 1548494 1431587 -7.55%
big.BenchmarkString10Base8 415 422 +1.69%
big.BenchmarkString100Base8 1025 978 -4.59%
big.BenchmarkString1000Base8 6822 6428 -5.78%
big.BenchmarkString10000Base8 64598 61065 -5.47%
big.BenchmarkString100000Base8 593788 549150 -7.52%
big.BenchmarkString10Base10 654 645 -1.38%
big.BenchmarkString100Base10 1863 1835 -1.50%
big.BenchmarkString1000Base10 12099 11981 -0.98%
big.BenchmarkString10000Base10 57601 56888 -1.24%
big.BenchmarkString100000Base10 20123120 19827890 -1.47%
big.BenchmarkString10Base16 358 362 +1.12%
big.BenchmarkString100Base16 815 776 -4.79%
big.BenchmarkString1000Base16 4710 4421 -6.14%
big.BenchmarkString10000Base16 43938 40968 -6.76%
big.BenchmarkString100000Base16 406307 373930 -7.97%
R=michael.jones, mtj
CC=golang-dev
https://golang.org/cl/5432090
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b4be65bc7f56ed7ee19bbe8c18fb2a35e08bedca
元コミット内容
math/big: simplify fast string conversion
- use slice ops for convertWords instead of lo/hi boundaries
- always compute leading zeroes (simplifies logic significantly),
but remove them once, at the end (since leafSize is small, the
worst-case scenario is not adding significant overhead)
- various comment cleanups (specifically, replaced direct -> iterative,
and indirect -> recursive)
- slightly faster overall for -bench=String
(This CL incorporates the changes re: my comments to CL 5418047
https://golang.org/cl/5418047/ )
変更の背景
このコミットは、Go言語の math/big
パッケージにおける数値の文字列変換処理の効率と可読性を向上させることを目的としています。特に、convertWords
関数は、大きな数値を異なる基数(例えば10進数や16進数)の文字列に変換する際に中心的な役割を担っています。
以前の実装では、convertWords
関数内で文字列の変換範囲を lo/hi
の境界値で管理していましたが、これがコードの複雑さを増していました。また、先行ゼロの扱いや、再帰的・反復的な変換ロジックに関するコメントが不明瞭であった可能性も指摘されています。
この変更は、既存のコードレビュー(CL 5418047)でのコメントを受けて行われたものであり、よりシンプルで高速な文字列変換ロジックの実現を目指しています。コミットメッセージに含まれるベンチマーク結果が示すように、特に大きな数値の変換において顕著なパフォーマンス改善が見られます。
前提知識の解説
math/big
パッケージ: Go言語の標準ライブラリの一部で、任意精度の算術演算(大きな整数、有理数、浮動小数点数)を扱うためのパッケージです。通常のGoの数値型(int
,int64
など)では表現できない非常に大きな数値を扱う際に使用されます。- 任意精度算術 (Arbitrary-precision arithmetic): コンピュータの固定長のデータ型に依存せず、必要に応じて任意の桁数の数値を表現・計算できる算術システムです。
math/big
パッケージは、この任意精度算術を提供します。 - 基数変換 (Base Conversion): 数値をある基数(例:10進数)から別の基数(例:2進数、16進数)に変換するプロセスです。例えば、10進数の10を2進数に変換すると1010になります。
math/big
パッケージでは、大きな数値を指定された基数の文字列に変換する機能が提供されています。 nat
型:math/big
パッケージ内部で使用される、符号なしの大きな整数を表す型です。通常、Word
型(uint
またはuint64
)の配列として実装され、各要素が数値の一部を構成します。- スライス操作 (Slice Operations): Go言語のスライスは、配列の一部を参照する軽量なデータ構造です。スライス操作(例:
s[h:]
,s[:h]
) を使用することで、データのコピーをせずに効率的に部分配列を扱えます。これにより、関数の引数として渡す範囲を明示的に指定する代わりに、スライス自体を操作して範囲を制御できます。 - 先行ゼロ (Leading Zeroes): 数値の先頭にある意味のないゼロのことです。例えば、"007" の "00" の部分です。通常、数値の文字列変換ではこれらは除去されますが、内部処理の簡素化のために一時的に保持されることがあります。
- 反復的アルゴリズム (Iterative Algorithm): ループ構造を用いて処理を繰り返し実行するアルゴリズムです。
- 再帰的アルゴリズム (Recursive Algorithm): 関数が自分自身を呼び出すことで処理を繰り返すアルゴリズムです。大きな問題を小さな同じ構造の問題に分割して解決する際に用いられます。
- ベンチマーク (Benchmarking): ソフトウェアの性能を測定し、評価するプロセスです。Go言語では、
go test -bench
コマンドを使用してベンチマークを実行できます。ns/op
は「操作あたりのナノ秒」を示し、値が小さいほど高速であることを意味します。
技術的詳細
このコミットは、math/big
パッケージの src/pkg/math/big/nat.go
ファイルに対して行われた変更であり、主に nat
型の文字列変換メソッド string
および convertWords
のロジックを改善しています。
-
string
関数のMaxBase
チェックの変更:- 変更前:
case b < 2 || MaxBase < b:
- 変更後:
case b < 2 || MaxBase > 256:
- これにより、
string
関数が受け入れる基数の上限がMaxBase
から256に明確に変更されました。これは、charset
の長さが256を超えることはないという前提に基づいている可能性があります。
- 変更前:
-
convertWords
関数のシグネチャ変更とスライス操作の導入:- 変更前:
func (q nat) convertWords(lo, hi int, s []byte, charset string, b Word, ndigits int, bb Word, table []divisor) int
lo
,hi
という整数型の引数で、s
(バイトスライス) 内の変換範囲を明示的に指定していました。
- 変更後:
func (q nat) convertWords(s []byte, charset string, b Word, ndigits int, bb Word, table []divisor)
lo
,hi
引数が削除され、代わりにs
スライス自体が変換対象の範囲を示すように変更されました。これにより、関数内部でs = s[:h]
のようなスライス再スライス操作を行うことで、データのコピーをせずに効率的に部分配列を扱えるようになり、コードが簡潔になりました。
- 変更前:
-
先行ゼロの扱い:
- 変更前は、
convertWords
関数内でlo == 0 && len(q) == 0
の条件で最上位の桁グループの先行ゼロをスキップするロジックがありました。これは、変換中に部分的に先行ゼロを処理しようとするものでした。 - 変更後、
convertWords
関数内では先行ゼロを常に計算し、string
関数に戻った後にs
スライスに対して一度だけ先行ゼロの除去を行うようになりました。
この変更により、// strip leading zeros // (x != 0; thus s must contain at least one non-zero digit // and the loop will terminate) i = 0 for zero := charset[0]; s[i] == zero; { i++ } return string(s[i:])
convertWords
のロジックが大幅に簡素化されました。コミットメッセージにあるように、leafSize
が小さいため、この「後で除去する」アプローチによるオーバーヘッドは無視できるレベルであると判断されています。
- 変更前は、
-
コメントのクリーンアップと用語の統一:
convertWords
関数のコメントにおいて、「direct」という用語が「iterative」(反復的)に、「indirect」という用語が「recursive」(再帰的)に置き換えられました。これにより、アルゴリズムの性質がより正確に表現され、コードの可読性が向上しました。leafSize
の説明も「indirect conversion」から「recursive conversion」に修正されています。
-
パフォーマンスの改善:
- コミットメッセージに含まれるベンチマーク結果は、この変更が
math/big
の文字列変換性能に与えた影響を示しています。 big.BenchmarkString100000Base2
では約7.55%の改善、big.BenchmarkString100000Base8
では約7.52%の改善、big.BenchmarkString100000Base16
では約7.97%の改善が見られます。- 一方で、
big.BenchmarkString10Base2
やbig.BenchmarkString10Base8
、big.BenchmarkString10Base16
のように、一部の小さい数値の変換ではわずかな性能低下(1-2%程度)が見られますが、全体としては「slightly faster overall for -bench=String」というコミットメッセージの記述と一致しています。これは、大きな数値の変換における最適化が、小さな数値の変換におけるわずかなオーバーヘッドを上回る効果をもたらしたことを示唆しています。
- コミットメッセージに含まれるベンチマーク結果は、この変更が
-
expWW
関数の削除:nat
型のexpWW
関数(x**y
を計算する)が削除されました。この関数は、expNN
を呼び出すだけのラッパー関数であり、冗長であったため削除されたと考えられます。divisors
関数内でnat(nil).expWW(bb, Word(leafSize))
の呼び出しがありましたが、これはnat(nil).expNN(nat(nil).setWord(bb), nat(nil).setWord(Word(leafSize)), nil)
に置き換えられました。
コアとなるコードの変更箇所
src/pkg/math/big/nat.go
ファイルにおいて、主に以下の変更が行われました。
-
string
関数の変更:MaxBase
のチェック条件がMaxBase > 256
に変更。convertWords
の呼び出し後、s
スライスから先行ゼロを除去するロジックが追加。
-
convertWords
関数のシグネチャ変更:func (q nat) convertWords(s []byte, charset string, b Word, ndigits int, bb Word, table []divisor)
に変更。lo
,hi
引数が削除。
-
convertWords
関数の内部ロジック変更:- 再帰的な分割処理において、
s
スライスを直接操作する (s[h:]
,s = s[:h]
) ように変更。 - 反復的な変換処理において、先行ゼロのスキップロジックが削除され、常に桁を生成するように変更。
- 10進数変換の最適化部分で、
r - t<<3 - t - t
のような演算が導入され、コメントが追加。 - 高位のゼロを付加するロジックが、
lo != 0
の条件なしに常に実行されるように変更。
- 再帰的な分割処理において、
-
コメントの修正:
convertWords
の説明コメントで「direct」を「iterative」に、「indirect」を「recursive」に置換。leafSize
の説明コメントも同様に修正。
-
expWW
関数の削除:func (z nat) expWW(x, y Word) nat
関数がファイルから完全に削除。
コアとなるコードの解説
このコミットの核となる変更は、nat
型の string
メソッドと、その内部で呼び出される convertWords
メソッドの改善にあります。
-
convertWords
のシグネチャ変更とスライス操作:- 以前の
convertWords(lo, hi int, s []byte, ...)
では、s
のどの範囲に結果を書き込むかをlo
とhi
で明示的に指定していました。これは、関数が呼び出されるたびにこれらの境界値を適切に管理する必要があり、コードが複雑になる原因でした。 - 新しい
convertWords(s []byte, ...)
では、s
スライス自体が「現在処理している部分」を表すように変更されました。例えば、再帰呼び出しの際にr.convertWords(s[h:], ...)
のようにスライスの部分を渡すことで、その部分スライスが新しい関数のs
引数となり、関数内部では常にs
の先頭から書き込みを開始できるため、境界値の管理が不要になります。これにより、コードがより直感的で簡潔になりました。
- 以前の
-
先行ゼロの処理の簡素化:
- 以前は、
convertWords
の中で「最上位の桁グループの先行ゼロをスキップする」という条件付きロジックがありました。これは、変換の途中で部分的な先行ゼロの除去を試みるもので、ロジックを複雑にしていました。 - 新しいアプローチでは、
convertWords
は常にすべての桁を生成し、先行ゼロもそのまま含めます。そして、string
メソッドの最後で、生成された文字列の先頭から最初の非ゼロ文字を見つけるまでインデックスを進めることで、一度にすべての先行ゼロを除去します。
この変更は、i = 0 for zero := charset[0]; s[i] == zero; { i++ } return string(s[i:])
convertWords
の内部ロジックを大幅に簡素化し、条件分岐を減らすことで、コードの保守性を高めています。コミットメッセージが示唆するように、leafSize
(再帰的分割の閾値)が小さいため、一時的に先行ゼロを保持することによるパフォーマンス上のペナルティは最小限に抑えられています。
- 以前は、
-
コメントの明確化:
- 「direct」を「iterative」に、「indirect」を「recursive」に置き換えることで、アルゴリズムの性質がより正確に伝わるようになりました。これは、コードを理解しようとする開発者にとって非常に重要です。
これらの変更は、コードの簡素化とパフォーマンスの向上という二重の目標を達成しています。特に、スライス操作の活用と先行ゼロ処理の一元化は、Go言語のイディオムに沿ったクリーンな実装を実現しています。ベンチマーク結果は、これらの変更が特に大きな数値の文字列変換において、実質的な速度向上をもたらしたことを裏付けています。
関連リンク
- Go CL 5418047: https://golang.org/cl/5418047/
- Go CL 5432090: https://golang.org/cl/5432090 (このコミットのChange List)
参考にした情報源リンク
特になし (コミットメッセージとコード差分から直接情報を抽出)