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

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

このコミットは、Go言語のencoding/gobパッケージにおけるデコード処理の最適化に関するものです。具体的には、デコード時にメモリ割り当てを行うdecAlloc関数の呼び出し回数を削減し、コードのクリーンアップとパフォーマンスのわずかな向上を図っています。

コミット

commit e4bc3c462bedb253cb73b2dc290e200d1ffde9fe
Author: Russ Cox <rsc@golang.org>
Date:   Tue Jul 1 14:19:27 2014 -0400

    encoding/gob: fewer decAlloc calls
    
    Move decAlloc calls a bit higher in the call tree.
    Cleans code marginally, improves speed marginally.
    The benchmarks are noisy but the median time from
    20 consective 1-second runs improves by about 2%.
    
    LGTM=r
    R=r
    CC=golang-codereviews
    https://golang.org/cl/105530043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/e4bc3c462bedb253cb73b2dc290e200d1ffde9fe

元コミット内容

encoding/gob: fewer decAlloc calls

Move decAlloc calls a bit higher in the call tree.
Cleans code marginally, improves speed marginally.
The benchmarks are noisy but the median time from
20 consective 1-second runs improves by about 2%.

変更の背景

このコミットの主な目的は、encoding/gobパッケージにおけるデコード処理の効率を向上させることです。decAlloc関数は、デコード対象のGoの値を適切に初期化し、ポインタが指す先のメモリを確保する役割を担っています。この関数がデコード処理の様々な場所で頻繁に呼び出されると、オーバーヘッドが発生し、パフォーマンスに影響を与える可能性があります。

コミットメッセージによると、decAllocの呼び出しをコールツリーのより上位に移動させることで、呼び出し回数を減らし、コードをわずかにクリーンにし、速度をわずかに向上させることが期待されています。ベンチマークはノイズが多いものの、20回の連続した1秒間の実行の中央値で約2%の改善が見られたと報告されています。これは、デコード処理における細かな最適化が、全体的なパフォーマンスに寄与することを示しています。

前提知識の解説

encoding/gobパッケージ

encoding/gobは、Go言語のデータ構造をシリアライズ(バイトストリームに変換)およびデシリアライズ(バイトストリームからデータ構造に復元)するためのGo標準ライブラリのパッケージです。Goのプログラム間で構造化されたデータを効率的にやり取りするために設計されており、特にGoの型システムと密接に連携しています。

gobエンコーディングは、自己記述的であるという特徴があります。つまり、エンコードされたデータストリームには、そのデータがどのような型情報を持っているかというメタデータが含まれています。これにより、受信側は事前に型を知らなくてもデータをデコードできます。

reflectパッケージとリフレクション

Go言語のreflectパッケージは、実行時にプログラムの構造を検査・操作するための機能を提供します。これにより、型情報(reflect.Type)や値情報(reflect.Value)を動的に取得・設定できます。

  • reflect.Value: Goの変数の値を表す構造体です。このコミットでは、デコード対象のGoの値をreflect.Valueとして扱っています。
  • reflect.Kind: reflect.Valueが表す値の具体的な種類(例: reflect.Int, reflect.String, reflect.Ptr, reflect.Structなど)を示します。
  • reflect.Ptr: ポインタ型を表すreflect.Kindの値です。
  • value.Elem(): reflect.Valueがポインタの場合、そのポインタが指す先の要素のreflect.Valueを返します。ポインタでない場合はパニックします。
  • value.IsNil(): reflect.Valueがポインタ、インターフェース、マップ、スライス、関数、またはチャネルの場合に、それがnilであるかどうかを返します。
  • value.SetBool(b bool): reflect.Valueがブール型の場合、その値をbに設定します。
  • value.SetInt(i int64): reflect.Valueが整数型の場合、その値をiに設定します。
  • value.SetUint(u uint64): reflect.Valueが符号なし整数型の場合、その値をuに設定します。
  • value.SetFloat(f float64): reflect.Valueが浮動小数点型の場合、その値をfに設定します。
  • value.SetComplex(c complex128): reflect.Valueが複素数型の場合、その値をcに設定します。
  • value.SetString(s string): reflect.Valueが文字列型の場合、その値をsに設定します。
  • reflect.MakeSlice(typ reflect.Type, len, cap int): 指定された型、長さ、容量を持つ新しいスライスを作成し、そのreflect.Valueを返します。
  • reflect.MakeMap(typ reflect.Type): 指定されたマップ型を持つ新しいマップを作成し、そのreflect.Valueを返します。
  • value.SetMapIndex(key, elem reflect.Value): マップのreflect.Valueに対して、指定されたキーと要素を設定します。
  • value.FieldByIndex(index []int): 構造体のreflect.Valueから、指定されたインデックスパスに対応するフィールドのreflect.Valueを返します。
  • value.Index(i int): スライスまたは配列のreflect.Valueから、指定されたインデックスiの要素のreflect.Valueを返します。
  • value.Addr(): reflect.Valueがアドレス可能(つまり、ポインタを介して変更可能)な場合、その値のアドレスを表すポインタのreflect.Valueを返します。
  • value.Cap(): スライス、配列、またはチャネルの容量を返します。
  • value.Len(): スライス、配列、マップ、文字列、またはチャネルの長さを返します。
  • value.Type(): reflect.Valueが表す値のreflect.Typeを返します。
  • reflect.Zero(typ reflect.Type): 指定された型のゼロ値を表すreflect.Valueを返します。

