[インデックス 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トラッカー