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

[インデックス 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 #5192Update #5195 は、この問題に関連するGoのIssueトラッカーのエントリを示しています。これらのIssueは、time.Format のパフォーマンスに関する懸念や、より効率的なフォーマット方法の必要性について議論されていたと考えられます。

FormatAppend の導入により、開発者は既存のバイトスライスを再利用してフォーマット結果を格納できるようになり、不要なメモリ割り当てを回避できるようになります。これは、特に高性能が求められるアプリケーションや、メモリ使用量を厳しく管理する必要がある組み込みシステムなどにおいて、非常に有効な最適化手法となります。

前提知識の解説

Go言語におけるメモリ割り当てとガベージコレクション (GC)

Go言語はガベージコレクタを持つ言語であり、開発者が明示的にメモリを解放する必要はありません。しかし、新しいデータ構造(特に文字列やスライス)を作成するたびに、Goランタイムはヒープ領域にメモリを割り当てます。これらの割り当てられたメモリは、もはや参照されなくなった時点でガベージコレクタによって回収されます。

ガベージコレクションは非常に効率的ですが、頻繁なメモリ割り当てと解放はGCの実行頻度を増加させ、アプリケーションの実行を一時的に停止させる(ストップ・ザ・ワールド)ことで、レイテンシやスループットに悪影響を与える可能性があります。そのため、高性能なGoアプリケーションでは、可能な限りメモリ割り当てを削減する「アロケーション・フリー」なコードを書くことが推奨されます。

string[]byte の違い

Go言語において、string は不変(immutable)なバイトのシーケンスです。一度作成された文字列の内容は変更できません。一方、[]byte (バイトスライス) は可変(mutable)なバイトのシーケンスです。スライスは基になる配列への参照であり、その内容を変更したり、スライスの長さを変更したり(容量内で)することができます。

Time.Formatstring を返しますが、これはフォーマット結果を格納するために常に新しい文字列オブジェクトとそれに伴うメモリ割り当てを必要とします。これに対し、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 サイズを計算していました。もし maxbuf のサイズ(64バイト)以下であれば、スタック上に確保された buf を利用し、そうでなければ make([]byte, 0, max) を使ってヒープに新しいバイトスライスを割り当てていました。そして、最終的にフォーマットされたバイトスライスを string(b) で文字列に変換して返していました。

新しい Time.FormatAppend メソッドは、このフォーマット処理のコアロジックを担います。引数として layout stringb []byte を受け取り、b にフォーマット結果を追記して、その拡張された []byte を返します。これにより、呼び出し元は事前に確保したバイトスライスを渡すことができ、FormatAppend 内部での新たなメモリ割り当てを回避できます。

Time.Format メソッドは、FormatAppend を利用するように変更されました。

  1. bufSize (64バイト) の固定サイズバッファ buf を宣言します。
  2. max (レイアウト文字列の長さ + 10) を計算します。
  3. もし maxbufSize 以下であれば、スタック上の buf を利用して b = buf[:0] とします。
  4. そうでなければ、b = make([]byte, 0, max) を使ってヒープに新しいバイトスライスを割り当てます。
  5. t.FormatAppend(layout, b) を呼び出して、フォーマット結果を b に追記します。
  6. 最後に 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 ファイルが変更されています。

  1. 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 を返す)が維持されます。
  2. Time.FormatAppend メソッドの追加:

    • この新しいメソッドは、Time.Format のコアとなるフォーマットロジックをカプセル化したものです。
    • シグネチャは func (t Time) FormatAppend(layout string, b []byte) []byte となっており、layout 文字列と、結果を追記するための既存のバイトスライス b を引数に取ります。
    • メソッドの内部では、時刻の各要素(年、月、日など)を抽出し、指定された layout に従ってバイトスライス b に追記していきます。
    • 最終的に、フォーマットされた結果が追記された b を返します。このメソッドは、文字列への変換を行わないため、呼び出し元が []byte を直接利用する場合に、余分な文字列割り当てを回避できます。