decAlloc関数

decAlloc関数は、encoding/gobパッケージのデコード処理において、デコード対象のreflect.Valueがポインタ型である場合に、そのポインタがnilであれば新しいメモリを割り当ててポインタを更新し、常に設定可能なreflect.Valueを返す役割を担っています。これにより、デコードされた値が正しく格納されることが保証されます。

元の実装では、各プリミティブ型(bool, int8, uint8など)のデコーダ関数内でdecAllocが呼び出されていました。これは、デコードされる値がポインタである場合に、その都度メモリ割り当てのチェックと実行を行うことを意味します。

技術的詳細

このコミットの技術的な核心は、decAlloc関数の呼び出し位置を最適化することにあります。

変更前: 各プリミティブ型(decBool, decInt8, decUint8など)のデコーダ関数内で、value.SetXxxを呼び出す直前にdecAlloc(value)が呼び出されていました。これは、デコードされる値がポインタである場合に、その都度メモリ割り当てのチェックと実行が行われることを意味します。

// 変更前の例: decBool
func decBool(i *decInstr, state *decoderState, value reflect.Value) {
	decAlloc(value).SetBool(state.decodeUint() != 0)
}

変更後: decAllocの呼び出しを、個々のデコーダ関数から、それらを呼び出す上位の関数(例: decodeStruct, decodeArrayHelper, decodeIntoValue, decodeValueなど)に移動させました。これにより、個々のデコーダ関数は、渡されるreflect.Valueがすでに設定可能(つまり、ポインタであれば適切にメモリが割り当てられている)であることを前提とできるようになります。

decAlloc関数のコメントも更新され、この新しい前提が明記されています。

// decAlloc takes a value and returns a settable value that can
// be assigned to. If the value is a pointer, decAlloc guarantees it points to storage.
// The callers to the individual decoders are expected to have used decAlloc.
// The individual decoders don't need to it.
func decAlloc(v reflect.Value) reflect.Value {
	// ...
}

この変更により、以下のメリットが期待されます。

  1. コードのクリーンアップ: 各デコーダ関数からdecAllocの呼び出しが削除され、コードがよりシンプルで読みやすくなりました。デコーダ関数は純粋に値の設定に集中できるようになります。
  2. パフォーマンスの向上: decAllocはポインタのチェックと、必要に応じたメモリ割り当てを行う関数です。この呼び出しがコールツリーのより上位に移動することで、重複するチェックや不要な関数呼び出しが削減され、全体的なデコード処理のオーバーヘッドが減少します。コミットメッセージにある2%の速度向上は、この最適化によるものです。特に、多数の小さな値がデコードされるシナリオで効果を発揮すると考えられます。

decodeStructdecodeArrayHelperdecodeIntoValuedecodeMapdecodeSlicedecodeInterfacegobDecodeOpFordecodeValueといった、様々な型のデコードを処理する関数でdecAllocの呼び出し位置が変更されています。これにより、デコード処理全体でdecAllocの呼び出しが効率化されています。

