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

[インデックス 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 のロジックを改善しています。

  1. string 関数の MaxBase チェックの変更:

    • 変更前: case b < 2 || MaxBase < b:
    • 変更後: case b < 2 || MaxBase > 256:
    • これにより、string 関数が受け入れる基数の上限が MaxBase から256に明確に変更されました。これは、charset の長さが256を超えることはないという前提に基づいている可能性があります。
  2. 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] のようなスライス再スライス操作を行うことで、データのコピーをせずに効率的に部分配列を扱えるようになり、コードが簡潔になりました。
  3. 先行ゼロの扱い:

    • 変更前は、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 が小さいため、この「後で除去する」アプローチによるオーバーヘッドは無視できるレベルであると判断されています。
  4. コメントのクリーンアップと用語の統一:

    • convertWords 関数のコメントにおいて、「direct」という用語が「iterative」(反復的)に、「indirect」という用語が「recursive」(再帰的)に置き換えられました。これにより、アルゴリズムの性質がより正確に表現され、コードの可読性が向上しました。
    • leafSize の説明も「indirect conversion」から「recursive conversion」に修正されています。
  5. パフォーマンスの改善:

    • コミットメッセージに含まれるベンチマーク結果は、この変更が math/big の文字列変換性能に与えた影響を示しています。
    • big.BenchmarkString100000Base2 では約7.55%の改善、big.BenchmarkString100000Base8 では約7.52%の改善、big.BenchmarkString100000Base16 では約7.97%の改善が見られます。
    • 一方で、big.BenchmarkString10Base2big.BenchmarkString10Base8big.BenchmarkString10Base16 のように、一部の小さい数値の変換ではわずかな性能低下(1-2%程度)が見られますが、全体としては「slightly faster overall for -bench=String」というコミットメッセージの記述と一致しています。これは、大きな数値の変換における最適化が、小さな数値の変換におけるわずかなオーバーヘッドを上回る効果をもたらしたことを示唆しています。
  6. 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 メソッドの改善にあります。

  1. convertWords のシグネチャ変更とスライス操作:

    • 以前の convertWords(lo, hi int, s []byte, ...) では、s のどの範囲に結果を書き込むかを lohi で明示的に指定していました。これは、関数が呼び出されるたびにこれらの境界値を適切に管理する必要があり、コードが複雑になる原因でした。
    • 新しい convertWords(s []byte, ...) では、s スライス自体が「現在処理している部分」を表すように変更されました。例えば、再帰呼び出しの際に r.convertWords(s[h:], ...) のようにスライスの部分を渡すことで、その部分スライスが新しい関数の s 引数となり、関数内部では常に s の先頭から書き込みを開始できるため、境界値の管理が不要になります。これにより、コードがより直感的で簡潔になりました。
  2. 先行ゼロの処理の簡素化:

    • 以前は、convertWords の中で「最上位の桁グループの先行ゼロをスキップする」という条件付きロジックがありました。これは、変換の途中で部分的な先行ゼロの除去を試みるもので、ロジックを複雑にしていました。
    • 新しいアプローチでは、convertWords は常にすべての桁を生成し、先行ゼロもそのまま含めます。そして、string メソッドの最後で、生成された文字列の先頭から最初の非ゼロ文字を見つけるまでインデックスを進めることで、一度にすべての先行ゼロを除去します。
      i = 0
      for zero := charset[0]; s[i] == zero; {
          i++
      }
      return string(s[i:])
      
      この変更は、convertWords の内部ロジックを大幅に簡素化し、条件分岐を減らすことで、コードの保守性を高めています。コミットメッセージが示唆するように、leafSize(再帰的分割の閾値)が小さいため、一時的に先行ゼロを保持することによるパフォーマンス上のペナルティは最小限に抑えられています。
  3. コメントの明確化:

    • 「direct」を「iterative」に、「indirect」を「recursive」に置き換えることで、アルゴリズムの性質がより正確に伝わるようになりました。これは、コードを理解しようとする開発者にとって非常に重要です。

これらの変更は、コードの簡素化とパフォーマンスの向上という二重の目標を達成しています。特に、スライス操作の活用と先行ゼロ処理の一元化は、Go言語のイディオムに沿ったクリーンな実装を実現しています。ベンチマーク結果は、これらの変更が特に大きな数値の文字列変換において、実質的な速度向上をもたらしたことを裏付けています。

関連リンク

参考にした情報源リンク

特になし (コミットメッセージとコード差分から直接情報を抽出)