[インデックス 10823] ファイルの概要
このコミットは、Go言語のfmt
パッケージにおける浮動小数点数(float)の書式設定処理のパフォーマンス改善とコードの整理を目的としています。特に、%g
フォーマットにおけるメモリ割り当て(mallocs)の回数を4回から2回に削減し、それに伴う速度向上を実現しています。また、ブーリアン値や文字列の16進数表現、文字定数の書式設定に関するコードも整理されています。
コミット
commit 04faa08c070191a7c187e4ff3a9eb7ed9b6b38a6
Author: Rob Pike <r@golang.org>
Date: Thu Dec 15 12:52:29 2011 -0800
fmt: speed up floating point print, clean up some code
%g down to two mallocs from four. Also a mild speedup.
fmt_test.BenchmarkSprintfFloat 3016 2703 -10.38%
Fixes #2557.
R=rsc
CC=golang-dev
https://golang.org/cl/5491054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/04faa08c070191a7c187e4ff3a9eb7ed9b6b38a6
元コミット内容
fmt: speed up floating point print, clean up some code
%g down to two mallocs from four. Also a mild speedup.
fmt_test.BenchmarkSprintfFloat 3016 2703 -10.38%
Fixes #2557.
R=rsc
CC=golang-dev
https://golang.org/cl/5491054
変更の背景
この変更の主な背景は、fmt
パッケージにおける浮動小数点数の書式設定、特に%g
フォーマットのパフォーマンス改善です。元の実装では、浮動小数点数を文字列に変換する際に、不要なメモリ割り当て(malloc
)が複数回発生していました。これがパフォーマンスのボトルネックとなっていたため、メモリ割り当ての回数を削減し、処理速度を向上させることが目的でした。
コミットメッセージに記載されているベンチマーク結果「fmt_test.BenchmarkSprintfFloat 3016 2703 -10.38%
」は、この変更によってSprintfFloat
ベンチマークが約10.38%高速化されたことを示しています。
また、「Fixes #2557
」とあるように、このコミットはGoのIssue 2557を修正するものです。Issue 2557は「fmt: %g is slow
」というタイトルで、fmt.Sprintf
で%g
フォーマットを使用した場合のパフォーマンス問題が報告されていました。このコミットは、そのパフォーマンス問題を解決するための直接的な対応となります。
さらに、浮動小数点数以外の書式設定(ブーリアン値、文字列の16進数表現、文字定数)についても、コードの整理と効率化が行われています。これは、全体的なコード品質の向上と、将来的なメンテナンス性の改善に寄与します。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とfmt
パッケージの内部動作に関する知識が必要です。
fmt
パッケージ: Go言語の標準ライブラリで、フォーマットされたI/O(入出力)を実装するためのパッケージです。fmt.Printf
、fmt.Sprintf
、fmt.Print
、fmt.Println
などの関数を提供し、様々なデータ型を文字列に変換して出力する機能を持っています。fmt.Formatter
インターフェース:fmt
パッケージでカスタムの書式設定を定義するためのインターフェースです。Format(f fmt.State, c rune)
メソッドを実装することで、%v
などの動詞で型をフォーマットする際の挙動を制御できます。fmt.State
インターフェース:fmt.Formatter
インターフェースのFormat
メソッドに渡されるインターフェースで、書式設定の状態(幅、精度、フラグなど)を提供します。fmt.Printer
インターフェース:fmt
パッケージの内部で書式設定を行うためのインターフェースです。fmt.pp
構造体:fmt
パッケージの内部で、書式設定の処理状態を管理するプライベートな構造体です。fmt.fmt
構造体:fmt.pp
構造体から呼び出される、具体的な書式設定ロジックをカプセル化するプライベートな構造体です。この構造体には、書式設定に必要なフラグ(plus
、space
など)、幅、精度、そして内部バッファ(intbuf
)などが含まれます。fmt.padString
メソッド:fmt.fmt
構造体のメソッドで、与えられた文字列を指定された幅とパディングフラグに従ってパディングし、出力バッファに書き込む役割を担っていました。fmt.pad
メソッド:fmt.padString
の代わりに導入された、バイトスライスを直接パディングして出力バッファに書き込むメソッドです。文字列(string
)からバイトスライスへの変換に伴う余分なメモリ割り当てを避けることができます。strconv
パッケージ: 文字列と数値の変換を行うための標準ライブラリです。strconv.FormatFloat
: 浮動小数点数を指定された書式('e'
、'f'
、'g'
など)、精度、ビットサイズ(32ビットまたは64ビット)で文字列に変換する関数です。strconv.AppendFloat
:strconv.FormatFloat
と同様に浮動小数点数を文字列に変換しますが、既存のバイトスライスに結果を追記する形式です。これにより、新しい文字列を生成するためのメモリ割り当てを削減できます。strconv.QuoteRune
: Unicodeルーン(文字)をGoの文字定数形式(例:'a'
、'\u00e9'
)で引用符付き文字列に変換します。strconv.AppendQuoteRune
:strconv.QuoteRune
と同様にルーンを引用符付き文字列に変換しますが、既存のバイトスライスに結果を追記します。strconv.QuoteRuneToASCII
: UnicodeルーンをASCII文字のみで表現されるGoの文字定数形式(例:'a'
、'\x00e9'
)で引用符付き文字列に変換します。strconv.AppendQuoteRuneToASCII
:strconv.QuoteRuneToASCII
と同様にルーンを引用符付き文字列に変換しますが、既存のバイトスライスに結果を追記します。
bytes.Buffer
: 可変長のバイトバッファを提供する構造体です。バイトスライスを効率的に構築するために使用されます。malloc
(メモリ割り当て): プログラムが実行時に動的にメモリを要求する操作です。malloc
の回数が多いと、そのオーバーヘッド(メモリの検索、割り当て、解放)によりパフォーマンスが低下する可能性があります。特に、小さなオブジェクトを頻繁に割り当てる場合に顕著です。ldigits
/udigits
: 16進数変換に使用される、小文字/大文字の16進数文字('0'
から'9'
、'a'
から'f'
または'A'
から'F'
)を含むバイトスライスまたは文字列です。float64
/float32
: Go言語における浮動小数点数型です。float64
は倍精度、float32
は単精度です。complex64
: Go言語における複素数型です。verb
:fmt
パッケージの書式設定動詞(例:%e
、%f
、%g
、%x
など)を指します。prec
(精度): 浮動小数点数の書式設定における小数点以下の桁数や有効数字の桁数を指定するオプションです。n
(ビットサイズ): 浮動小数点数のビットサイズ(32または64)を指定します。space
フラグ (%
): 数値が負でない場合に、符号の代わりにスペースを挿入する書式設定フラグです。plus
フラグ (%+
): 数値が負でない場合に、常にプラス記号を挿入する書式設定フラグです。
技術的詳細
このコミットにおける技術的な改善点は多岐にわたりますが、主な焦点はメモリ割り当ての削減と効率的なバッファ利用です。
-
ブーリアン値の書式設定の改善:
- 変更前は
f.padString("true")
やf.padString("false")
のように、文字列リテラルを直接渡していました。 - 変更後は、
trueBytes
とfalseBytes
という[]byte
型のグローバル変数を定義し、f.pad(trueBytes)
やf.pad(falseBytes)
のように、バイトスライスを直接渡すように変更されました。 - これにより、
padString
内部で文字列からバイトスライスへの変換が不要になり、その分のオーバーヘッドが削減されます。
- 変更前は
-
文字列の16進数書式設定(
fmt_sx
)の改善:- 変更前は、
fmt_sx
(小文字16進数)とfmt_sX
(大文字16進数)がそれぞれ独立した関数として存在し、内部でstring
型のt
を構築し、最後にf.padString(t)
を呼び出していました。文字列の結合はGoでは新しい文字列を生成するため、メモリ割り当てが頻繁に発生します。 - 変更後は、
fmt_sx
関数にdigits
という引数を追加し、小文字/大文字の16進数文字を共通の関数で処理できるようにしました。 - 最も重要な変更は、
bytes.Buffer
を導入したことです。bytes.Buffer
は可変長のバイトスライスを効率的に構築できるため、文字列の結合による頻繁なメモリ割り当てを避けることができます。 - 最終的に
f.pad(b.Bytes())
を呼び出すことで、bytes.Buffer
の内容を直接fmt
構造体の内部バッファに書き込みます。これにより、中間的な文字列生成とそれに伴うメモリ割り当てが削減されます。
- 変更前は、
-
文字定数の書式設定(
fmt_qc
)の改善:- 変更前は
strconv.QuoteRune
やstrconv.QuoteRuneToASCII
が返したstring
型の結果をf.padString
に渡していました。 - 変更後は、
strconv.AppendQuoteRune
やstrconv.AppendQuoteRuneToASCII
を使用するように変更されました。これらの関数は、既存のバイトスライス(ここではf.intbuf[0:0]
、つまりf.intbuf
の先頭から0バイトのスライス)に結果を追記します。 - これにより、
strconv
関数が新しい文字列を割り当てるのではなく、fmt
構造体内部の既存のバッファを再利用できるようになり、メモリ割り当てが削減されます。
- 変更前は
-
浮動小数点数の書式設定の抜本的な改善(
formatFloat
の導入):- これがこのコミットの最も重要な変更点です。変更前は、
fmt_e64
、fmt_E64
などの各浮動小数点数書式設定関数が、それぞれstrconv.FormatFloat
を呼び出してstring
型の結果を受け取り、その結果をf.plusSpace
に渡していました。f.plusSpace
はさらにその文字列を加工し、最終的にf.padString
を呼び出していました。この一連の処理で複数の文字列生成とメモリ割り当てが発生していました。 - 新しい
formatFloat
ヘルパー関数が導入されました。この関数は、strconv.AppendFloat
を使用して、fmt
構造体内部のf.intbuf
(整数書式設定用のバッファ)に直接浮動小数点数の書式設定結果を書き込みます。 f.intbuf[0:1]
をstrconv.AppendFloat
の最初の引数として渡すことで、f.intbuf
の先頭1バイトを符号(+
または- 符号の処理も
formatFloat
内で直接行われます。strconv.AppendFloat
が返したバイトスライス(slice
)の先頭バイトをチェックし、必要に応じて+
またはplusSpace
関数が不要になり、中間的な文字列操作がなくなります。 - 最終的に
f.pad(slice)
を呼び出すことで、f.intbuf
に直接書き込まれたバイトスライスをパディングして出力バッファに書き込みます。 - この変更により、浮動小数点数の書式設定における
malloc
の回数が大幅に削減され、パフォーマンスが向上しました。特に%g
フォーマットでは、4回のmalloc
が2回に削減されたとコミットメッセージに記載されています。
- これがこのコミットの最も重要な変更点です。変更前は、
これらの変更は、Go言語のfmt
パッケージが、文字列操作において可能な限りバイトスライスを直接扱い、既存のバッファを再利用することで、メモリ割り当てのオーバーヘッドを最小限に抑えるという設計思想を反映しています。
コアとなるコードの変更箇所
diff --git a/src/pkg/fmt/format.go b/src/pkg/fmt/format.go
index 5f62c067f0..78d9e998b1 100644
--- a/src/pkg/fmt/format.go
+++ b/src/pkg/fmt/format.go
@@ -154,12 +154,17 @@ func putint(buf []byte, base, val uint64, digits string) int {
return i - 1
}
+var (
+ trueBytes = []byte("true")
+ falseBytes = []byte("false")
+)
+
// fmt_boolean formats a boolean.
func (f *fmt) fmt_boolean(v bool) {
if v {
-\t\tf.padString("true")
+\t\tf.pad(trueBytes)
} else {
-\t\tf.padString("false")
+\t\tf.pad(falseBytes)
}
}
@@ -283,31 +288,18 @@ func (f *fmt) fmt_s(s string) {
}
// fmt_sx formats a string as a hexadecimal encoding of its bytes.
-func (f *fmt) fmt_sx(s string) {
-\tt := ""
+func (f *fmt) fmt_sx(s, digits string) {
+ // TODO: Avoid buffer by pre-padding.
+ var b bytes.Buffer
for i := 0; i < len(s); i++ {
if i > 0 && f.space {
-\t\t\tt += " "
+\t\t\tb.WriteByte(' ')
}
v := s[i]
-\t\tt += string(ldigits[v>>4])
-\t\t\tt += string(ldigits[v&0xF])
+\t\t\tb.WriteByte(digits[v>>4])
+\t\t\tb.WriteByte(digits[v&0xF])
}
-\tf.padString(t)
-}
-
-// fmt_sX formats a string as an uppercase hexadecimal encoding of its bytes.
-func (f *fmt) fmt_sX(s string) {
-\tt := ""
-\tfor i := 0; i < len(s); i++ {
-\t\tif i > 0 && f.space {
-\t\t\tt += " "
-\t\t}
-\t\tv := s[i]
-\t\tt += string(udigits[v>>4])
-\t\tt += string(udigits[v&0xF])
-\t}\n-\tf.padString(t)
+\tf.pad(b.Bytes())
}
// fmt_q formats a string as a double-quoted, escaped Go string constant.
@@ -329,13 +321,13 @@ func (f *fmt) fmt_q(s string) {
// fmt_qc formats the integer as a single-quoted, escaped Go character constant.
// If the character is not valid Unicode, it will print '\ufffd'.
func (f *fmt) fmt_qc(c int64) {
-\tvar quoted string
+\tvar quoted []byte
if f.plus {
-\t\tquoted = strconv.QuoteRuneToASCII(rune(c))
+\t\tquoted = strconv.AppendQuoteRuneToASCII(f.intbuf[0:0], rune(c))
} else {
-\t\tquoted = strconv.QuoteRune(rune(c))
+\t\tquoted = strconv.AppendQuoteRune(f.intbuf[0:0], rune(c))
}\n-\tf.padString(quoted)
+\tf.pad(quoted)
}
// floating-point
@@ -347,57 +339,70 @@ func doPrec(f *fmt, def int) int {
return def
}
-// Add a plus sign or space to the floating-point string representation if missing and required.\n-func (f *fmt) plusSpace(s string) {\n-\tif s[0] != '-' {\n+// formatFloat formats a float64; it is an efficient equivalent to f.pad(strconv.FormatFloat()...).\n+func (f *fmt) formatFloat(v float64, verb byte, prec, n int) {\n+\t// We leave one byte at the beginning of f.intbuf for a sign if needed,\n+\t// and make it a space, which we might be able to use.\n+\tf.intbuf[0] = ' '\n+\tslice := strconv.AppendFloat(f.intbuf[0:1], v, verb, prec, n)\n+\t// Add a plus sign or space to the floating-point string representation if missing and required.\n+\t// The formatted number starts at slice[1].\n+\tswitch slice[1] {\n+\tcase '-', '+':\n+\t\t// We're set; drop the leading space.\n+\t\tslice = slice[1:]\n+\tdefault:\n+\t\t// There's no sign, but we might need one.\n \t\tif f.plus {\n-\t\t\ts = "+" + s\n+\t\t\tslice[0] = '+'\n \t\t} else if f.space {\n-\t\t\ts = " " + s\n+\t\t\t// space is already there\n+\t\t} else {\n+\t\t\tslice = slice[1:]\n \t\t}\n \t}\n-\tf.padString(s)\n+\tf.pad(slice)\n }
// fmt_e64 formats a float64 in the form -1.23e+12.
-func (f *fmt) fmt_e64(v float64) { f.plusSpace(strconv.FormatFloat(v, 'e', doPrec(f, 6), 64)) }\n+func (f *fmt) fmt_e64(v float64) { f.formatFloat(v, 'e', doPrec(f, 6), 64) }
// fmt_E64 formats a float64 in the form -1.23E+12.
-func (f *fmt) fmt_E64(v float64) { f.plusSpace(strconv.FormatFloat(v, 'E', doPrec(f, 6), 64)) }\n+func (f *fmt) fmt_E64(v float64) { f.formatFloat(v, 'E', doPrec(f, 6), 64) }
// fmt_f64 formats a float64 in the form -1.23.
-func (f *fmt) fmt_f64(v float64) { f.plusSpace(strconv.FormatFloat(v, 'f', doPrec(f, 6), 64)) }\n+func (f *fmt) fmt_f64(v float64) { f.formatFloat(v, 'f', doPrec(f, 6), 64) }
// fmt_g64 formats a float64 in the 'f' or 'e' form according to size.
-func (f *fmt) fmt_g64(v float64) { f.plusSpace(strconv.FormatFloat(v, 'g', doPrec(f, -1), 64)) }\n+func (f *fmt) fmt_g64(v float64) { f.formatFloat(v, 'g', doPrec(f, -1), 64) }
// fmt_g64 formats a float64 in the 'f' or 'E' form according to size.
-func (f *fmt) fmt_G64(v float64) { f.plusSpace(strconv.FormatFloat(v, 'G', doPrec(f, -1), 64)) }\n+func (f *fmt) fmt_G64(v float64) { f.formatFloat(v, 'G', doPrec(f, -1), 64) }
// fmt_fb64 formats a float64 in the form -123p3 (exponent is power of 2).
-func (f *fmt) fmt_fb64(v float64) { f.plusSpace(strconv.FormatFloat(v, 'b', 0, 64)) }\n+func (f *fmt) fmt_fb64(v float64) { f.formatFloat(v, 'b', 0, 64) }
// float32
// cannot defer to float64 versions
// because it will get rounding wrong in corner cases.
// fmt_e32 formats a float32 in the form -1.23e+12.
-func (f *fmt) fmt_e32(v float32) { f.plusSpace(strconv.FormatFloat(float64(v), 'e', doPrec(f, 6), 32)) }\n+func (f *fmt) fmt_e32(v float32) { f.formatFloat(float64(v), 'e', doPrec(f, 6), 32) }
// fmt_E32 formats a float32 in the form -1.23E+12.
-func (f *fmt) fmt_E32(v float32) { f.plusSpace(strconv.FormatFloat(float64(v), 'E', doPrec(f, 6), 32)) }\n+func (f *fmt) fmt_E32(v float32) { f.formatFloat(float64(v), 'E', doPrec(f, 6), 32) }
// fmt_f32 formats a float32 in the form -1.23.
-func (f *fmt) fmt_f32(v float32) { f.plusSpace(strconv.FormatFloat(float64(v), 'f', doPrec(f, 6), 32)) }\n+func (f *fmt) fmt_f32(v float32) { f.formatFloat(v, 'f', doPrec(f, 6), 32) }
// fmt_g32 formats a float32 in the 'f' or 'e' form according to size.
-func (f *fmt) fmt_g32(v float32) { f.plusSpace(strconv.FormatFloat(float64(v), 'g', doPrec(f, -1), 32)) }\n+func (f *fmt) fmt_g32(v float32) { f.formatFloat(float64(v), 'g', doPrec(f, -1), 32) }
// fmt_G32 formats a float32 in the 'f' or 'E' form according to size.
-func (f *fmt) fmt_G32(v float32) { f.plusSpace(strconv.FormatFloat(float64(v), 'G', doPrec(f, -1), 32)) }\n+func (f *fmt) fmt_G32(v float32) { f.formatFloat(float64(v), 'G', doPrec(f, -1), 32) }
// fmt_fb32 formats a float32 in the form -123p3 (exponent is power of 2).
-func (f *fmt) fmt_fb32(v float32) { f.padString(strconv.FormatFloat(float64(v), 'b', 0, 32)) }\n+func (f *fmt) fmt_fb32(v float32) { f.formatFloat(float64(v), 'b', 0, 32) }
// fmt_c64 formats a complex64 according to the verb.
func (f *fmt) fmt_c64(v complex64, verb rune) {
diff --git a/src/pkg/fmt/print.go b/src/pkg/fmt/print.go
index 9f157daaee..3b7d3464e2 100644
--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -503,9 +503,9 @@ func (p *pp) fmtString(v string, verb rune, goSyntax bool) {
case 's':
p.fmt.fmt_s(v)
case 'x':
-\t\tp.fmt.fmt_sx(v)
+\t\tp.fmt.fmt_sx(v, ldigits)
case 'X':
-\t\tp.fmt.fmt_sX(v)
+\t\tp.fmt.fmt_sx(v, udigits)
case 'q':
p.fmt.fmt_q(v)
default:
@@ -542,9 +542,9 @@ func (p *pp) fmtBytes(v []byte, verb rune, goSyntax bool, depth int) {
case 's':
p.fmt.fmt_s(s)
case 'x':
-\t\tp.fmt.fmt_sx(s)
+\t\tp.fmt.fmt_sx(s, ldigits)
case 'X':
-\t\tp.fmt.fmt_sX(s)
+\t\tp.fmt.fmt_sx(s, udigits)
case 'q':
p.fmt.fmt_q(s)
default:
コアとなるコードの解説
src/pkg/fmt/format.go
-
ブーリアン値の書式設定 (
fmt_boolean
):- 追加:
trueBytes
とfalseBytes
という[]byte
型のグローバル変数が定義されました。これにより、"true"
や"false"
という文字列リテラルを毎回バイトスライスに変換するオーバーヘッドがなくなります。 - 変更:
f.padString("true")
/f.padString("false")
がf.pad(trueBytes)
/f.pad(falseBytes)
に変更されました。f.pad
はバイトスライスを直接受け取るため、文字列からバイトスライスへの変換が不要になり、効率が向上します。
- 追加:
-
文字列の16進数書式設定 (
fmt_sx
,fmt_sX
):- 削除:
fmt_sX
(大文字16進数)関数が削除されました。 - 変更:
fmt_sx
関数がs
(入力文字列)とdigits
(16進数文字セット、ldigits
またはudigits
)の2つの引数を受け取るように変更されました。これにより、小文字と大文字の16進数書式設定が単一の関数で処理できるようになりました。 - 追加:
var b bytes.Buffer
が導入されました。これは、16進数文字列を構築するための一時的な可変長バイトバッファです。 - 変更: ループ内で
t += " "
やt += string(...)
のように文字列結合を行っていた箇所が、b.WriteByte(' ')
やb.WriteByte(digits[v>>4])
のようにbytes.Buffer
に直接バイトを書き込む形式に変更されました。これにより、文字列結合による頻繁なメモリ割り当てが回避されます。 - 変更: 最後に
f.padString(t)
を呼び出していた箇所がf.pad(b.Bytes())
に変更されました。bytes.Buffer
の内容を直接バイトスライスとしてf.pad
に渡すことで、さらに効率化されています。
- 削除:
-
文字定数の書式設定 (
fmt_qc
):- 変更:
strconv.QuoteRune
やstrconv.QuoteRuneToASCII
が返したstring
型の結果をf.padString
に渡していた箇所が、strconv.AppendQuoteRune
やstrconv.AppendQuoteRuneToASCII
を使用するように変更されました。 - これらの
Append
系の関数は、f.intbuf[0:0]
(fmt
構造体内部の整数バッファの先頭から0バイトのスライス)を最初の引数として受け取り、そのバッファに直接引用符付き文字定数を追記します。これにより、新しい文字列の割り当てが不要になり、既存のバッファを再利用できます。 - 変更:
f.padString(quoted)
がf.pad(quoted)
に変更されました。quoted
は[]byte
型になったため、f.pad
を直接呼び出せます。
- 変更:
-
浮動小数点数の書式設定 (
plusSpace
の削除とformatFloat
の導入):- 削除:
plusSpace
ヘルパー関数が削除されました。この関数は、strconv.FormatFloat
が返した文字列を受け取り、必要に応じて+
またはf.padString
に渡していました。 - 追加:
formatFloat
という新しいヘルパー関数が導入されました。この関数は、浮動小数点数v
、書式設定動詞verb
、精度prec
、ビットサイズn
を受け取ります。 f.intbuf[0] = ' '
:f.intbuf
の先頭1バイトをスペースで初期化します。これは、符号(+
またはslice := strconv.AppendFloat(f.intbuf[0:1], v, verb, prec, n)
:strconv.AppendFloat
を呼び出し、f.intbuf
の先頭1バイト以降に浮動小数点数の書式設定結果を直接追記します。これにより、strconv.FormatFloat
が新しい文字列を割り当てるのを防ぎます。switch slice[1]
:strconv.AppendFloat
が返したslice
の2番目のバイト(f.intbuf
の2番目のバイトに相当)をチェックします。これは、実際に書式設定された数値の最初の文字です。- もし
'-'
または'+'
であれば、符号が既に存在するため、slice = slice[1:]
として先頭の予約バイト(スペース)を削除します。 - それ以外の場合(符号がない場合)は、
f.plus
フラグが設定されていればslice[0] = '+'
として先頭の予約バイトを+
に設定します。f.space
フラグが設定されていれば、既にスペースが設定されているため何もしません。どちらのフラグも設定されていなければ、slice = slice[1:]
として先頭の予約バイトを削除します。
- もし
f.pad(slice)
: 最終的に、f.intbuf
に直接書き込まれたslice
をf.pad
に渡してパディングし、出力バッファに書き込みます。- 変更:
fmt_e64
,fmt_E64
,fmt_f64
,fmt_g64
,fmt_G64
,fmt_fb64
(float64用) およびfmt_e32
,fmt_E32
,fmt_f32
,fmt_g32
,fmt_G32
,fmt_fb32
(float32用) の全ての浮動小数点数書式設定関数が、plusSpace(strconv.FormatFloat(...))
の代わりに新しく導入されたformatFloat(...)
を呼び出すように変更されました。これにより、全ての浮動小数点数書式設定において、メモリ割り当ての削減と効率化が図られました。
- 削除:
src/pkg/fmt/print.go
fmtString
およびfmtBytes
関数内の%x
、%X
処理の変更:- 変更:
p.fmt.fmt_sx(v)
/p.fmt.fmt_sX(v)
の呼び出しが、p.fmt.fmt_sx(v, ldigits)
/p.fmt.fmt_sx(v, udigits)
に変更されました。 - これは、
format.go
でfmt_sx
関数がdigits
引数を受け取るように変更されたことに対応するものです。ldigits
は小文字の16進数文字、udigits
は大文字の16進数文字を提供します。これにより、fmt_sX
関数が不要になり、コードの重複が解消されました。
- 変更:
これらの変更は、Goのfmt
パッケージが、文字列の書式設定において、可能な限りバイトスライスを直接操作し、既存のバッファを再利用することで、メモリ割り当ての回数を減らし、パフォーマンスを向上させるという設計原則を徹底していることを示しています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/04faa08c070191a7c187e4ff3a9eb7ed9b6b38a6
- Go CL (Code Review) 5491054: https://golang.org/cl/5491054
- Go Issue 2557: https://github.com/golang/go/issues/2557
参考にした情報源リンク
- Go言語の
fmt
パッケージのドキュメント: https://pkg.go.dev/fmt - Go言語の
strconv
パッケージのドキュメント: https://pkg.go.dev/strconv - Go言語の
bytes
パッケージのドキュメント: https://pkg.go.dev/bytes - Go言語のメモリ管理とガベージコレクションに関する一般的な情報 (例: Goのメモリ割り当ての仕組み、エスケープ解析など)
- Go言語のベンチマークに関する情報 (例:
go test -bench=.
)