例えば、decodeStructでは、構造体のフィールドをデコードする際に、そのフィールドがポインタ型であればdecAllocを呼び出すように変更されています。

// 変更後の decodeStruct 内の関連箇所
				if instr.index != nil {
					// Otherwise the field is unknown to us and instr.op is an ignore op.
					field = value.FieldByIndex(instr.index)
					if field.Kind() == reflect.Ptr { // ここでポインタの場合のみ decAlloc を呼び出す
						field = decAlloc(field)
					}
				}
				instr.op(instr, state, field)

また、decodeArrayHelperでは、配列やスライスの要素がポインタ型であればdecAllocを呼び出すように変更されています。

// 変更後の decodeArrayHelper 内の関連箇所
	isPtr := value.Type().Elem().Kind() == reflect.Ptr // 要素がポインタ型か事前にチェック
	for i := 0; i < length; i++ {
		// ...
		v := value.Index(i)
		if isPtr { // ポインタの場合のみ decAlloc を呼び出す
			v = decAlloc(v)
		}
		elemOp(instr, state, v)
	}

これらの変更により、decAllocの呼び出しがより戦略的に行われるようになり、デコード処理の効率が向上しています。

コアとなるコードの変更箇所

diff --git a/src/pkg/encoding/gob/codec_test.go b/src/pkg/encoding/gob/codec_test.go
index c7b2567ca0..4f17a28931 100644
--- a/src/pkg/encoding/gob/codec_test.go
+++ b/src/pkg/encoding/gob/codec_test.go
@@ -323,7 +323,7 @@ func execDec(typ string, instr *decInstr, state *decoderState, t *testing.T, val
 	if v+state.fieldnum != 6 {
 		t.Fatalf("decoding field number %d, got %d", 6, v+state.fieldnum)
 	}
-	instr.op(instr, state, value)
+	instr.op(instr, state, value.Elem())
 	state.fieldnum = 6
 }
 
