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

[インデックス 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 パッケージにおける書式設定の柔軟性と効率性を向上させることを目的としています。

  1. # フラグと %x の組み合わせの許可: 従来の fmt パッケージでは、%x (16進数表示) と # (代替形式) フラグを文字列やバイトスライスに対して同時に使用することができませんでした。しかし、数値型に対しては 0x または 0X プレフィックスを付加するためにこの組み合わせが一般的に使用されており、文字列やバイトスライスに対しても同様の挙動が期待されることがありました。この変更は、書式設定の一貫性を高め、ユーザーの期待に応えるものです。例えば、バイトスライス []byte{0x61, 0x62}%#x でフォーマットした場合に 0x610x62 のように各バイトに 0x プレフィックスが付与されるようになります。

  2. バイトスライスに対する %x の最適化: 以前の実装では、バイトスライスを %x でフォーマットする際に、一度 string 型に変換してから処理を行っていました。この string(v) への変換は、新しい文字列のメモリ割り当てを伴い、特に大きなバイトスライスを扱う場合に不要なオーバーヘッドとなっていました。このコミットでは、バイトスライスを直接処理するように変更することで、この余分なメモリ割り当てを排除し、パフォーマンスを向上させています。これは、Issue #4149 で報告された問題の解決にも繋がっています。

これらの変更は、fmt パッケージの使いやすさと効率性を同時に改善するものです。

前提知識の解説

Go言語の fmt パッケージ

fmt パッケージは、Go言語における書式設定I/Oを実装するためのパッケージです。C言語の printfscanf に似た機能を提供し、様々なデータ型を整形して出力したり、入力からデータを解析したりすることができます。

書式指定子 (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進数表現の間にスペースを挿入する挙動が維持されています。

バイトスライス ([]byte)

[]byte は、Go言語におけるバイトのシーケンスを表すスライス型です。ファイルの内容、ネットワークからのデータ、暗号化されたデータなど、バイナリデータを扱う際によく使用されます。

メモリ割り当て (Memory Allocation)

プログラムが実行時にメモリを要求し、オペレーティングシステムからそのメモリが割り当てられるプロセスです。Go言語ではガベージコレクションによって不要になったメモリは自動的に解放されますが、不必要なメモリ割り当てはパフォーマンスのオーバーヘッド(CPU時間、メモリ使用量)を引き起こす可能性があります。特にループ内で頻繁にメモリ割り当てが行われると、パフォーマンスに大きな影響を与えることがあります。

技術的詳細

このコミットの技術的な変更点は、主に src/pkg/fmt/format.gosrc/pkg/fmt/print.go の2つのファイルに集中しています。

1. fmt_sx 関数の汎用化と fmt_sbx の導入

以前の fmt_sx 関数は、文字列 (string) を16進数にフォーマットするために使用されていました。この関数は内部で string[]byte に変換し、そのバイトを処理していました。

このコミットでは、fmt_sx のロジックをより汎用的な fmt_sbx 関数に抽出し、fmt_sbxstring[]byte の両方を処理できるように変更されました。

  • fmt_sbx(s string, b []byte, digits string):
    • この新しい関数は、s (文字列) または b (バイトスライス) のいずれかを受け取ります。どちらか一方が nil であることを前提としています。
    • n は、処理するバイト数を示します。bnil でなければ len(b)、そうでなければ len(s) となります。
    • x は、# フラグが指定された場合に 0x または 0X プレフィックスを生成するために使用される文字 ('x' または 'X') を決定します。
    • ループ内で各バイトを処理する際に、以下のロジックが追加されました。
      • f.spacetrue (スペースフラグが指定されている) かつ i > 0 (最初のバイトではない) の場合、buf にスペースを追加します。
      • f.sharptrue (代替形式フラグが指定されている) の場合、buf'0'x (例: 'x' または 'X') を追加して 0x または 0X プレフィックスを生成します。
      • 処理対象のバイト c は、bnil でなければ b[i] から、そうでなければ s[i] から取得されます。
      • bufc の16進数表現(2桁)を追加します。
    • 最終的に、整形されたバイト列 buff.pad メソッドに渡して、パディングなどの最終処理を行います。

2. fmt_sxfmt_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.gofmtBytes 関数は、バイトスライスをフォーマットする際のディスパッチロジックを担当しています。

  • 以前は、%x%X の場合、バイトスライス vs := 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 に変換する不要なメモリ割り当てがなくなりました。

src/pkg/fmt/print.go の変更

  • fmtBytes 関数の変更:
    • fmtBytes 関数は、fmt パッケージがバイトスライスを処理する際のディスパッチポイントです。
    • 以前は、%x%X の書式指定子の場合、バイトスライス vs := 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言語の公式ドキュメント
  • GitHubのコミット履歴と差分表示
  • Go言語のソースコード
  • Go言語のIssueトラッカー