この変更により、Time.Format は既存のコードとの互換性を保ちつつ、内部的に最適化された FormatAppend を利用するようになりました。そして、開発者はパフォーマンスが重要な場面で FormatAppend を直接利用することで、メモリ割り当てをより細かく制御できるようになりました。

関連リンク

参考にした情報源リンク

  • 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.Formatstring を返しますが、これはフォーマット結果を格納するために常に新しい文字列オブジェクトとそれに伴うメモリ割り当てを必要とします。これに対し、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 サイズを計算していました。もし maxbuf のサイズ(64バイト)以下であれば、スタック上に確保された buf を利用し、そうでなければ make([]byte, 0, max) を使ってヒープに新しいバイトスライスを割り当てていました。そして、最終的にフォーマットされたバイトスライスを string(b) で文字列に変換して返していました。

新しい Time.FormatAppend メソッドは、このフォーマット処理のコアロジックを担います。引数として layout stringb []byte を受け取り、b にフォーマット結果を追記して、その拡張された []byte を返します。これにより、呼び出し元は事前に確保したバイトスライスを渡すことができ、FormatAppend 内部での新たなメモリ割り当てを回避できます。

Time.Format メソッドは、FormatAppend を利用するように変更されました。

  1. const bufSize = 64 を定義し、固定サイズのバッファサイズを指定します。
  2. var b []byte を宣言し、フォーマット結果を格納するバイトスライスを準備します。
  3. max := len(layout) + 10 を計算し、フォーマット結果の最大長を見積もります。
  4. もし maxbufSize 以下であれば、スタック上に確保された var buf [bufSize]byte を利用し、b = buf[:0] とします。これにより、ヒープ割り当てを避けます。
  5. そうでなければ、b = make([]byte, 0, max) を使ってヒープに新しいバイトスライスを割り当てます。この際、max の容量を事前に確保することで、append による再割り当てを最小限に抑えます。
  6. b = t.FormatAppend(layout, b) を呼び出して、フォーマット結果を b に追記します。
  7. 最後に 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 ファイルが変更されています。

  1. 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 を返す)が維持されます。
  2. Time.FormatAppend メソッドの追加:

    • この新しいメソッドは、Time.Format のコアとなるフォーマットロジックをカプセル化したものです。
    • シグネチャは func (t Time) FormatAppend(layout string, b []byte) []byte となっており、layout 文字列と、結果を追記するための既存のバイトスライス b を引数に取ります。
    • メソッドの内部では、時刻の各要素(年、月、日など)を抽出し、指定された layout に従ってバイトスライス b に追記していきます。
    • 最終的に、フォーマットされた結果が追記された b を返します。このメソッドは、文字列への変換を行わないため、呼び出し元が []byte を直接利用する場合に、余分な文字列割り当てを回避できます。

この変更により、Time.Format は既存のコードとの互換性を保ちつつ、内部的に最適化された FormatAppend を利用するようになりました。そして、開発者はパフォーマンスが重要な場面で FormatAppend を直接利用することで、メモリ割り当てをより細かく制御できるようになりました。

関連リンク

参考にした情報源リンク

  • Go Issue #5192 (関連するパフォーマンス議論): time.Format のパフォーマンスに関する一般的な議論は、フォーマット文字列の繰り返し解析によるオーバーヘッドに焦点を当てています。FormatAppend はこの問題に対処するために導入されました。
  • Go Issue #5195 (FormatAppend の挙動に関するIssue): time.FormatAppend が特定のレイアウト(例: 年のみ)で期待される出力を生成しない可能性があるという、導入後の挙動に関するIssueです。
  • Go言語のメモリ管理とGCに関する公式ドキュメントやブログ記事
  • Go言語の string[]byte の違いに関する解説
  • Go言語のスライスと append 関数に関する解説