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

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

このコミットは、Go言語の標準ライブラリである math/big パッケージ内の nat.go ファイルに対する様々なクリーンアップと最適化を目的としています。math/big パッケージは、任意精度の算術演算(大きな整数や有理数)を扱うための機能を提供します。nat.go は、その中でも特に非負整数(natural numbers)の内部表現と操作に関するロジックを実装しています。

コミット

commit b7c5e23df02fd0acf8f3c3a1d26024c90983684e
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Jun 13 09:37:47 2012 -0700

    math/big: various cleanups
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/6295072

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

https://github.com/golang/go/commit/b7c5e23df02fd0acf8f3c3a1d26024c90983684e

元コミット内容

このコミットの元の内容は、math/big パッケージにおけるいくつかのコードの整理と改善です。具体的には、div メソッドにおける剰余の処理の簡素化、trailingZeroBits 関数におけるパニックメッセージの修正、そして random メソッドにおける乱数生成ロジックの最適化と堅牢化が含まれます。

変更の背景

この変更の背景には、math/big パッケージのコードベースの品質向上とパフォーマンス最適化があります。

  1. div メソッドの簡素化: div メソッドは除算を処理しますが、特に除数が単一の「ワード」(Word型、通常は uint または uint64)である場合の剰余の扱いが冗長でした。この冗長性を排除し、より簡潔で効率的なコードにすることが目的でした。
  2. trailingZeroBits のパニックメッセージ修正: これは純粋にスタイルの統一と、より一貫性のあるエラーメッセージを提供するための修正です。
  3. random メソッドの最適化と堅牢化: random メソッドは、指定された範囲内でランダムな大きな整数を生成します。このメソッド内で、システムのワードサイズ(32ビットまたは64ビット)に応じた乱数生成ロジックが、ループ内で繰り返しチェックされていました。このチェックをループの外に出すことで、不要な条件分岐のオーバーヘッドを削減し、パフォーマンスを向上させることが意図されました。また、未知のワードサイズに対する panic 処理を追加することで、将来的な拡張性や予期せぬ環境での堅牢性を高めています。

これらの変更は、Go言語の標準ライブラリが常に高い品質と効率性を維持するための継続的な取り組みの一環として行われました。

前提知識の解説

このコミットを理解するためには、以下の概念が役立ちます。

  • math/big パッケージ: Go言語の標準ライブラリで、任意精度の数値演算を提供します。通常の intuint 型では扱えない非常に大きな整数や、正確な有理数を扱う際に使用されます。
  • nat: math/big パッケージ内で定義されている型で、非負の大きな整数を内部的に表現するために使用されます。通常、Word 型のスライス(配列)として実装されており、各要素が数値の一部(桁)を表します。
  • Word: math/big パッケージ内で定義されている、プラットフォームのワードサイズ(32ビットまたは64ビット)に対応する符号なし整数型です。通常は uint または uint64 のエイリアスとして定義されます。大きな整数を扱う際に、この Word を基本単位として演算が行われます。
  • _W 定数: math/big パッケージ内で使用される内部的な定数で、現在のシステムが32ビットワードを使用しているか、64ビットワードを使用しているかを示します。これはコンパイル時に決定されるか、実行環境の特性に基づいて設定されます。
  • divW メソッド: nat 型のメソッドで、nat 型の数値を単一の Word で除算する操作を行います。
  • setWord メソッド: nat 型のメソッドで、単一の Word から nat 型の数値を生成します。このメソッドは、入力 Word が0の場合に空の nat を返すなど、適切な初期化を行う責任があります。
  • rand.Rand: Go言語の math/rand パッケージで提供される乱数ジェネレータです。

技術的詳細

