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

[インデックス 19192] ファイルの概要

このコミットは、Go言語の標準ライブラリ compress/gzip パッケージに Reader 型の Reset メソッドを追加するものです。具体的には、src/pkg/compress/gzip/gunzip.goReset メソッドの実装が追加され、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.ReaderReset メソッドは、既存の Reader インスタンスを再初期化し、新しい io.Reader からGZIPデータを読み込めるようにします。これにより、NewReader を呼び出して新しい Reader を割り当てる必要がなくなります。

Reset メソッドのシグネチャは以下の通りです。

func (z *Reader) Reset(r io.Reader) error

引数 r は、次にGZIPデータを読み込む新しい io.Reader です。このメソッドはエラーを返す可能性があります。

内部的には、Reset メソッドは以下の処理を行います。

  1. z.r = makeReader(r): Reader の内部的な io.Reader を、新しい入力 r に設定します。makeReader は、io.Readergzip パッケージが内部で使用する reader 型にラップするヘルパー関数です。
  2. z.digest.Reset(): GZIPフッターに含まれるCRC-32チェックサムを計算するための hash.Hash32 インターフェースを実装する digest フィールドの状態をリセットします。これにより、新しいストリームのチェックサム計算がゼロから開始されます。
  3. z.size = 0: 読み込まれた非圧縮データの合計サイズを追跡する size フィールドをゼロにリセットします。
  4. z.err = nil: 以前の読み込み操作で発生したエラー状態をクリアします。
  5. 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データを正確に解凍できることを保証します。

関連リンク

参考にした情報源リンク