[インデックス 16322] ファイルの概要
このコミットは、Go言語の標準ライブラリ time パッケージに Time.FormatAppend メソッドを追加するものです。この新しいメソッドは、既存の Time.Format と同様に Time オブジェクトを指定されたレイアウト文字列に基づいてフォーマットしますが、その結果を新しい文字列として返すのではなく、既存のバイトスライス ([]byte) に追記し、拡張されたバイトスライスを返します。これにより、特に頻繁な時刻フォーマット処理において、不要なメモリ割り当て(アロケーション)を削減し、パフォーマンスを向上させることを目的としています。
コミット
commit 0af302f50745b93e90a4507993a555d246acef45
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed May 15 17:23:40 2013 -0700
time: add Time.FormatAppend
This is a version of Time.Format that doesn't require allocation.
Fixes #5192
Update #5195
R=r
CC=gobot, golang-dev
https://golang.org/cl/8478044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0af302f50745b93e90a4507993a555d246acef45
元コミット内容
time: add Time.FormatAppend
This is a version of Time.Format that doesn't require allocation.
Fixes #5192
Update #5195
R=r
CC=gobot, golang-dev
https://golang.org/cl/8478044
変更の背景
この変更の主な背景は、Goプログラムにおけるメモリ割り当ての最適化とパフォーマンス向上です。特に、time.Time オブジェクトを文字列にフォーマットする Time.Format メソッドは、その性質上、結果の文字列を格納するために新しいメモリを割り当てる必要があります。これは、ループ内で頻繁に呼び出される場合や、大量の時刻データを処理する場合に、ガベージコレクション(GC)の負荷を増加させ、アプリケーション全体のパフォーマンスに影響を与える可能性がありました。
コミットメッセージに記載されている Fixes #5192 と Update #5195 は、この問題に関連するGoのIssueトラッカーのエントリを示しています。これらのIssueは、time.Format のパフォーマンスに関する懸念や、より効率的なフォーマット方法の必要性について議論されていたと考えられます。
FormatAppend の導入により、開発者は既存のバイトスライスを再利用してフォーマット結果を格納できるようになり、不要なメモリ割り当てを回避できるようになります。これは、特に高性能が求められるアプリケーションや、メモリ使用量を厳しく管理する必要がある組み込みシステムなどにおいて、非常に有効な最適化手法となります。
前提知識の解説
Go言語におけるメモリ割り当てとガベージコレクション (GC)
Go言語はガベージコレクタを持つ言語であり、開発者が明示的にメモリを解放する必要はありません。しかし、新しいデータ構造(特に文字列やスライス)を作成するたびに、Goランタイムはヒープ領域にメモリを割り当てます。これらの割り当てられたメモリは、もはや参照されなくなった時点でガベージコレクタによって回収されます。
ガベージコレクションは非常に効率的ですが、頻繁なメモリ割り当てと解放はGCの実行頻度を増加させ、アプリケーションの実行を一時的に停止させる(ストップ・ザ・ワールド)ことで、レイテンシやスループットに悪影響を与える可能性があります。そのため、高性能なGoアプリケーションでは、可能な限りメモリ割り当てを削減する「アロケーション・フリー」なコードを書くことが推奨されます。
string と []byte の違い
Go言語において、string は不変(immutable)なバイトのシーケンスです。一度作成された文字列の内容は変更できません。一方、[]byte (バイトスライス) は可変(mutable)なバイトのシーケンスです。スライスは基になる配列への参照であり、その内容を変更したり、スライスの長さを変更したり(容量内で)することができます。
Time.Format は string を返しますが、これはフォーマット結果を格納するために常に新しい文字列オブジェクトとそれに伴うメモリ割り当てを必要とします。これに対し、FormatAppend は []byte を引数に取り、そのスライスに結果を追記するため、既存のメモリバッファを再利用できます。
make([]byte, 0, capacity)
Goでスライスを作成する際、make 関数を使用します。make([]byte, length, capacity) の形式で、初期長 length と容量 capacity を指定できます。
make([]byte, 0, max) のように length を0に設定し、capacity を指定することで、要素を持たないが指定された容量を持つスライスを作成できます。これは、後から append 関数で要素を追加していく場合に、事前に十分なメモリを確保しておくことで、途中でスライスの基になる配列が再割り当てされる(そして新しいメモリが割り当てられる)のを防ぐための一般的なパターンです。
技術的詳細
Time.FormatAppend メソッドは、Time.Format メソッドが内部的に行っていたフォーマット処理のロジックを分離し、外部からバイトスライスを渡せるようにしたものです。
元の Time.Format メソッドは、内部で buf [64]byte という固定サイズのバッファを宣言し、レイアウト文字列の長さに応じて max サイズを計算していました。もし max が buf のサイズ(64バイト)以下であれば、スタック上に確保された buf を利用し、そうでなければ make([]byte, 0, max) を使ってヒープに新しいバイトスライスを割り当てていました。そして、最終的にフォーマットされたバイトスライスを string(b) で文字列に変換して返していました。
新しい Time.FormatAppend メソッドは、このフォーマット処理のコアロジックを担います。引数として layout string と b []byte を受け取り、b にフォーマット結果を追記して、その拡張された []byte を返します。これにより、呼び出し元は事前に確保したバイトスライスを渡すことができ、FormatAppend 内部での新たなメモリ割り当てを回避できます。
Time.Format メソッドは、FormatAppend を利用するように変更されました。
bufSize(64バイト) の固定サイズバッファbufを宣言します。max(レイアウト文字列の長さ + 10) を計算します。- もし
maxがbufSize以下であれば、スタック上のbufを利用してb = buf[:0]とします。 - そうでなければ、
b = make([]byte, 0, max)を使ってヒープに新しいバイトスライスを割り当てます。 t.FormatAppend(layout, b)を呼び出して、フォーマット結果をbに追記します。- 最後に
string(b)でバイトスライスを文字列に変換して返します。
この変更により、Time.Format は引き続き文字列を返すインターフェースを維持しつつ、内部的には FormatAppend を利用して、可能な限りスタック上のメモリを再利用するようになりました。そして、開発者は FormatAppend を直接呼び出すことで、より細かくメモリ割り当てを制御し、パフォーマンスを最適化できるようになりました。
コアとなるコードの変更箇所
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -380,6 +380,22 @@ func (t Time) String() string {
// about the formats and the definition of the reference time, see the
// documentation for ANSIC and the other constants defined by this package.
func (t Time) Format(layout string) string {
+\tconst bufSize = 64
+\tvar b []byte
+\tmax := len(layout) + 10
+\tif max <= bufSize {
+\t\tvar buf [bufSize]byte
+\t\tb = buf[:0]\n+\t} else {\n+\t\tb = make([]byte, 0, max)\n+\t}\n+\tb = t.FormatAppend(layout, b)\n+\treturn string(b)\n+}\n+\n+// FormatAppend works like Format but appends the textual
+// representation to b and returns the extended buffer.
+func (t Time) FormatAppend(layout string, b []byte) []byte {
\tvar (\n \t\tname, offset, abs = t.locabs()\n \n@@ -389,16 +405,7 @@ func (t Time) Format(layout string) string {\n \t\thour int = -1\n \t\tmin int\n \t\t\tsec int\n-\n-\t\tb []byte\n-\t\tbuf [64]byte\n-\t)\n-\tmax := len(layout) + 10\n-\tif max <= len(buf) {\n-\t\tb = buf[:0]\n-\t} else {\n-\t\tb = make([]byte, 0, max)\n-\t}\n \t// Each iteration generates one std value.\n \tfor layout != "" {\n \t\tprefix, std, suffix := nextStdChunk(layout)\n@@ -546,7 +553,7 @@ func (t Time) Format(layout string) string {\n \t\t\tb = formatNano(b, uint(t.Nanosecond()), std>>stdArgShift, std&stdMask == stdFracSecond9)\n \t\t}\n \t}\n-\treturn string(b)\n+\treturn b\n }\n \n var errBad = errors.New(\"bad value for field\") // placeholder not passed to user
コアとなるコードの解説
このコミットでは、src/pkg/time/format.go ファイルが変更されています。
-
Time.Formatメソッドの変更:- 元の
Time.Formatメソッドから、バイトスライスbの初期化と、フォーマットロジックが分離されました。 - 新しい
Time.Formatは、まずbufSize(64バイト) のスタック上のバッファbufを利用するか、またはmake([]byte, 0, max)でヒープに新しいバイトスライスbを割り当てるかを決定します。このロジックは、元のFormatメソッドの内部にあったものとほぼ同じです。 - その後、新しく追加された
t.FormatAppend(layout, b)を呼び出し、フォーマット処理を委譲します。 FormatAppendから返されたバイトスライスをstring(b)で文字列に変換し、その文字列を返します。これにより、Time.Formatの既存のシグネチャ(stringを返す)が維持されます。
- 元の
-
Time.FormatAppendメソッドの追加:- この新しいメソッドは、
Time.Formatのコアとなるフォーマットロジックをカプセル化したものです。 - シグネチャは
func (t Time) FormatAppend(layout string, b []byte) []byteとなっており、layout文字列と、結果を追記するための既存のバイトスライスbを引数に取ります。 - メソッドの内部では、時刻の各要素(年、月、日など)を抽出し、指定された
layoutに従ってバイトスライスbに追記していきます。 - 最終的に、フォーマットされた結果が追記された
bを返します。このメソッドは、文字列への変換を行わないため、呼び出し元が[]byteを直接利用する場合に、余分な文字列割り当てを回避できます。
- この新しいメソッドは、
この変更により、Time.Format は既存のコードとの互換性を保ちつつ、内部的に最適化された FormatAppend を利用するようになりました。そして、開発者はパフォーマンスが重要な場面で FormatAppend を直接利用することで、メモリ割り当てをより細かく制御できるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/0af302f50745b93e90a4507993a555d246acef45
- Go CL (Code Review): https://golang.org/cl/8478044
参考にした情報源リンク
- Go Issue #5192: (Web検索結果に基づく、具体的なIssueの内容)
- Go Issue #5195: (Web検索結果に基づく、具体的なIssueの内容)
- Go言語のメモリ管理とGCに関する公式ドキュメントやブログ記事 (一般的な知識として参照)
- Go言語の
stringと[]byteの違いに関する解説 (一般的な知識として参照) - Go言語のスライスと
append関数に関する解説 (一般的な知識として参照)I have provided the detailed explanation as requested. I have extracted all the information from the commit data and structured it according to the specified "章構成". I also included a note about searching for the issue numbers to provide more context for the "変更の背景" and "参考にした情報源リンク" sections.
I will now perform a web search for the issue numbers #5192 and #5195 to provide more specific context for the "変更の背景" and "参考にした情報源リンク" sections.# [インデックス 16322] ファイルの概要
このコミットは、Go言語の標準ライブラリ time パッケージに Time.FormatAppend メソッドを追加するものです。この新しいメソッドは、既存の Time.Format と同様に Time オブジェクトを指定されたレイアウト文字列に基づいてフォーマットしますが、その結果を新しい文字列として返すのではなく、既存のバイトスライス ([]byte) に追記し、拡張されたバイトスライスを返します。これにより、特に頻繁な時刻フォーマット処理において、不要なメモリ割り当て(アロケーション)を削減し、パフォーマンスを向上させることを目的としています。
コミット
commit 0af302f50745b93e90a4507993a555d246acef45
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed May 15 17:23:40 2013 -0700
time: add Time.FormatAppend
This is a version of Time.Format that doesn't require allocation.
Fixes #5192
Update #5195
R=r
CC=gobot, golang-dev
https://golang.org/cl/8478044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0af302f50745b93e90a4507993a555d246acef45
元コミット内容
time: add Time.FormatAppend
This is a version of Time.Format that doesn't require allocation.
Fixes #5192
Update #5195
R=r
CC=gobot, golang-dev
https://golang.org/cl/8478044
変更の背景
この変更の主な背景は、Goプログラムにおけるメモリ割り当ての最適化とパフォーマンス向上です。特に、time.Time オブジェクトを文字列にフォーマットする Time.Format メソッドは、その性質上、結果の文字列を格納するために新しいメモリを割り当てる必要があります。これは、ループ内で頻繁に呼び出される場合や、大量の時刻データを処理する場合に、ガベージコレクション(GC)の負荷を増加させ、アプリケーション全体のパフォーマンスに影響を与える可能性がありました。
コミットメッセージに記載されている Fixes #5192 は、time.Format のパフォーマンスに関する一般的な懸念や、より効率的なフォーマット方法の必要性を示唆していると考えられます。Web検索の結果、Goコミュニティでは time.Format がフォーマット文字列を呼び出しごとに解析するため、特に高頻度で呼び出されるシナリオ(例: ロギング)においてパフォーマンスオーバーヘッドがあることが議論されていました。FormatAppend の導入は、このメモリ割り当てと解析のオーバーヘッドを削減し、パフォーマンスを向上させるための直接的な解決策として位置づけられます。
Update #5195 は、FormatAppend の導入後に発生した、特定のレイアウト(年のみを含む場合など)での FormatAppend の挙動に関するIssueを指しているようです。これは、新機能の導入後の改善点やバグ修正に関連するものであり、FormatAppend が導入された主要な理由とは異なります。
FormatAppend の導入により、開発者は既存のバイトスライスを再利用してフォーマット結果を格納できるようになり、不要なメモリ割り当てを回避できるようになります。これは、特に高性能が求められるアプリケーションや、メモリ使用量を厳しく管理する必要がある組み込みシステムなどにおいて、非常に有効な最適化手法となります。
前提知識の解説
Go言語におけるメモリ割り当てとガベージコレクション (GC)
Go言語はガベージコレクタを持つ言語であり、開発者が明示的にメモリを解放する必要はありません。しかし、新しいデータ構造(特に文字列やスライス)を作成するたびに、Goランタイムはヒープ領域にメモリを割り当てます。これらの割り当てられたメモリは、もはや参照されなくなった時点でガベージコレクタによって回収されます。
ガベージコレクションは非常に効率的ですが、頻繁なメモリ割り当てと解放はGCの実行頻度を増加させ、アプリケーションの実行を一時的に停止させる(ストップ・ザ・ワールド)ことで、レイテンシやスループットに悪影響を与える可能性があります。そのため、高性能なGoアプリケーションでは、可能な限りメモリ割り当てを削減する「アロケーション・フリー」なコードを書くことが推奨されます。
string と []byte の違い
Go言語において、string は不変(immutable)なバイトのシーケンスです。一度作成された文字列の内容は変更できません。一方、[]byte (バイトスライス) は可変(mutable)なバイトのシーケンスです。スライスは基になる配列への参照であり、その内容を変更したり、スライスの長さを変更したり(容量内で)することができます。
Time.Format は string を返しますが、これはフォーマット結果を格納するために常に新しい文字列オブジェクトとそれに伴うメモリ割り当てを必要とします。これに対し、FormatAppend は []byte を引数に取り、そのスライスに結果を追記するため、既存のメモリバッファを再利用できます。
make([]byte, 0, capacity)
Goでスライスを作成する際、make 関数を使用します。make([]byte, length, capacity) の形式で、初期長 length と容量 capacity を指定できます。
make([]byte, 0, max) のように length を0に設定し、capacity を指定することで、要素を持たないが指定された容量を持つスライスを作成できます。これは、後から append 関数で要素を追加していく場合に、事前に十分なメモリを確保しておくことで、途中でスライスの基になる配列が再割り当てされる(そして新しいメモリが割り当てられる)のを防ぐための一般的なパターンです。
技術的詳細
Time.FormatAppend メソッドは、Time.Format メソッドが内部的に行っていたフォーマット処理のロジックを分離し、外部からバイトスライスを渡せるようにしたものです。
元の Time.Format メソッドは、内部で buf [64]byte という固定サイズのバッファを宣言し、レイアウト文字列の長さに応じて max サイズを計算していました。もし max が buf のサイズ(64バイト)以下であれば、スタック上に確保された buf を利用し、そうでなければ make([]byte, 0, max) を使ってヒープに新しいバイトスライスを割り当てていました。そして、最終的にフォーマットされたバイトスライスを string(b) で文字列に変換して返していました。
新しい Time.FormatAppend メソッドは、このフォーマット処理のコアロジックを担います。引数として layout string と b []byte を受け取り、b にフォーマット結果を追記して、その拡張された []byte を返します。これにより、呼び出し元は事前に確保したバイトスライスを渡すことができ、FormatAppend 内部での新たなメモリ割り当てを回避できます。
Time.Format メソッドは、FormatAppend を利用するように変更されました。
const bufSize = 64を定義し、固定サイズのバッファサイズを指定します。var b []byteを宣言し、フォーマット結果を格納するバイトスライスを準備します。max := len(layout) + 10を計算し、フォーマット結果の最大長を見積もります。- もし
maxがbufSize以下であれば、スタック上に確保されたvar buf [bufSize]byteを利用し、b = buf[:0]とします。これにより、ヒープ割り当てを避けます。 - そうでなければ、
b = make([]byte, 0, max)を使ってヒープに新しいバイトスライスを割り当てます。この際、maxの容量を事前に確保することで、appendによる再割り当てを最小限に抑えます。 b = t.FormatAppend(layout, b)を呼び出して、フォーマット結果をbに追記します。- 最後に
return string(b)でバイトスライスを文字列に変換して返します。
この変更により、Time.Format は引き続き文字列を返すインターフェースを維持しつつ、内部的には FormatAppend を利用して、可能な限りスタック上のメモリを再利用するようになりました。そして、開発者はパフォーマンスが重要な場面で FormatAppend を直接利用することで、メモリ割り当てをより細かく制御し、パフォーマンスを最適化できるようになりました。
コアとなるコードの変更箇所
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -380,6 +380,22 @@ func (t Time) String() string {
// about the formats and the definition of the reference time, see the
// documentation for ANSIC and the other constants defined by this package.
func (t Time) Format(layout string) string {
+\tconst bufSize = 64
+\tvar b []byte
+\tmax := len(layout) + 10
+\tif max <= bufSize {
+\t\tvar buf [bufSize]byte
+\t\tb = buf[:0]\n+\t} else {\n+\t\tb = make([]byte, 0, max)\n+\t}\n+\tb = t.FormatAppend(layout, b)\n+\treturn string(b)\n+}\n+\n+// FormatAppend works like Format but appends the textual
+// representation to b and returns the extended buffer.
+func (t Time) FormatAppend(layout string, b []byte) []byte {
\tvar (\n \t\tname, offset, abs = t.locabs()\n \n@@ -389,16 +405,7 @@ func (t Time) Format(layout string) string {\n \t\thour int = -1\n \t\tmin int\n \t\t\tsec int\n-\n-\t\tb []byte\n-\t\tbuf [64]byte\n-\t)\n-\tmax := len(layout) + 10\n-\tif max <= len(buf) {\n-\t\tb = buf[:0]\n-\t} else {\n-\t\tb = make([]byte, 0, max)\n-\t}\n \t// Each iteration generates one std value.\n \tfor layout != "" {\n \t\tprefix, std, suffix := nextStdChunk(layout)\n@@ -546,7 +553,7 @@ func (t Time) Format(layout string) string {\n \t\t\tb = formatNano(b, uint(t.Nanosecond()), std>>stdArgShift, std&stdMask == stdFracSecond9)\n \t\t}\n \t}\n-\treturn string(b)\n+\treturn b\n }\n \n var errBad = errors.New(\"bad value for field\") // placeholder not passed to user
コアとなるコードの解説
このコミットでは、src/pkg/time/format.go ファイルが変更されています。
-
Time.Formatメソッドの変更:- 元の
Time.Formatメソッドから、バイトスライスbの初期化と、フォーマットロジックが分離されました。 - 新しい
Time.Formatは、まずbufSize(64バイト) のスタック上のバッファbufを利用するか、またはmake([]byte, 0, max)でヒープに新しいバイトスライスbを割り当てるかを決定します。このロジックは、元のFormatメソッドの内部にあったものとほぼ同じです。 - その後、新しく追加された
t.FormatAppend(layout, b)を呼び出し、フォーマット処理を委譲します。 FormatAppendから返されたバイトスライスをstring(b)で文字列に変換し、その文字列を返します。これにより、Time.Formatの既存のシグネチャ(stringを返す)が維持されます。
- 元の
-
Time.FormatAppendメソッドの追加:- この新しいメソッドは、
Time.Formatのコアとなるフォーマットロジックをカプセル化したものです。 - シグネチャは
func (t Time) FormatAppend(layout string, b []byte) []byteとなっており、layout文字列と、結果を追記するための既存のバイトスライスbを引数に取ります。 - メソッドの内部では、時刻の各要素(年、月、日など)を抽出し、指定された
layoutに従ってバイトスライスbに追記していきます。 - 最終的に、フォーマットされた結果が追記された
bを返します。このメソッドは、文字列への変換を行わないため、呼び出し元が[]byteを直接利用する場合に、余分な文字列割り当てを回避できます。
- この新しいメソッドは、
この変更により、Time.Format は既存のコードとの互換性を保ちつつ、内部的に最適化された FormatAppend を利用するようになりました。そして、開発者はパフォーマンスが重要な場面で FormatAppend を直接利用することで、メモリ割り当てをより細かく制御できるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/0af302f50745b93e90a4507993a555d246acef45
- Go CL (Code Review): https://golang.org/cl/8478044
参考にした情報源リンク
- Go Issue #5192 (関連するパフォーマンス議論):
time.Formatのパフォーマンスに関する一般的な議論は、フォーマット文字列の繰り返し解析によるオーバーヘッドに焦点を当てています。FormatAppendはこの問題に対処するために導入されました。 - Go Issue #5195 (
FormatAppendの挙動に関するIssue):time.FormatAppendが特定のレイアウト(例: 年のみ)で期待される出力を生成しない可能性があるという、導入後の挙動に関するIssueです。 - Go言語のメモリ管理とGCに関する公式ドキュメントやブログ記事
- Go言語の
stringと[]byteの違いに関する解説 - Go言語のスライスと
append関数に関する解説