[インデックス 16363] ファイルの概要
このコミットは、Go言語の標準ライブラリtime
パッケージからTime.FormatAppend
関数を削除するものです。この関数は、Time.Format
の非アロケーションバージョンとして導入されましたが、コンセンサスが得られなかったため元に戻されました。
コミット
commit 509a1173a33d4fd914409cd941470d440fd5eed3
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue May 21 14:32:09 2013 -0700
time: remove Time.FormatAppend
undo CL 8478044 / 0d28fd55e721
Lack of consensus.
««« original CL description
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
»»»
R=r
CC=golang-dev
https://golang.org/cl/9462049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/509a1173a33d4fd914409cd941470d440fd5eed3
元コミット内容
このコミットは、以前のコミット (CL 8478044 / 0d28fd55e721) を元に戻すものです。元のコミットでは、time
パッケージにTime.FormatAppend
という新しい関数が追加されました。この関数の目的は、既存のTime.Format
関数が文字列を返す際に発生するメモリ割り当て(アロケーション)を避けることでした。FormatAppend
は、[]byte
スライスに時刻のテキスト表現を追記し、拡張されたバッファを返すことで、アロケーションを不要にすることを目指していました。これは、特にパフォーマンスが重要なアプリケーションにおいて、頻繁な時刻のフォーマット処理によるガベージコレクションの負荷を軽減することを意図していました。
変更の背景
Time.FormatAppend
の導入は、パフォーマンス最適化を目的としていましたが、このコミットメッセージには「Lack of consensus.」(コンセンサスが得られなかった)と明記されています。これは、Goコミュニティ内またはGo開発チーム内で、この新しいAPIの設計、必要性、あるいは既存のTime.Format
との関係性について意見の相違があったことを示唆しています。
一般的に、Go言語の標準ライブラリに新しいAPIを追加する際には、その設計がGoの哲学(シンプルさ、明瞭さ、効率性)に合致しているか、既存のAPIとの一貫性があるか、そして将来的なメンテナンス性など、多角的な観点から厳密なレビューが行われます。Time.FormatAppend
は、アロケーションを避けるという明確な利点があったにもかかわらず、何らかの理由でこれらの基準を満たさないと判断されたか、あるいはそのメリットがAPIの複雑性増加や他の潜在的なデメリットを上回らないと判断された可能性があります。
後のGoのバージョン(Go 1.24.0以降)では、time.Time
型にAppendText
という類似の機能を持つメソッドが導入されています。これは、Time.FormatAppend
が目指した「既存のバイトスライスにテキスト表現を追記する」というコンセプトが、形を変えて再評価され、より適切なAPIとして実装された可能性を示唆しています。
前提知識の解説
Go言語のtime
パッケージ
Go言語のtime
パッケージは、時刻の表現、操作、フォーマット、および時間間隔の測定を行うための機能を提供します。主要な型としてtime.Time
があり、これは特定の時点を表します。
time.Time.Format
関数
time.Time.Format(layout string) string
は、time.Time
型の値を指定されたレイアウト文字列に従ってフォーマットし、その結果を新しいstring
として返します。レイアウト文字列は、Go言語独自の参照時刻(Mon Jan 2 15:04:05 MST 2006
)に基づいてフォーマットを指定します。
メモリ割り当て(アロケーション)とガベージコレクション
Go言語はガベージコレクタを持つ言語であり、メモリ管理の多くを自動で行います。しかし、新しい文字列やスライスが作成されるたびに、ヒープメモリに新しい領域が割り当てられます(アロケーション)。これらのアロケーションが頻繁に発生すると、ガベージコレクタがより頻繁に実行され、プログラムの実行が一時的に停止する(ストップ・ザ・ワールド)など、パフォーマンスに影響を与える可能性があります。
特に、ループ内でTime.Format
のように新しい文字列を生成する処理が繰り返される場合、大量の短期的なオブジェクトが生成され、ガベージコレクションの負荷が高まることがあります。これを避けるために、既存のバッファにデータを追記するようなAPI(例: bytes.Buffer
や、このコミットで削除されたFormatAppend
のようなもの)が検討されることがあります。
[]byte
とstring
の変換
Go言語では、[]byte
とstring
は異なる型であり、相互に変換する際には通常、新しいメモリ割り当てが発生します。string(b)
のように[]byte
をstring
に変換すると、b
の内容が新しい文字列としてコピーされます。パフォーマンスが重要な場面では、この変換コストも考慮に入れる必要があります。
技術的詳細
このコミットの技術的詳細は、主にTime.FormatAppend
の削除と、それに伴うTime.Format
関数の内部実装の変更に集約されます。
元のTime.FormatAppend
は、[]byte
スライスを引数として受け取り、そのスライスにフォーマットされた時刻データを追記して、拡張された[]byte
スライスを返していました。これにより、呼び出し元は事前に確保したバッファを再利用でき、Time.Format
が毎回新しい文字列を生成する際に発生するアロケーションを回避できました。
このコミットでは、Time.FormatAppend
関数自体が削除されます。そして、Time.Format
関数が、Time.FormatAppend
が内部で行っていたバッファ管理のロジックを直接取り込む形に変更されます。具体的には、Time.Format
の内部で、小さな固定サイズのバッファ([64]byte
)をスタック上に確保し、レイアウト文字列の長さに応じて必要であればヒープに大きなバッファを割り当てるという最適化が行われます。最終的に、この内部バッファの内容をstring
に変換して返します。
この変更により、Time.Format
は引き続きstring
を返すAPIとして一貫性を保ちつつ、内部的にはある程度のパフォーマンス最適化(特に短いフォーマット文字列の場合)を維持します。しかし、呼び出し元が独自の[]byte
バッファを再利用してアロケーションを完全に回避する手段は、この時点では提供されなくなります。
コアとなるコードの変更箇所
変更はsrc/pkg/time/format.go
ファイルに集中しています。
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -380,22 +380,6 @@ 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 {
-- const bufSize = 64
-- var b []byte
-- max := len(layout) + 10
-- if max <= bufSize {
-- var buf [bufSize]byte
-- b = buf[:0]
-- } else {
-- b = make([]byte, 0, max)
-- }
-- b = t.FormatAppend(layout, b)
-- return string(b)
-}
-
-// 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 {
var (
name, offset, abs = t.locabs()
@@ -405,7 +389,16 @@ func (t Time) FormatAppend(layout string, b []byte) []byte {
hour int = -1
min int
sec int
++
++ b []byte
++ buf [64]byte
)
++ max := len(layout) + 10
++ if max <= len(buf) {
++ b = buf[:0]
++ } else {
++ b = make([]byte, 0, max)
++ }
// Each iteration generates one std value.
for layout != "" {
prefix, std, suffix := nextStdChunk(layout)
@@ -553,7 +546,7 @@ func (t Time) FormatAppend(layout string, b []byte) []byte {
\tb = formatNano(b, uint(t.Nanosecond()), std>>stdArgShift, std&stdMask == stdFracSecond9)
}\n \t}\n-- return b
-+ return string(b)
}\n \n var errBad = errors.New("bad value for field") // placeholder not passed to user
コアとなるコードの解説
このコミットの主要な変更点は以下の通りです。
-
Time.FormatAppend
関数の削除:func (t Time) FormatAppend(layout string, b []byte) []byte
というシグネチャを持つ関数が完全に削除されました。これにより、外部から[]byte
バッファを渡して時刻をフォーマットし、アロケーションを避けるという直接的な手段がなくなりました。 -
Time.Format
関数の内部実装の変更: 削除されたTime.FormatAppend
が持っていたバッファ管理ロジックが、Time.Format
関数に直接統合されました。- 変更前:
Time.Format
は内部でbufSize
(64バイト)のスタックバッファを試み、必要に応じてヒープにmake([]byte, 0, max)
でバッファを割り当て、そのバッファをt.FormatAppend(layout, b)
に渡していました。そして、FormatAppend
が返した[]byte
をstring(b)
で文字列に変換して返していました。 - 変更後:
Time.Format
関数は、Time.FormatAppend
の呼び出しを削除し、その代わりにTime.FormatAppend
の内部ロジックを自身の内部に直接取り込みました。var b []byte
とvar buf [64]byte
が宣言されます。max := len(layout) + 10
で必要な最大バッファサイズを計算します。if max <= len(buf)
の条件で、レイアウト文字列の長さが64バイトのスタックバッファに収まる場合はb = buf[:0]
としてスタックバッファを使用します。- 収まらない場合は
b = make([]byte, 0, max)
としてヒープに新しいバッファを割り当てます。 - 時刻のフォーマット処理は、以前
FormatAppend
が行っていたのと同様に、b
にデータを追記していきます。 - 最終的に、
return string(b)
として、フォーマットされた[]byte
バッファの内容を新しいstring
に変換して返します。
- 変更前:
この変更により、Time.Format
は引き続きstring
を返すAPIとして提供されますが、内部的には短いフォーマット文字列の場合にスタックアロケーションを利用することで、ヒープアロケーションを避ける最適化が施されています。しかし、呼び出し元がバッファを再利用してアロケーションを完全に制御することはできなくなりました。
関連リンク
- 元のコミット (CL 8478044): https://golang.org/cl/8478044
- このコミット (CL 9462049): https://golang.org/cl/9462049
- 関連するIssue:
- Fixes #5192
- Update #5195
参考にした情報源リンク
- Go言語
time
パッケージのドキュメント: https://pkg.go.dev/time - Go言語における
string
と[]byte
の変換とアロケーションに関する一般的な情報源 - Go 1.24.0で導入された
Time.AppendText
に関する情報:- go.dev: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE_uS7btFIt4MIWP7-1UfkcCpDbsi7jc95BXoepYdx06tpKQZwyiw6L7SEU0TKZTlnAHp1WGDxxEGQAD3Zsz4aPRKmOZqhiqGD_LdtzVP4ZrHrB
- github.com (Goソースコード): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHx0Huwm888GIoqDrr8oPhZph5oKgb3OUI-ES5yzQsqhEQOdye949cQiUBDj5eY3-hu0fNG-W4tUduOMkxTuzSakLBYC_Ifsnrwp4tniImpcPce941GL89Rz7UTLOHNFOBsIDQu
- github.com (MarshalTextの内部使用例): https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGqd3RuUeke2WChlCdUSVR9JXd6pwYqt0tyPXGJWV4Wct9sPlvxDLstnl8jN8lzonrOvaZyt78qkbF6QlHxz-_Gt8x5ifxyazh_BwhKjtLRgSjerqgf9fDfoSTPHym2WbWjStm-muTvkjcsY5QzP2ENujtmNg==