[インデックス 19192] ファイルの概要
このコミットは、Go言語の標準ライブラリ compress/gzip
パッケージに Reader
型の Reset
メソッドを追加するものです。具体的には、src/pkg/compress/gzip/gunzip.go
に Reset
メソッドの実装が追加され、src/pkg/compress/gzip/gunzip_test.go
にそのテストが追加されています。
コミット
compress/gzip
パッケージの Reader
型に Reset
メソッドが追加されました。これにより、既存の Reader
インスタンスを再利用して新しい io.Reader
からデータを読み込むことが可能になり、オブジェクトの再割り当てを避けることでパフォーマンスが向上します。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6277cc3df578455c22a0da4deb4213d8db4c9ffe
元コミット内容
commit 6277cc3df578455c22a0da4deb4213d8db4c9ffe
Author: Robert Daniel Kortschak <dan.kortschak@adelaide.edu.au>
Date: Wed Apr 16 22:43:44 2014 -0400
compress/gzip: add Reset method to Reader
Fixes #6364.
LGTM=rsc
R=golang-codereviews, bradfitz, rsc, gobot
CC=golang-codereviews
https://golang.org/cl/13512052
---
src/pkg/compress/gzip/gunzip.go | 11 +++++++++++
src/pkg/compress/gzip/gunzip_test.go | 20 ++++++++++++++++++++\n 2 files changed, 31 insertions(+)
変更の背景
この変更は、Go issue #6364 で報告された問題に対応するものです。Goの compress/gzip
パッケージの Reader
は、GZIP圧縮されたデータを読み込むためのストリームインターフェースを提供します。しかし、このコミット以前は、一度 Reader
を使用してデータを読み終えると、別のGZIPストリームを読み込むためには新しい Reader
インスタンスを NewReader
関数で作成し直す必要がありました。
これは、特に多数のGZIP圧縮ファイルを連続して処理する場合や、HTTPリクエストのボディなど、短命なGZIPストリームを頻繁に扱うようなシナリオにおいて、ガベージコレクションの負荷を増大させ、パフォーマンスのボトルネックとなる可能性がありました。オブジェクトの生成と破棄にはコストがかかるため、既存のオブジェクトを再利用できるメカニズムが求められていました。
Reset
メソッドの導入により、Reader
インスタンスの状態をリセットし、新しい io.Reader
からの読み込みに再利用できるようになります。これにより、オブジェクトの再割り当てを回避し、メモリ使用量を削減し、ガベージコレクションの頻度を減らすことで、全体的なパフォーマンスを向上させることが目的です。
前提知識の解説
io.Reader
インターフェース
Go言語における io.Reader
インターフェースは、データを読み込むための基本的な抽象化を提供します。これは、Read(p []byte) (n int, err error)
メソッドを一つだけ持つシンプルなインターフェースです。ファイル、ネットワーク接続、メモリ上のバッファなど、様々なデータソースからの読み込みを統一的に扱うことができます。compress/gzip.Reader
もこの io.Reader
インターフェースを実装しており、GZIP圧縮されたデータを透過的に解凍して提供します。
GZIP圧縮
GZIPは、RFC 1952で定義されたファイルフォーマットであり、Deflateアルゴリズム(LZ77とハフマン符号化の組み合わせ)を使用してデータを圧縮します。通常、単一のファイルやストリームを圧縮するために使用され、ファイル名、コメント、タイムスタンプなどのメタデータを含むヘッダーと、CRC-32チェックサムおよび元のデータサイズを含むフッターを持ちます。compress/gzip
パッケージは、このGZIPフォーマットのエンコードとデコードを扱います。
リソースの再利用とパフォーマンス最適化
プログラミングにおいて、特に高性能が求められるアプリケーションでは、オブジェクトの生成と破棄に伴うオーバーヘッドを最小限に抑えることが重要です。Go言語では、ガベージコレクタが不要になったメモリを自動的に解放しますが、オブジェクトの割り当てが頻繁に行われると、ガベージコレクタがより頻繁に実行され、アプリケーションの実行が一時的に停止する(ストップ・ザ・ワールド)可能性があります。
sync.Pool
のようなメカニズムや、今回のように Reset
メソッドを提供するパターンは、既存のオブジェクトを再利用することで、これらのオーバーヘッドを削減し、アプリケーションのスループットとレイテンシを改善するための一般的な最適化手法です。Reset
メソッドは、オブジェクトの内部状態を初期化し、新しい入力で再利用できるようにすることで、この目的を達成します。
技術的詳細
compress/gzip.Reader
の Reset
メソッドは、既存の Reader
インスタンスを再初期化し、新しい io.Reader
からGZIPデータを読み込めるようにします。これにより、NewReader
を呼び出して新しい Reader
を割り当てる必要がなくなります。
Reset
メソッドのシグネチャは以下の通りです。
func (z *Reader) Reset(r io.Reader) error
引数 r
は、次にGZIPデータを読み込む新しい io.Reader
です。このメソッドはエラーを返す可能性があります。
内部的には、Reset
メソッドは以下の処理を行います。
z.r = makeReader(r)
:Reader
の内部的なio.Reader
を、新しい入力r
に設定します。makeReader
は、io.Reader
をgzip
パッケージが内部で使用するreader
型にラップするヘルパー関数です。z.digest.Reset()
: GZIPフッターに含まれるCRC-32チェックサムを計算するためのhash.Hash32
インターフェースを実装するdigest
フィールドの状態をリセットします。これにより、新しいストリームのチェックサム計算がゼロから開始されます。z.size = 0
: 読み込まれた非圧縮データの合計サイズを追跡するsize
フィールドをゼロにリセットします。z.err = nil
: 以前の読み込み操作で発生したエラー状態をクリアします。return z.readHeader(true)
: 新しい入力r
からGZIPヘッダーを読み込み、解析します。true
を渡すことで、ヘッダーの読み込み中にエラーが発生した場合でも、Reader
の状態が適切にリセットされるようにします。これにより、次の読み込み操作が正しく開始できるようになります。
この一連の処理により、Reader
インスタンスは、あたかも NewReader
で新しく作成されたかのように、完全に初期化された状態に戻り、新しいGZIPストリームの処理を開始する準備が整います。
コアとなるコードの変更箇所
src/pkg/compress/gzip/gunzip.go
への追加:
--- a/src/pkg/compress/gzip/gunzip.go
+++ b/src/pkg/compress/gzip/gunzip.go
@@ -89,6 +89,17 @@ func NewReader(r io.Reader) (*Reader, error) {
return z, nil
}
+// Reset discards the Reader z's state and makes it equivalent to the
+// result of its original state from NewReader, but reading from r instead.
+// This permits reusing a Reader rather than allocating a new one.
+func (z *Reader) Reset(r io.Reader) error {
+ z.r = makeReader(r)
+ z.digest.Reset()
+ z.size = 0
+ z.err = nil
+ return z.readHeader(true)
+}
+
// GZIP (RFC 1952) is little-endian, unlike ZLIB (RFC 1950).
func get4(p []byte) uint32 {
return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24
src/pkg/compress/gzip/gunzip_test.go
への追加:
--- a/src/pkg/compress/gzip/gunzip_test.go
+++ b/src/pkg/compress/gzip/gunzip_test.go
@@ -303,6 +303,26 @@ func TestDecompressor(t *testing.T) {
if s != tt.raw {
t.Errorf("%s: got %d-byte %q want %d-byte %q", tt.name, n, s, len(tt.raw), tt.raw)
}
+
+ // Test Reader Reset.
+ in = bytes.NewReader(tt.gzip)
+ err = gzip.Reset(in)
+ if err != nil {
+ t.Errorf("%s: Reset: %s", tt.name, err)
+ continue
+ }
+ if tt.name != gzip.Name {
+ t.Errorf("%s: got name %s", tt.name, gzip.Name)
+ }
+ b.Reset()
+ n, err = io.Copy(b, gzip)
+ if err != tt.err {
+ t.Errorf("%s: io.Copy: %v want %v", tt.name, err, tt.err)
+ }
+ s = b.String()
+ if s != tt.raw {
+ t.Errorf("%s: got %d-byte %q want %d-byte %q", tt.name, n, s, len(tt.raw), tt.raw)
+ }
}
}
コアとなるコードの解説
gunzip.go
の変更
追加された Reset
メソッドは、*Reader
型のレシーバーを持つメソッドとして定義されています。これは、既存の Reader
インスタンスに対して操作を行うことを意味します。
z.r = makeReader(r)
:Reader
の内部状態を管理するr
フィールドを、新しい入力io.Reader
r
に設定します。これにより、以降の読み込み操作はこの新しいソースから行われます。z.digest.Reset()
: GZIPフッターのCRC-32チェックサム計算に使用されるハッシュオブジェクトをリセットします。これにより、新しいGZIPストリームの整合性チェックが正しく行われます。z.size = 0
: 読み込まれた非圧縮データのバイト数を追跡するカウンターをゼロにリセットします。z.err = nil
: 以前の操作で設定されたエラー状態をクリアします。これにより、Reader
はクリーンな状態で新しい読み込みを開始できます。return z.readHeader(true)
: 新しい入力r
からGZIPヘッダーを読み込み、解析します。readHeader
はGZIPヘッダーのフォーマットを検証し、必要に応じてメタデータ(ファイル名など)を抽出します。true
を引数に渡すことで、ヘッダーの読み込み中にエラーが発生した場合でも、Reader
の状態が適切にリセットされるようにします。
gunzip_test.go
の変更
TestDecompressor
関数内に Reset
メソッドのテストケースが追加されています。
- 既存のテストループ内で、GZIPデータを
bytes.NewReader
で作成し、gzip.Reset(in)
を呼び出してReader
をリセットしています。 - リセット後、
gzip.Name
(GZIPヘッダーに含まれるファイル名)が期待通りであるかを確認しています。 bytes.Buffer
b
をリセットし、io.Copy(b, gzip)
を使用してリセットされたReader
からデータを読み込み、解凍が正しく行われるか、およびエラーが期待通りであるかを確認しています。- 最後に、解凍された文字列
s
が元の生データtt.raw
と一致するかを検証しています。
これらのテストは、Reset
メソッドが Reader
の内部状態を正しくリセットし、新しい入力ストリームからGZIPデータを正確に解凍できることを保証します。
関連リンク
- Go issue #6364: https://github.com/golang/go/issues/6364
参考にした情報源リンク
- Go issue #6364: https://github.com/golang/go/issues/6364
- Gerrit Change-ID 13512052: https://golang.org/cl/13512052
- RFC 1952 (GZIP file format specification): https://www.rfc-editor.org/rfc/rfc1952
- Go
io.Reader
documentation: https://pkg.go.dev/io#Reader - Go
compress/gzip
documentation: https://pkg.go.dev/compress/gzip