diff --git a/src/pkg/encoding/gob/decode.go b/src/pkg/encoding/gob/decode.go
index feed80513c..76274a0cac 100644
--- a/src/pkg/encoding/gob/decode.go
+++ b/src/pkg/encoding/gob/decode.go
@@ -155,6 +155,8 @@ func ignoreTwoUints(i *decInstr, state *decoderState, v reflect.Value) {
 
 // decAlloc takes a value and returns a settable value that can
 // be assigned to. If the value is a pointer, decAlloc guarantees it points to storage.
+// The callers to the individual decoders are expected to have used decAlloc.
+// The individual decoders don't need to it.
 func decAlloc(v reflect.Value) reflect.Value {
 	for v.Kind() == reflect.Ptr {
 		if v.IsNil() {
@@ -167,7 +169,7 @@ func decAlloc(v reflect.Value) reflect.Value {
 
 // decBool decodes a uint and stores it as a boolean in value.
 func decBool(i *decInstr, state *decoderState, value reflect.Value) {
-	decAlloc(value).SetBool(state.decodeUint() != 0)
+	value.SetBool(state.decodeUint() != 0)
 }
 
 // decInt8 decodes an integer and stores it as an int8 in value.
@@ -176,7 +178,7 @@ func decInt8(i *decInstr, state *decoderState, value reflect.Value) {
 	if v < math.MinInt8 || math.MaxInt8 < v {
 		error_(i.ovfl)
 	}
-	decAlloc(value).SetInt(v)
+	value.SetInt(v)
 }
 
 // decUint8 decodes an unsigned integer and stores it as a uint8 in value.
@@ -185,7 +187,7 @@ func decUint8(i *decInstr, state *decoderState, value reflect.Value) {
 	if math.MaxUint8 < v {
 		error_(i.ovfl)
 	}
-	decAlloc(value).SetUint(v)
+	value.SetUint(v)
 }
 
 // decInt16 decodes an integer and stores it as an int16 in value.
@@ -194,7 +196,7 @@ func decInt16(i *decInstr, state *decoderState, value reflect.Value) {
 	if v < math.MinInt16 || math.MaxInt16 < v {
 		error_(i.ovfl)
 	}
-	decAlloc(value).SetInt(v)
+	value.SetInt(v)
 }
 
 // decUint16 decodes an unsigned integer and stores it as a uint16 in value.
@@ -203,7 +205,7 @@ func decUint16(i *decInstr, state *decoderState, value reflect.Value) {
 	if math.MaxUint16 < v {
 		error_(i.ovfl)
 	}
-	decAlloc(value).SetUint(v)
+	value.SetUint(v)
 }
 
 // decInt32 decodes an integer and stores it as an int32 in value.
@@ -212,7 +214,7 @@ func decInt32(i *decInstr, state *decoderState, value reflect.Value) {
 	if v < math.MinInt32 || math.MaxInt32 < v {
 		error_(i.ovfl)
 	}
-	decAlloc(value).SetInt(v)
+	value.SetInt(v)
 }
 
 // decUint32 decodes an unsigned integer and stores it as a uint32 in value.
@@ -221,19 +223,19 @@ func decUint32(i *decInstr, state *decoderState, value reflect.Value) {
 	if math.MaxUint32 < v {
 		error_(i.ovfl)
 	}
-	decAlloc(value).SetUint(v)
+	value.SetUint(v)
 }
 
 // decInt64 decodes an integer and stores it as an int64 in value.
 func decInt64(i *decInstr, state *decoderState, value reflect.Value) {
 	v := state.decodeInt()
-	decAlloc(value).SetInt(v)
+	value.SetInt(v)
 }
 
 // decUint64 decodes an unsigned integer and stores it as a uint64 in value.
 func decUint64(i *decInstr, state *decoderState, value reflect.Value) {
 	v := state.decodeUint()
-	decAlloc(value).SetUint(v)
+	value.SetUint(v)
 }
 
 // Floating-point numbers are transmitted as uint64s holding the bits
@@ -271,13 +273,13 @@ func float32FromBits(i *decInstr, u uint64) float64 {
 // decFloat32 decodes an unsigned integer, treats it as a 32-bit floating-point
 // number, and stores it in value.\n func decFloat32(i *decInstr, state *decoderState, value reflect.Value) {
-	decAlloc(value).SetFloat(float32FromBits(i, state.decodeUint()))
+	value.SetFloat(float32FromBits(i, state.decodeUint()))
 }
 
 // decFloat64 decodes an unsigned integer, treats it as a 64-bit floating-point
 // number, and stores it in value.
 func decFloat64(i *decInstr, state *decoderState, value reflect.Value) {
-	decAlloc(value).SetFloat(float64FromBits(state.decodeUint()))
+	value.SetFloat(float64FromBits(state.decodeUint()))
 }
 
 // decComplex64 decodes a pair of unsigned integers, treats them as a
@@ -286,7 +288,7 @@ func decComplex64(i *decInstr, state *decoderState, value reflect.Value) {
 func decComplex64(i *decInstr, state *decoderState, value reflect.Value) {
 	real := float32FromBits(i, state.decodeUint())
 	imag := float32FromBits(i, state.decodeUint())
-	decAlloc(value).SetComplex(complex(real, imag))
+	value.SetComplex(complex(real, imag))
 }
 
 // decComplex128 decodes a pair of unsigned integers, treats them as a
@@ -295,7 +297,7 @@ func decComplex64(i *decInstr, state *decoderState, value reflect.Value) {
 func decComplex128(i *decInstr, state *decoderState, value reflect.Value) {
 	real := float64FromBits(state.decodeUint())
 	imag := float64FromBits(state.decodeUint())
-	decAlloc(value).SetComplex(complex(real, imag))
+	value.SetComplex(complex(real, imag))
 }
 
 // decUint8Slice decodes a byte slice and stores in value a slice header
@@ -310,7 +312,6 @@ func decUint8Slice(i *decInstr, state *decoderState, value reflect.Value) {
 	if n > state.b.Len() {
 		errorf("%s data too long for buffer: %d", value.Type(), n)
 	}
-	value = decAlloc(value)
 	if value.Cap() < n {
 		value.Set(reflect.MakeSlice(value.Type(), n, n))
 	} else {
@@ -338,7 +339,7 @@ func decString(i *decInstr, state *decoderState, value reflect.Value) {
 	if _, err := state.b.Read(data); err != nil {
 		errorf("error decoding string: %s", err)
 	}
-	decAlloc(value).SetString(string(data))
+	value.SetString(string(data))
 }
 
 // ignoreUint8Array skips over the data for a byte slice value with no destination.
@@ -376,8 +377,6 @@ func (dec *Decoder) decodeSingle(engine *decEngine, ut *userTypeInfo, value refl
 // This state cannot arise for decodeSingle, which is called directly
 // from the user's value, not from the innards of an engine.
 func (dec *Decoder) decodeStruct(engine *decEngine, ut *userTypeInfo, value reflect.Value) {
-	value = decAlloc(value)
-	//	println(value.Kind() == reflect.Ptr)
 	state := dec.newDecoderState(&dec.buf)
 	defer dec.freeDecoderState(state)
 	state.fieldnum = -1
@@ -399,6 +398,9 @@ func (dec *Decoder) decodeStruct(engine *decEngine, ut *userTypeInfo, value refl
 		if instr.index != nil {
 			// Otherwise the field is unknown to us and instr.op is an ignore op.
 			field = value.FieldByIndex(instr.index)
+			if field.Kind() == reflect.Ptr {
+				field = decAlloc(field)
+			}
 		}
 		instr.op(instr, state, field)
 		state.fieldnum = fieldnum
@@ -447,11 +449,16 @@ func (dec *Decoder) ignoreSingle(engine *decEngine) {
 // decodeArrayHelper does the work for decoding arrays and slices.
 func (dec *Decoder) decodeArrayHelper(state *decoderState, value reflect.Value, elemOp decOp, length int, ovfl error) {
 	instr := &decInstr{elemOp, 0, nil, ovfl}
+	isPtr := value.Type().Elem().Kind() == reflect.Ptr
 	for i := 0; i < length; i++ {
 		if state.b.Len() == 0 {
 			errorf("decoding array or slice: length exceeds input size (%d elements)", length)
 		}
-		elemOp(instr, state, value.Index(i))
+		v := value.Index(i)
+		if isPtr {
+			v = decAlloc(v)
+		}
+		elemOp(instr, state, v)
 	}
 }
 
@@ -459,7 +466,6 @@ func (dec *Decoder) decodeArrayHelper(state *decoderState, value reflect.Value,\
 // The length is an unsigned integer preceding the elements.  Even though the length is redundant
 // (it's part of the type), it's a useful check and is included in the encoding.\n func (dec *Decoder) decodeArray(atyp reflect.Type, state *decoderState, value reflect.Value, elemOp decOp, length int, ovfl error) {
-	value = decAlloc(value)
 	if n := state.decodeUint(); n != uint64(length) {
 		errorf("length mismatch in decodeArray")
 	}
@@ -467,15 +473,16 @@ func (dec *Decoder) decodeArray(atyp reflect.Type, state *decoderState, value re
 }
 
 // decodeIntoValue is a helper for map decoding.\n-func decodeIntoValue(state *decoderState, op decOp, value reflect.Value, ovfl error) reflect.Value {
+func decodeIntoValue(state *decoderState, op decOp, isPtr bool, value reflect.Value, ovfl error) reflect.Value {
 	instr := &decInstr{op, 0, nil, ovfl}
-	op(instr, state, value)
+	v := value
+	if isPtr {
+		v = decAlloc(value)
+	}
+	op(instr, state, v)
 	return value
 }
 
@@ -484,15 +491,16 @@ func decodeIntoValue(state *decoderState, op decOp, value reflect.Value, ovfl er
 // Because the internals of maps are not visible to us, we must
 // use reflection rather than pointer magic.\n func (dec *Decoder) decodeMap(mtyp reflect.Type, state *decoderState, value reflect.Value, keyOp, elemOp decOp, ovfl error) {
-	value = decAlloc(value)
 	if value.IsNil() {
 		// Allocate map.
 		value.Set(reflect.MakeMap(mtyp))
 	}
 	n := int(state.decodeUint())
+	keyIsPtr := mtyp.Key().Kind() == reflect.Ptr
+	elemIsPtr := mtyp.Elem().Kind() == reflect.Ptr
 	for i := 0; i < n; i++ {
-		key := decodeIntoValue(state, keyOp, allocValue(mtyp.Key()), ovfl)
-		elem := decodeIntoValue(state, elemOp, allocValue(mtyp.Elem()), ovfl)
+		key := decodeIntoValue(state, keyOp, keyIsPtr, allocValue(mtyp.Key()), ovfl)
+		elem := decodeIntoValue(state, elemOp, elemIsPtr, allocValue(mtyp.Elem()), ovfl)
 		value.SetMapIndex(key, elem)
 	}\n }\n@@ -528,7 +539,6 @@ func (dec *Decoder) decodeSlice(state *decoderState, value reflect.Value, elemOp
 		// of interfaces, there will be buffer reloads.
 		errorf("length of %s is negative (%d bytes)", value.Type(), u)
 	}
-	value = decAlloc(value)
 	if value.Cap() < n {
 		value.Set(reflect.MakeSlice(value.Type(), n, n))
 	} else {
@@ -558,7 +568,6 @@ func (dec *Decoder) decodeInterface(ityp reflect.Type, state *decoderState, valu
 	state.b.Read(b)
 	name := string(b)
 	// Allocate the destination interface value.
-	value = decAlloc(value)
 	if name == "" {
 		// Copy the nil interface value to the target.
 		value.Set(reflect.Zero(value.Type()))
@@ -834,7 +843,6 @@ func (dec *Decoder) gobDecodeOpFor(ut *userTypeInfo) *decOp {\
 	}\n 	var op decOp
 	op = func(i *decInstr, state *decoderState, value reflect.Value) {
-		value = decAlloc(value)
 		// We now have the base type. We need its address if the receiver is a pointer.
 		if value.Kind() != reflect.Ptr && rcvrType.Kind() == reflect.Ptr {
 			value = value.Addr()
@@ -1072,6 +1080,7 @@ func (dec *Decoder) decodeValue(wireId typeId, value reflect.Value) {
 	if dec.err != nil {
 		return
 	}
+	value = decAlloc(value)
 	engine := *enginePtr
 	if st := base; st.Kind() == reflect.Struct && ut.externalDec == 0 {
 		if engine.numInstr == 0 && st.NumField() > 0 &&

コアとなるコードの解説

このコミットの主要な変更は、src/pkg/encoding/gob/decode.goファイルに集中しています。

  1. decAlloc関数のコメント更新: decAlloc関数のコメントに以下の行が追加されました。

    // The callers to the individual decoders are expected to have used decAlloc.
    // The individual decoders don't need to it.
    

    これは、個々のデコーダ関数(decBool, decInt8など)は、decAllocを呼び出す必要がなく、呼び出し元がすでにdecAllocを使ってreflect.Valueを適切に準備していることを前提としていることを明確にしています。

  2. プリミティブ型デコーダからのdecAlloc呼び出しの削除: decBool, decInt8, decUint8, decInt16, decUint16, decInt32, decUint32, decInt64, decUint64, decFloat32, decFloat64, decComplex64, decComplex128, decStringといった、各プリミティブ型をデコードする関数から、decAlloc(value)の呼び出しが削除されました。 例:

    -	decAlloc(value).SetBool(state.decodeUint() != 0)
    +	value.SetBool(state.decodeUint() != 0)
    

    これにより、これらの関数は、渡されたvalueがすでに設定可能であることを前提として、直接SetXxxメソッドを呼び出すようになります。

  3. decUint8SliceからのdecAlloc呼び出しの削除: バイトスライスをデコードするdecUint8Slice関数からもvalue = decAlloc(value)の行が削除されました。

  4. decodeStructにおけるdecAllocの移動: 構造体をデコードするdecodeStruct関数では、以前は関数冒頭でvalue = decAlloc(value)が呼び出されていましたが、これが削除されました。 代わりに、構造体の各フィールドを処理するループ内で、そのフィールドがポインタ型である場合にのみdecAllocを呼び出すように変更されました。

    +			if field.Kind() == reflect.Ptr {
    +				field = decAlloc(field)
    +			}
    

    これにより、構造体全体に対して一度decAllocを呼び出すのではなく、個々のポインタフィールドが必要な場合にのみdecAllocが呼び出されるようになります。

  5. decodeArrayHelperにおけるdecAllocの追加: 配列やスライスをデコードするヘルパー関数decodeArrayHelperでは、要素がポインタ型であるかどうかを事前にチェックし、ポインタ型であれば各要素に対してdecAllocを呼び出すように変更されました。

    +	isPtr := value.Type().Elem().Kind() == reflect.Ptr
    	for i := 0; i < length; i++ {
    		// ...
    -		elemOp(instr, state, value.Index(i))
    +		v := value.Index(i)
    +		if isPtr {
    +			v = decAlloc(v)
    +		}
    +		elemOp(instr, state, v)
    

    これにより、配列/スライスの要素がポインタである場合に、適切にメモリが割り当てられるようになります。

  6. decodeArrayからのdecAlloc呼び出しの削除: 配列をデコードするdecodeArray関数からvalue = decAlloc(value)の行が削除されました。

  7. decodeIntoValueのシグネチャ変更とdecAllocの追加: マップのキーや要素のデコードに使用されるヘルパー関数decodeIntoValueのシグネチャにisPtr boolが追加され、このフラグに基づいてdecAllocを呼び出すように変更されました。

    -func decodeIntoValue(state *decoderState, op decOp, value reflect.Value, ovfl error) reflect.Value {
    +func decodeIntoValue(state *decoderState, op decOp, isPtr bool, value reflect.Value, ovfl error) reflect.Value {
    	instr := &decInstr{op, 0, nil, ovfl}
    -	op(instr, state, value)
    +	v := value
    +	if isPtr {
    +		v = decAlloc(value)
    +	}
    +	op(instr, state, v)
    	return value
    }
    
  8. decodeMapにおけるdecAllocの移動: マップをデコードするdecodeMap関数では、以前は関数冒頭でvalue = decAlloc(value)が呼び出されていましたが、これが削除されました。 代わりに、マップのキーと要素がポインタ型であるかどうかを事前にチェックし、decodeIntoValueを呼び出す際にその情報(keyIsPtr, elemIsPtr)を渡すように変更されました。

    +	keyIsPtr := mtyp.Key().Kind() == reflect.Ptr
    +	elemIsPtr := mtyp.Elem().Kind() == reflect.Ptr
     	for i := 0; i < n; i++ {\n-		key := decodeIntoValue(state, keyOp, allocValue(mtyp.Key()), ovfl)
    -		elem := decodeIntoValue(state, elemOp, allocValue(mtyp.Elem()), ovfl)
    +		key := decodeIntoValue(state, keyOp, keyIsPtr, allocValue(mtyp.Key()), ovfl)
    +		elem := decodeIntoValue(state, elemOp, elemIsPtr, allocValue(mtyp.Elem()), ovfl)
     		value.SetMapIndex(key, elem)
     	}
    
  9. decodeSliceからのdecAlloc呼び出しの削除: スライスをデコードするdecodeSlice関数からvalue = decAlloc(value)の行が削除されました。

  10. decodeInterfaceからのdecAlloc呼び出しの削除: インターフェースをデコードするdecodeInterface関数からvalue = decAlloc(value)の行が削除されました。

  11. gobDecodeOpForからのdecAlloc呼び出しの削除: gobDecodeOpFor関数内の匿名関数からもvalue = decAlloc(value)の行が削除されました。

  12. decodeValueにおけるdecAllocの追加: decodeValue関数では、デコード処理の開始時にvalue = decAlloc(value)が追加されました。これは、デコードされる値の最上位レベルで一度decAllocを呼び出すことで、その後の個々のデコーダがdecAllocを呼び出す必要がなくなるという設計思想を反映しています。

  13. codec_test.goの変更: テストファイルsrc/pkg/encoding/gob/codec_test.goでは、execDec関数内でinstr.opを呼び出す際に、valueではなくvalue.Elem()を渡すように変更されました。これは、execDecがポインタを受け取る場合、そのポインタが指す先の要素をデコーダに渡す必要があるためです。この変更は、decAllocの呼び出し位置の変更と整合性を保つためのものです。

これらの変更は、decAllocの呼び出しをより戦略的な位置に移動させることで、デコード処理の効率とコードの明確性を向上させることを目的としています。

関連リンク

参考にした情報源リンク