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

[インデックス 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.Printffmt.Sprintffmt.Printfmt.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構造体から呼び出される、具体的な書式設定ロジックをカプセル化するプライベートな構造体です。この構造体には、書式設定に必要なフラグ(plusspaceなど)、幅、精度、そして内部バッファ(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フラグ (%+): 数値が負でない場合に、常にプラス記号を挿入する書式設定フラグです。

技術的詳細

このコミットにおける技術的な改善点は多岐にわたりますが、主な焦点はメモリ割り当ての削減と効率的なバッファ利用です。

  1. ブーリアン値の書式設定の改善:

    • 変更前はf.padString("true")f.padString("false")のように、文字列リテラルを直接渡していました。
    • 変更後は、trueBytesfalseBytesという[]byte型のグローバル変数を定義し、f.pad(trueBytes)f.pad(falseBytes)のように、バイトスライスを直接渡すように変更されました。
    • これにより、padString内部で文字列からバイトスライスへの変換が不要になり、その分のオーバーヘッドが削減されます。
  2. 文字列の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構造体の内部バッファに書き込みます。これにより、中間的な文字列生成とそれに伴うメモリ割り当てが削減されます。
  3. 文字定数の書式設定(fmt_qc)の改善:

    • 変更前はstrconv.QuoteRunestrconv.QuoteRuneToASCIIが返したstring型の結果をf.padStringに渡していました。
    • 変更後は、strconv.AppendQuoteRunestrconv.AppendQuoteRuneToASCIIを使用するように変更されました。これらの関数は、既存のバイトスライス(ここではf.intbuf[0:0]、つまりf.intbufの先頭から0バイトのスライス)に結果を追記します。
    • これにより、strconv関数が新しい文字列を割り当てるのではなく、fmt構造体内部の既存のバッファを再利用できるようになり、メモリ割り当てが削減されます。
  4. 浮動小数点数の書式設定の抜本的な改善(formatFloatの導入):

    • これがこのコミットの最も重要な変更点です。変更前は、fmt_e64fmt_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

  1. ブーリアン値の書式設定 (fmt_boolean):

    • 追加: trueBytesfalseBytesという[]byte型のグローバル変数が定義されました。これにより、"true""false"という文字列リテラルを毎回バイトスライスに変換するオーバーヘッドがなくなります。
    • 変更: f.padString("true") / f.padString("false")f.pad(trueBytes) / f.pad(falseBytes) に変更されました。f.padはバイトスライスを直接受け取るため、文字列からバイトスライスへの変換が不要になり、効率が向上します。
  2. 文字列の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に渡すことで、さらに効率化されています。
  3. 文字定数の書式設定 (fmt_qc):

    • 変更: strconv.QuoteRunestrconv.QuoteRuneToASCIIが返したstring型の結果をf.padStringに渡していた箇所が、strconv.AppendQuoteRunestrconv.AppendQuoteRuneToASCIIを使用するように変更されました。
    • これらのAppend系の関数は、f.intbuf[0:0]fmt構造体内部の整数バッファの先頭から0バイトのスライス)を最初の引数として受け取り、そのバッファに直接引用符付き文字定数を追記します。これにより、新しい文字列の割り当てが不要になり、既存のバッファを再利用できます。
    • 変更: f.padString(quoted)f.pad(quoted) に変更されました。quoted[]byte型になったため、f.padを直接呼び出せます。
  4. 浮動小数点数の書式設定 (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に直接書き込まれたslicef.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

  1. 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.gofmt_sx関数がdigits引数を受け取るように変更されたことに対応するものです。ldigitsは小文字の16進数文字、udigitsは大文字の16進数文字を提供します。これにより、fmt_sX関数が不要になり、コードの重複が解消されました。

これらの変更は、Goのfmtパッケージが、文字列の書式設定において、可能な限りバイトスライスを直接操作し、既存のバッファを再利用することで、メモリ割り当ての回数を減らし、パフォーマンスを向上させるという設計原則を徹底していることを示しています。

関連リンク

参考にした情報源リンク

  • 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=.)