このコミットで行われた技術的な変更は、主に以下の3点です。

  1. div メソッドにおける剰余処理の改善:

    • 変更前は、v(除数)の長さが1の場合、divW メソッドで得られた剰余 rprime が0より大きいかどうかをチェックし、その結果に基づいて r(結果の剰余)を z2.make(1) で作成して rprime を代入するか、z2.make(0) で空の nat を作成していました。
    • 変更後は、z2.setWord(r2) を直接呼び出すようになりました。これは、setWord メソッドが内部的に r2 の値(0か否か)に応じて適切な nat を生成するロジックを持っているため、呼び出し側での条件分岐が不要になったことを意味します。これにより、コードがより簡潔になり、可読性が向上しました。また、setWord の実装によっては、不要なメモリ割り当てを避ける最適化も期待できます。
  2. trailingZeroBits のパニックメッセージの修正:

    • panic("Unknown word size") から panic("unknown word size") へと変更されました。これは機能的な変更ではなく、単にエラーメッセージの文字列リテラルにおける大文字・小文字の修正です。Go言語の慣習や他のエラーメッセージとの一貫性を保つための、純粋なコードスタイルのクリーンアップです。
  3. random メソッドにおけるワードサイズチェックの最適化と堅牢化:

    • 変更前は、for i := range z ループ(nat の各ワードを処理するループ)の内側switch _W ステートメントが実行されていました。これは、_W が通常はコンパイル時に決定される定数であるにもかかわらず、ループの各イテレーションでワードサイズチェックが繰り返し行われることを意味します。
    • 変更後は、switch _W ステートメントが for i := range z ループの外側に移動されました。これにより、ワードサイズチェックは random メソッドが呼び出された際に一度だけ行われ、その結果に基づいて適切な乱数生成ロジックが選択されます。これにより、ループ内の不要な条件分岐が削減され、特に大きな nat を生成する際のパフォーマンスが向上します。
    • さらに、default: ケースが追加され、panic("unknown word size") が呼び出されるようになりました。これにより、将来的に32ビットや64ビット以外のワードサイズがサポートされた場合や、予期せぬ環境で _W が未知の値になった場合に、プログラムが未定義の動作をするのではなく、明確なエラーで終了するようになり、堅牢性が向上しました。

これらの変更は、Go言語のコードベースでよく見られる、パフォーマンスとコードの簡潔さを両立させるための典型的な最適化とクリーンアップの例です。

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

src/pkg/math/big/nat.go

--- a/src/pkg/math/big/nat.go
+++ b/src/pkg/math/big/nat.go
@@ -493,14 +493,9 @@ func (z nat) div(z2, u, v nat) (q, r nat) {
 	}
 
 	if len(v) == 1 {
-		var rprime Word
-		q, rprime = z.divW(u, v[0])
-		if rprime > 0 {
-			r = z2.make(1)
-			r[0] = rprime
-		} else {
-			r = z2.make(0)
-		}
+		var r2 Word
+		q, r2 = z.divW(u, v[0])
+		r = z2.setWord(r2)
 		return
 	}
 
@@ -1011,7 +1006,7 @@ func trailingZeroBits(x Word) uint {
 	case 64:
 		return uint(deBruijn64Lookup[((x&-x)*(deBruijn64&_M))>>58])
 	default:
-		panic("Unknown word size")
+		panic("unknown word size")
 	}
 
 	return 0
