[インデックス 13970] ファイルの概要
このコミットは、Go言語の標準ライブラリ fmt
パッケージにおける書式設定の改善と最適化に関するものです。具体的には、文字列やバイトスライスを16進数で表示する際の書式指定子 %x
に、#
(代替形式) フラグを組み合わせることを許可し、さらにバイトスライスに対して %x
を使用する際のメモリ割り当てを削減する最適化が行われています。
コミット
commit ffea835b8f18681f2736a6b88b83aa27baf0a575
Author: Rob Pike <r@golang.org>
Date: Thu Sep 27 06:21:38 2012 +1000
fmt: allow # and x together for strings
Silly and small but easy to be consistent.
To make it worthwhile, I eliminated an allocation when using
%x on a byte slice.
Fixes #4149.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6574046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ffea835b8f18681f2736a6b88b83aa27baf0a575
元コミット内容
fmt: allow # and x together for strings
Silly and small but easy to be consistent.
To make it worthwhile, I eliminated an allocation when using
%x on a byte slice.
Fixes #4149.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6574046
変更の背景
このコミットは、Go言語の fmt
パッケージにおける書式設定の柔軟性と効率性を向上させることを目的としています。
-
#
フラグと%x
の組み合わせの許可: 従来のfmt
パッケージでは、%x
(16進数表示) と#
(代替形式) フラグを文字列やバイトスライスに対して同時に使用することができませんでした。しかし、数値型に対しては0x
または0X
プレフィックスを付加するためにこの組み合わせが一般的に使用されており、文字列やバイトスライスに対しても同様の挙動が期待されることがありました。この変更は、書式設定の一貫性を高め、ユーザーの期待に応えるものです。例えば、バイトスライス[]byte{0x61, 0x62}
を%#x
でフォーマットした場合に0x610x62
のように各バイトに0x
プレフィックスが付与されるようになります。 -
バイトスライスに対する
%x
の最適化: 以前の実装では、バイトスライスを%x
でフォーマットする際に、一度string
型に変換してから処理を行っていました。このstring(v)
への変換は、新しい文字列のメモリ割り当てを伴い、特に大きなバイトスライスを扱う場合に不要なオーバーヘッドとなっていました。このコミットでは、バイトスライスを直接処理するように変更することで、この余分なメモリ割り当てを排除し、パフォーマンスを向上させています。これは、Issue #4149 で報告された問題の解決にも繋がっています。
これらの変更は、fmt
パッケージの使いやすさと効率性を同時に改善するものです。
前提知識の解説
Go言語の fmt
パッケージ
fmt
パッケージは、Go言語における書式設定I/Oを実装するためのパッケージです。C言語の printf
や scanf
に似た機能を提供し、様々なデータ型を整形して出力したり、入力からデータを解析したりすることができます。
書式指定子 (Format Verbs)
fmt
パッケージでは、値をどのように表示するかを制御するために「書式指定子」と呼ばれる特殊な文字を使用します。書式指定子は %
で始まり、その後に続く文字(verb)によって表示形式が決定されます。
%s
: 文字列として値を表示します。%x
: 値を小文字の16進数として表示します。バイトスライスや文字列の場合、各バイトが2桁の16進数で表現されます。%X
: 値を大文字の16進数として表示します。%q
: Goの構文に沿った引用符付き文字列として表示します。
フラグ (Flags)
書式指定子には、追加の表示オプションを制御するための「フラグ」を付加することができます。フラグは %
と verb の間に配置されます。
#
(代替形式):- 数値型の場合: 16進数 (
%x
,%X
) では0x
または0X
プレフィックスを付加します。8進数 (%o
) では0
プレフィックスを付加します。 - このコミット以前は、文字列やバイトスライスに対する
%x
では#
フラグはサポートされていませんでした。このコミットにより、各バイトの16進数表現に0x
または0X
プレフィックスが付加されるようになります。
- 数値型の場合: 16進数 (
- 数値型の場合: 符号なしの数値の前にスペースを挿入します。
- このコミットでは、バイトスライスや文字列の16進数表示において、各バイトの16進数表現の間にスペースを挿入する挙動が維持されています。
バイトスライス ([]byte
)
[]byte
は、Go言語におけるバイトのシーケンスを表すスライス型です。ファイルの内容、ネットワークからのデータ、暗号化されたデータなど、バイナリデータを扱う際によく使用されます。
メモリ割り当て (Memory Allocation)
プログラムが実行時にメモリを要求し、オペレーティングシステムからそのメモリが割り当てられるプロセスです。Go言語ではガベージコレクションによって不要になったメモリは自動的に解放されますが、不必要なメモリ割り当てはパフォーマンスのオーバーヘッド(CPU時間、メモリ使用量)を引き起こす可能性があります。特にループ内で頻繁にメモリ割り当てが行われると、パフォーマンスに大きな影響を与えることがあります。
技術的詳細
このコミットの技術的な変更点は、主に src/pkg/fmt/format.go
と src/pkg/fmt/print.go
の2つのファイルに集中しています。
1. fmt_sx
関数の汎用化と fmt_sbx
の導入
以前の fmt_sx
関数は、文字列 (string
) を16進数にフォーマットするために使用されていました。この関数は内部で string
を []byte
に変換し、そのバイトを処理していました。
このコミットでは、fmt_sx
のロジックをより汎用的な fmt_sbx
関数に抽出し、fmt_sbx
が string
と []byte
の両方を処理できるように変更されました。
fmt_sbx(s string, b []byte, digits string)
:- この新しい関数は、
s
(文字列) またはb
(バイトスライス) のいずれかを受け取ります。どちらか一方がnil
であることを前提としています。 n
は、処理するバイト数を示します。b
がnil
でなければlen(b)
、そうでなければlen(s)
となります。x
は、#
フラグが指定された場合に0x
または0X
プレフィックスを生成するために使用される文字 ('x'
または'X'
) を決定します。- ループ内で各バイトを処理する際に、以下のロジックが追加されました。
f.space
がtrue
(スペースフラグが指定されている) かつi > 0
(最初のバイトではない) の場合、buf
にスペースを追加します。f.sharp
がtrue
(代替形式フラグが指定されている) の場合、buf
に'0'
とx
(例:'x'
または'X'
) を追加して0x
または0X
プレフィックスを生成します。- 処理対象のバイト
c
は、b
がnil
でなければb[i]
から、そうでなければs[i]
から取得されます。 buf
にc
の16進数表現(2桁)を追加します。
- 最終的に、整形されたバイト列
buf
をf.pad
メソッドに渡して、パディングなどの最終処理を行います。
- この新しい関数は、
2. fmt_sx
と fmt_bx
のラッパー関数化
fmt_sx(s, digits string)
:- この関数は、
fmt_sbx(s, nil, digits)
を呼び出すラッパー関数となりました。これにより、文字列の16進数フォーマットは新しい汎用関数に委譲されます。
- この関数は、
fmt_bx(b []byte, digits string)
:- この新しい関数は、
fmt_sbx("", b, digits)
を呼び出すラッパー関数です。これにより、バイトスライスの16進数フォーマットが直接fmt_sbx
で処理されるようになり、string(v)
への不要な変換がなくなりました。
- この新しい関数は、
3. print.go
における fmtBytes
の変更
src/pkg/fmt/print.go
の fmtBytes
関数は、バイトスライスをフォーマットする際のディスパッチロジックを担当しています。
- 以前は、
%x
や%X
の場合、バイトスライスv
をs := string(v)
として文字列に変換してからp.fmt.fmt_sx(s, ...)
を呼び出していました。 - このコミットでは、この
string(v)
への変換が削除され、代わりにp.fmt.fmt_bx(v, ...)
が直接呼び出されるようになりました。これにより、バイトスライスがfmt_sbx
に直接渡され、不要なメモリ割り当てが回避されます。
4. fmt_test.go
におけるテストケースの追加
変更された機能の検証のために、src/pkg/fmt/fmt_test.go
に新しいテストケースが追加されました。
- バイトスライスに対する
%#x
および%#X
のテストケースが追加され、0x
または0X
プレフィックスが正しく付加されることを確認しています。 - スペースフラグ (
#
フラグの組み合わせ (%# x
,%# X
) のテストケースも追加され、両方のフラグが正しく機能することを確認しています。 Stringer
インターフェースを実装するカスタム型I
に対しても、%#x
および%# x
のテストケースが追加され、同様の挙動が期待されることを確認しています。
これらの変更により、fmt
パッケージはより柔軟で効率的な書式設定機能を提供できるようになりました。
コアとなるコードの変更箇所
src/pkg/fmt/format.go
--- a/src/pkg/fmt/format.go
+++ b/src/pkg/fmt/format.go
@@ -285,18 +285,41 @@ func (f *fmt) fmt_s(s string) {
f.padString(s)
}
-// fmt_sx formats a string as a hexadecimal encoding of its bytes.
-func (f *fmt) fmt_sx(s, digits string) {
+// fmt_sbx formats a string or byte slice as a hexadecimal encoding of its bytes.
+func (f *fmt) fmt_sbx(s string, b []byte, digits string) {
+ n := len(b)
+ if b == nil {
+ n = len(s)
+ }
+ x := digits[10] - 'a' + 'x'
// TODO: Avoid buffer by pre-padding.
- var b []byte
- for i := 0; i < len(s); i++ {
+ var buf []byte
+ for i := 0; i < n; i++ {
if i > 0 && f.space {
- b = append(b, ' ')
+ buf = append(buf, ' ')
+ }
+ if f.sharp {
+ buf = append(buf, '0', x)
+ }
+ var c byte
+ if b == nil {
+ c = s[i]
+ } else {
+ c = b[i]
}
- v := s[i]
- b = append(b, digits[v>>4], digits[v&0xF])
+ buf = append(buf, digits[c>>4], digits[c&0xF])
}
- f.pad(b)
+ f.pad(buf)
+}
+
+// fmt_sx formats a string as a hexadecimal encoding of its bytes.
+func (f *fmt) fmt_sx(s, digits string) {
+ f.fmt_sbx(s, nil, digits)
+}
+
+// fmt_bx formats a byte slice as a hexadecimal encoding of its bytes.
+func (f *fmt) fmt_bx(b []byte, digits string) {
+ f.fmt_sbx("", b, digits)
}
// fmt_q formats a string as a double-quoted, escaped Go string constant.
src/pkg/fmt/print.go
--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -569,16 +569,15 @@ func (p *pp) fmtBytes(v []byte, verb rune, goSyntax bool, depth int) {
}
return
}
-\ts := string(v)
\tswitch verb {\n \tcase \'s\':
-\t\tp.fmt.fmt_s(s)\n+\t\tp.fmt.fmt_s(string(v))\n \tcase \'x\':
-\t\tp.fmt.fmt_sx(s, ldigits)\n+\t\tp.fmt.fmt_bx(v, ldigits)\n \tcase \'X\':
-\t\tp.fmt.fmt_sx(s, udigits)\n+\t\tp.fmt.fmt_bx(v, udigits)\n \tcase \'q\':
-\t\tp.fmt.fmt_q(s)\n+\t\tp.fmt.fmt_q(string(v))\n \tdefault:\n \t\tp.badVerb(verb)\n \t}\n```
### `src/pkg/fmt/fmt_test.go`
```diff
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -127,6 +127,10 @@ var fmttests = []struct {
{"%s", []byte("abc"), "abc"},
{"%x", []byte("abc"), "616263"},
{"% x", []byte("abc\xff"), "61 62 63 ff"},
+\t{"%#x", []byte("abc\xff"), "0x610x620x630xff"},
+\t{"%#X", []byte("abc\xff"), "0X610X620X630XFF"},
+\t{"%# x", []byte("abc\xff"), "0x61 0x62 0x63 0xff"},
+\t{"%# X", []byte("abc\xff"), "0X61 0X62 0X63 0XFF"},
{"% X", []byte("abc\xff"), "61 62 63 FF"},
{"%x", []byte("xyz"), "78797a"},
{"%X", []byte("xyz"), "78797A"},
@@ -350,10 +354,12 @@ var fmttests = []struct {
{"%+v", B{1, 2}, `{I:<1> j:2}`},
{"%+v", C{1, B{2, 3}}, `{i:1 B:{I:<2> j:3}}`},
-\t// q on Stringable items
+\t// other formats on Stringable items
{"%s", I(23), `<23>`},
{"%q", I(23), `"<23>"`},
{"%x", I(23), `3c32333e`},
+\t{"%#x", I(23), `0x3c0x320x330x3e`},
+\t{"%# x", I(23), `0x3c 0x32 0x33 0x3e`},
{"%d", I(23), `23`}, // Stringer applies only to string formats.
// go syntax
コアとなるコードの解説
src/pkg/fmt/format.go
の変更
fmt_sbx
関数の追加:- この関数は、文字列 (
s
) またはバイトスライス (b
) のいずれかを受け取り、その内容を16進数形式でフォーマットする汎用的なロジックをカプセル化します。 n
は、処理すべき要素の数を決定します。バイトスライスが優先され、nil
の場合は文字列の長さが使用されます。x
変数は、#
フラグが指定された場合に0x
または0X
プレフィックスを生成するために、digits
文字列から適切な文字 ('x'
または'X'
) を抽出します。- ループ内で、各バイトの16進数表現を
buf
に追加します。 f.space
フラグが設定されている場合、各バイトの間にスペースが挿入されます。f.sharp
フラグが設定されている場合、各バイトの16進数表現の前に0x
または0X
が追加されます。これが、#
フラグと%x
の組み合わせを可能にする主要な変更点です。c
変数には、現在処理中のバイトが、b
(バイトスライス) またはs
(文字列) のどちらから取得されるかに応じて格納されます。- 最終的に、
f.pad(buf)
を呼び出して、パディングなどの最終的な書式設定を適用します。
- この関数は、文字列 (
fmt_sx
の変更:- 以前は文字列を直接処理していましたが、
fmt_sbx(s, nil, digits)
を呼び出すように変更され、fmt_sbx
に処理を委譲します。
- 以前は文字列を直接処理していましたが、
fmt_bx
の追加:- この新しい関数は、バイトスライスを16進数でフォーマットするためのもので、
fmt_sbx("", b, digits)
を呼び出すことで、バイトスライスを直接fmt_sbx
に渡します。これにより、バイトスライスをstring
に変換する不要なメモリ割り当てがなくなりました。
- この新しい関数は、バイトスライスを16進数でフォーマットするためのもので、
src/pkg/fmt/print.go
の変更
fmtBytes
関数の変更:fmtBytes
関数は、fmt
パッケージがバイトスライスを処理する際のディスパッチポイントです。- 以前は、
%x
や%X
の書式指定子の場合、バイトスライスv
をs := string(v)
として文字列に変換してからp.fmt.fmt_sx(s, ...)
を呼び出していました。 - このコミットでは、
s := string(v)
の行が削除され、代わりにp.fmt.fmt_bx(v, ...)
が直接呼び出されるようになりました。 これが、バイトスライスに対する%x
フォーマットにおけるメモリ割り当て削減の具体的な実装箇所です。バイトスライスが直接fmt_bx
に渡され、そこからfmt_sbx
に渡されるため、中間的な文字列変換が不要になります。 %s
と%q
のケースでも、string(v)
への変換は残っていますが、これはこれらの書式指定子が文字列を期待するためです。
src/pkg/fmt/fmt_test.go
の変更
- 新しいテストケースが追加され、
#
フラグとx
(またはX
) 書式指定子を組み合わせた場合のバイトスライスおよびStringer
インターフェースを実装するカスタム型の挙動が検証されています。これにより、0x
または0X
プレフィックスが正しく付加されること、およびスペースフラグとの組み合わせも正しく機能することが確認されます。
これらの変更により、fmt
パッケージはより一貫性のある書式設定オプションを提供し、同時に特定のシナリオでのパフォーマンスを向上させています。
関連リンク
- Go言語
fmt
パッケージのドキュメント: https://pkg.go.dev/fmt - Go言語の
string
と[]byte
の変換に関する議論: https://go.dev/blog/strings (このコミットの直接的な背景ではないが、関連する概念) - Go言語のIssue #4149: https://github.com/golang/go/issues/4149 (このコミットで修正された問題)
参考にした情報源リンク
- Go言語の公式ドキュメント
- GitHubのコミット履歴と差分表示
- Go言語のソースコード
- Go言語のIssueトラッカー