@@ -1198,17 +1193,19 @@ func (z nat) random(rand *rand.Rand, limit nat, n int) nat {
 	mask := Word((1 << bitLengthOfMSW) - 1)
 
 	for {
-		for i := range z {
-			switch _W {
-			case 32:
+		switch _W {
+		case 32:
+			for i := range z {
 				z[i] = Word(rand.Uint32())
-			case 64:
+			}
+		case 64:
+			for i := range z {
 				z[i] = Word(rand.Uint32()) | Word(rand.Uint32())<<32
 			}
+		default:
+			panic("unknown word size")
 		}
-
 		z[len(limit)-1] &= mask
-
 		if z.cmp(limit) < 0 {
 			break
 		}

コアとなるコードの解説

func (z nat) div(z2, u, v nat) (q, r nat) の変更点

この関数は、uv で除算し、商 q と剰余 r を計算します。変更された部分は、除数 v の長さが1(つまり単一の Word)である場合の特殊なケースです。

  • 変更前:

    		var rprime Word
    		q, rprime = z.divW(u, v[0]) // uをv[0]で除算し、商qと剰余rprimeを得る
    		if rprime > 0 {
    			r = z2.make(1) // 剰余が0でなければ、長さ1のnatを作成
    			r[0] = rprime
    		} else {
    			r = z2.make(0) // 剰余が0であれば、長さ0のnatを作成
    		}
    

    ここでは、rprime の値に応じて r の初期化方法を条件分岐していました。z2.make(1) は1つの Word を保持する nat を作成し、z2.make(0) は空の nat を作成します。

  • 変更後:

    		var r2 Word
    		q, r2 = z.divW(u, v[0]) // uをv[0]で除算し、商qと剰余r2を得る
    		r = z2.setWord(r2)      // r2から直接natを作成
    

    setWord メソッドは、単一の Word から nat を作成するユーティリティ関数です。この関数は、入力された Word が0であれば空の nat を、0でなければその Word を含む nat を適切に生成します。これにより、呼び出し側での冗長な条件分岐が不要になり、コードがより簡潔になりました。

func trailingZeroBits(x Word) uint の変更点

この関数は、Word 型の数値 x の最下位ビットから数えて、連続する0のビット数を返します。

  • 変更前: panic("Unknown word size")
  • 変更後: panic("unknown word size") これは、switch ステートメントの default ケースで発生するパニックメッセージの文字列リテラルにおける大文字・小文字の修正です。機能的な影響はありません。

func (z nat) random(rand *rand.Rand, limit nat, n int) nat の変更点

この関数は、指定された limit 未満のランダムな nat 型の数値を生成します。

  • 変更前:

    	for {
    		for i := range z { // natの各ワードをループ
    			switch _W { // ループ内でワードサイズをチェック
    			case 32:
    				z[i] = Word(rand.Uint32())
    			case 64:
    				z[i] = Word(rand.Uint32()) | Word(rand.Uint32())<<32
    			}
    		}
    		// ... (後続の処理)
    	}
    

    _W はシステムのワードサイズを示す定数であり、その値は実行中に変化しません。したがって、for i := range z ループの各イテレーションで switch _W を実行するのは冗長でした。

  • 変更後:

    	for {
    		switch _W { // ループの外でワードサイズを一度だけチェック
    		case 32:
    			for i := range z { // 32ビットワード用の乱数生成
    				z[i] = Word(rand.Uint32())
    			}
    		case 64:
    			for i := range z { // 64ビットワード用の乱数生成
    				z[i] = Word(rand.Uint32()) | Word(rand.Uint32())<<32
    			}
    		default:
    			panic("unknown word size") // 未知のワードサイズに対する堅牢な処理
    		}
    		// ... (後続の処理)
    	}
    

    switch _W ステートメントが外側の for ループの直下に移動されました。これにより、ワードサイズに応じた乱数生成ロジックの選択が一度だけ行われ、内側の for i := range z ループではその選択されたロジックが繰り返し実行されるため、パフォーマンスが向上します。 また、default: ケースが追加されたことで、32ビットや64ビット以外のワードサイズが将来的に登場した場合でも、プログラムが予期せぬ動作をする代わりに、明確なエラーで終了するようになり、コードの堅牢性が高まりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特に src/math/big/nat.go
  • Gerrit Change-ID: https://golang.org/cl/6295072 (これはGoプロジェクトのコードレビューシステムへのリンクであり、このコミットに関する議論や詳細な変更履歴が含まれている可能性があります。)
  • 一般的なプログラミングにおける任意精度算術の概念
  • Go言語におけるパフォーマンス最適化の一般的なプラクティス