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

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

このコミットは、Go言語の reflect パッケージにおける Value 型の内部表現を大幅に変更し、よりシンプルで高速な「不透明な(opaque)構造体」として再設計したものです。これにより、従来の interface を利用した複雑な実装が排除され、パフォーマンスが向上しました。特に、json パッケージのベンチマークにおいて、エンコード/デコード処理が約45%高速化されるなど、顕著な改善が見られます。

コミット

commit a479a455489bc3600c004367f16c4d452705d2c9
Author: Russ Cox <rsc@golang.org>
Date:   Wed Nov 16 19:18:25 2011 -0500

    reflect: make Value an opaque struct

    Making Value opaque means we can drop the interface kludges
    in favor of a significantly simpler and faster representation.
    v.Kind() will be a prime candidate for inlining too.

    On a Thinkpad X201s using -benchtime 10:

    benchmark                           old ns/op    new ns/op    delta
    json.BenchmarkCodeEncoder           284391780    157415960  -44.65%
    json.BenchmarkCodeMarshal           286979140    158992020  -44.60%
    json.BenchmarkCodeDecoder           717175800    388288220  -45.86%
    json.BenchmarkCodeUnmarshal         734470500    404548520  -44.92%
    json.BenchmarkCodeUnmarshalReuse    707172280    385258720  -45.52%
    json.BenchmarkSkipValue              24630036     18557062  -24.66%

    benchmark                            old MB/s     new MB/s  speedup
    json.BenchmarkCodeEncoder                6.82        12.33    1.81x
    json.BenchmarkCodeMarshal                6.76        12.20    1.80x
    json.BenchmarkCodeDecoder                2.71         5.00    1.85x
    json.BenchmarkCodeUnmarshal              2.64         4.80    1.82x
    json.BenchmarkCodeUnmarshalReuse         2.74         5.04    1.84x
    json.BenchmarkSkipValue                 77.92       103.42    1.33x

    I cannot explain why BenchmarkSkipValue gets faster.
    Maybe it is one of those code alignment things.

    R=iant, r, gri, r
    CC=golang-dev
    https://golang.org/cl/5373101

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

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

元コミット内容

このコミットは、Go言語の標準ライブラリである reflect パッケージの Value 型の内部実装を根本的に変更するものです。従来の Value 型は、その内部に interface{} 型のフィールド Internal を持ち、これを通じて実際の値の型情報とデータを保持していました。この実装は、Goの interface の性質を利用して、任意の型の値を抽象的に扱うことを可能にしていましたが、同時にいくつかの「ごまかし(kludges)」やオーバーヘッドを伴っていました。

コミットメッセージが示すように、この変更の主な目的は、Value を「不透明な(opaque)構造体」にすることで、よりシンプルで高速な表現を実現することです。これにより、v.Kind() のような頻繁に呼び出されるメソッドがインライン化されやすくなり、全体的なパフォーマンスが向上することが期待されています。

ベンチマーク結果は、特に json パッケージのエンコード/デコード処理において、大幅な速度向上があったことを示しています。これは、json パッケージが内部で reflect パッケージを多用しているため、reflect.Value の性能改善が直接的に影響した結果と考えられます。

変更の背景

Go言語の reflect パッケージは、実行時にプログラムの構造を検査・操作するための機能を提供します。これは、Goが静的型付け言語であるにもかかわらず、リフレクションという動的な機能を実現するために不可欠なものです。しかし、リフレクションは一般的にパフォーマンスのオーバーヘッドが大きい操作であり、特に reflect.Value のような基本的な型が非効率な実装になっていると、それを多用するライブラリ(例えば encoding/jsondatabase/sql など)の性能に悪影響を与えます。

従来の reflect.Value の実装は、Goの interface 型の内部構造に依存していました。Goの interface は、内部的に「型情報(itabまたは_type)」と「値(data)」の2つのポインタ(またはワード)で構成されます。reflect.Value はこの interface{} を直接内部に持つことで、任意の型の値を保持していました。しかし、このアプローチには以下のような課題がありました。

  1. オーバーヘッド: interface の内部構造を介して値にアクセスするためには、間接参照や型アサーションなどの操作が必要となり、これがパフォーマンスのボトルネックとなる可能性がありました。
  2. 複雑性: Value のアドレス可能性(CanAddr)や読み取り専用(read-only)といったプロパティを管理するために、interface の型情報ポインタの下位ビットを「ごまかし(kludges)」として利用していました。これは、Goの型システムを迂回するようなハックであり、コードの可読性や保守性を損ねる要因となっていました。
  3. インライン化の妨げ: Value のメソッド、特に Kind() のような基本的なメソッドが、interface の間接参照を伴うため、コンパイラによるインライン化が困難でした。インライン化は、関数呼び出しのオーバーヘッドを削減し、パフォーマンスを向上させる重要な最適化手法です。

このコミットは、これらの課題を解決し、reflect.Value の性能と内部実装の健全性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と内部実装に関する知識が役立ちます。

  1. Goの interface 型の内部構造:

    • Goの interface{} (空インターフェース) は、内部的に2つのワード(ポインタ)で構成されます。
      • _type ポインタ: 格納されている値の具体的な型情報(runtime._type 構造体へのポインタ)を指します。
      • data ポインタ: 格納されている値のデータ自体を指します。値がポインタサイズより小さい場合は直接 data に値が格納され、大きい場合はヒープ上のデータへのポインタが格納されます。
    • メソッドを持つインターフェースは、itab (interface table) と呼ばれる構造体へのポインタと、値のデータへのポインタで構成されます。itab は、インターフェースの型情報と、そのインターフェースが実装するメソッドのポインタの配列を含みます。
  2. reflect パッケージの役割:

    • reflect パッケージは、Goのプログラムが自身の構造(型、フィールド、メソッドなど)を検査し、実行時に値を操作するための機能を提供します。
    • reflect.Type はGoの型のメタデータを表し、reflect.Value はGoの変数の値を表します。
    • reflect.ValueOf(i interface{}) Value 関数は、任意のGoの値を reflect.Value 型に変換します。
    • reflect.Value のメソッド(例: Kind(), Int(), SetInt(), Call() など)を通じて、値の型を調べたり、値を読み書きしたり、関数を呼び出したりできます。
  3. 不透明な(Opaque)構造体:

    • プログラミングにおいて「不透明な(opaque)構造体」とは、その内部実装の詳細が外部から隠蔽されており、外部からはその構造体のフィールドに直接アクセスできないように設計されたデータ構造を指します。
    • Goでは、構造体のフィールドを小文字で始めることで、そのフィールドがパッケージ外からアクセスできない「非公開(unexported)」フィールドになります。これにより、構造体の内部表現を変更しても、外部のコードに影響を与えずに済みます。
    • reflect.Value を不透明な構造体にすることで、Goのコンパイラやランタイムがその内部表現をより効率的に最適化できるようになります。
  4. unsafe パッケージ:

    • unsafe パッケージは、Goの型システムを迂回して、メモリを直接操作するための機能を提供します。これには、ポインタと uintptr の相互変換や、任意の型へのポインタ変換などが含まれます。
    • unsafe.Pointer は、任意の型のポインタを保持できる特別なポインタ型です。Goのガベージコレクタは unsafe.Pointer が指すメモリを追跡します。
    • unsafe パッケージの使用は、Goの型安全性を損なう可能性があるため、非常に慎重に行われるべきであり、通常は標準ライブラリや低レベルのシステムプログラミングでのみ使用されます。このコミットでは、reflect.Value の内部表現を最適化するために unsafe が活用されています。
  5. インライン化 (Inlining):

    • コンパイラ最適化の一種で、関数呼び出しの代わりに、呼び出される関数の本体を呼び出し元に直接埋め込むことです。
    • これにより、関数呼び出しのオーバーヘッド(スタックフレームの作成、引数の渡し、戻り値の処理など)が削減され、プログラムの実行速度が向上します。
    • Goコンパイラは、小さな関数や頻繁に呼び出される関数を自動的にインライン化しようとします。しかし、複雑な制御フローや間接参照が多い関数は、インライン化が困難になる場合があります。

技術的詳細

このコミットの核心は、reflect.Value 型の内部構造を interface{} から、より低レベルで直接的な表現に変更した点にあります。

変更前(概念図):

type Value struct {
    Internal       interface{} // 実際の値が格納される
    InternalMethod int         // メソッド値の場合のインデックス
}

// Value.internal() メソッドで Internal を解析し、
// 内部的な internalValue 構造体に展開して処理していた
type internalValue struct {
    typ       *commonType
    kind      Kind
    flag      uint32 // アドレス可能性や読み取り専用フラグ
    word      iword  // 値のデータ(ポインタサイズ以下の場合)
    addr      unsafe.Pointer // 値のデータへのポインタ(ポインタサイズより大きい場合)
    // ... その他
}

変更前は、ValueInternal フィールドが interface{} であり、その interface の内部構造(型情報とデータワード)を unsafe を使って読み取り、internalValue という別の構造体に展開して処理していました。この internalValue は、値の型、種類、フラグ、データワード、アドレスなどを保持していました。Value の各メソッドは、まず internal() を呼び出して internalValue を取得し、それを使って処理を行っていました。

変更後(概念図):

type Value struct {
    typ *commonType    // 値の型情報
    val unsafe.Pointer // 値のデータ(直接、またはデータへのポインタ)
    flag flag          // メタデータ(Kind、アドレス可能性、読み取り専用、メソッド情報など)
}

type flag uintptr // 新しく導入されたフラグ型

const (
    flagRO        flag = 1 << iota // 読み取り専用
    flagIndir                      // val がデータへのポインタであるか
    flagAddr                       // アドレス可能か
    flagMethod                     // メソッド値であるか
    flagKindShift = iota           // Kind のビットシフト量
    flagKindWidth = 5              // Kind のビット幅
    flagKindMask  flag = 1<<flagKindWidth - 1
    flagMethodShift = flagKindShift + flagKindWidth // メソッド番号のビットシフト量
)

func (f flag) kind() Kind {
    return Kind((f >> flagKindShift) & flagKindMask)
}

変更後、Value は以下の3つの非公開フィールドを持つ「不透明な構造体」になりました。

  • typ *commonType: 値の型情報を保持します。これは reflect.Type の内部表現です。
  • val unsafe.Pointer: 値のデータを保持します。
    • flagIndir フラグがセットされている場合、val は実際のデータへのポインタです。これは、値がポインタサイズ(通常は32ビットまたは64ビット)より大きい場合に適用されます(例: string, slice, map, chan, interface, struct, array)。
    • flagIndir フラグがセットされていない場合、val は実際のデータそのものを保持します。これは、値がポインタサイズ以下のプリミティブ型(例: bool, int, float32 など)の場合に適用されます。
  • flag flag: 値に関する様々なメタデータを保持する新しい型です。
    • flaguintptr を基盤とする型で、ビットフィールドとして複数の情報を格納します。
    • flagRO: 値が非公開フィールドから取得されたため、読み取り専用であることを示します。
    • flagIndir: val フィールドがデータへのポインタであるか、それともデータそのものであるかを示します。
    • flagAddr: Value.CanAddr()true を返す、つまり値のアドレスが取得可能であることを示します。
    • flagMethod: Value がメソッド値(レシーバがバインドされた関数)であることを示します。
    • flagKindShift, flagKindWidth, flagKindMask: flag の中に Kind(値の種類、例: Int, String, Struct など)を格納するためのビットフィールド関連の定数です。
    • flagMethodShift: flag の中にメソッド番号を格納するためのビットフィールド関連の定数です。

この変更により、Value の各メソッドは、internalValue への展開を介さずに、typ, val, flag の各フィールドに直接アクセスして処理を行うようになりました。これにより、間接参照が減り、データアクセスが高速化されます。特に v.Kind() は、flag フィールドから直接 Kind を抽出するシンプルな操作になり、インライン化が容易になりました。

また、Kind 型が uint8 から uint に変更されています。これは、将来的に Kind の種類が増える可能性に備えた変更と考えられます。

src/pkg/reflect/type.go では、Method 構造体の Func フィールドが valueFromIword から直接 Value 型に設定されるようになりました。これは、メソッド値の表現も新しい Value 構造体で統一されたことを示しています。

src/pkg/reflect/value.go では、internalValue 構造体とその関連関数(internal(), packValue(), valueFromAddr(), valueFromIword() など)が削除され、Value の各メソッドが新しい内部構造に直接対応するように書き換えられています。例えば、Bool(), CanAddr(), Call(), Elem(), Field(), Index(), Int(), Interface(), IsNil(), IsValid(), Kind(), Len(), MapIndex(), MapKeys(), Method(), NumMethod(), MethodByName(), NumField(), Overflow*(), Pointer(), Recv(), Send(), Set*(), Slice(), String() など、ほぼ全ての Value メソッドが影響を受けています。

ベンチマーク結果が示すように、この変更は reflect パッケージの性能を大幅に向上させ、特に json パッケージのようなリフレクションを多用するアプリケーションに大きな恩恵をもたらしました。

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

このコミットにおけるコアとなるコードの変更は、主に src/pkg/reflect/value.go に集中しています。

  1. reflect.Value 構造体の再定義: Value 構造体が、Internal interface{}InternalMethod int から、typ *commonType, val unsafe.Pointer, flag flag の3つの非公開フィールドを持つように変更されました。

    --- a/src/pkg/reflect/value.go
    +++ b/src/pkg/reflect/value.go
    @@ -53,14 +54,54 @@ func memmove(adst, asrc unsafe.Pointer, n uintptr) {
     // its String method returns "<invalid Value>", and all other methods panic.
     // Most functions and methods never return an invalid value.
     // If one does, its documentation states the conditions explicitly.
    -//
    -// The fields of Value are exported so that clients can copy and
    -// pass Values around, but they should not be edited or inspected
    -// directly.  A future language change may make it possible not to
    -// export these fields while still keeping Values usable as values.
     type Value struct {
    -	Internal       interface{}
    -	InternalMethod int
    +	// typ holds the type of the value represented by a Value.
    +	typ *commonType
    +
    +	// val holds the 1-word representation of the value.
    +	// If flag's flagIndir bit is set, then val is a pointer to the data.
    +	// Otherwise val is a word holding the actual data.
    +	// When the data is smaller than a word, it begins at
    +	// the first byte (in the memory address sense) of val.
    +	// We use unsafe.Pointer so that the garbage collector
    +	// knows that val could be a pointer.
    +	val unsafe.Pointer
    +
    +	// flag holds metadata about the value.
    +	// The lowest bits are flag bits:
    +	//	- flagRO: obtained via unexported field, so read-only
    +	//	- flagIndir: val holds a pointer to the data
    +	//	- flagAddr: v.CanAddr is true (implies flagIndir)
    +	//	- flagMethod: v is a method value.
    +	// The next five bits give the Kind of the value.
    +	// This repeats typ.Kind() except for method values.
    +	// The remaining 23+ bits give a method number for method values.
    +	// If flag.kind() != Func, code can assume that flagMethod is unset.
    +	// If typ.size > ptrSize, code can assume that flagIndir is set.
    +	flag
    +
    +	// A method value represents a curried method invocation
    +	// like r.Read for some receiver r.  The typ+val+flag bits describe
    +	// the receiver r, but the flag's Kind bits say Func (methods are
    +	// functions), and the top bits of the flag give the method number
    +    // in r's type's method table.
     }
    +
    +type flag uintptr
    +
    +const (
    +	flagRO flag = 1 << iota
    +	flagIndir
    +	flagAddr
    +	flagMethod
    +	flagKindShift        = iota
    +	flagKindWidth        = 5 // there are 27 kinds
    +	flagKindMask    flag = 1<<flagKindWidth - 1
    +	flagMethodShift      = flagKindShift + flagKindWidth
    +)
    +
    +func (f flag) kind() Kind {
    +	return Kind((f >> flagKindShift) & flagKindMask)
    +}
    
  2. internalValue 構造体とその関連ロジックの削除: 従来の Value の内部表現であった internalValue 構造体と、ValueinternalValue に変換する internal() メソッド、および Value をパックする packValue() などの関数が全て削除されました。

    --- a/src/pkg/reflect/value.go
    +++ b/src/pkg/reflect/value.go
    @@ -170,232 +225,42 @@ type nonEmptyInterface struct {
     	word iword
     }
    
    -// Regarding the implementation of Value:
    -//
    -// The Internal interface is a true interface value in the Go sense,
    -// but it also serves as a (type, address) pair in which one cannot
    -// be changed separately from the other.  That is, it serves as a way
    -// to prevent unsafe mutations of the Internal state even though
    -// we cannot (yet?) hide the field while preserving the ability for
    -// clients to make copies of Values.
    -//
    -// The internal method converts a Value into the expanded internalValue struct.
    -// If we could avoid exporting fields we'd probably make internalValue the
    -// definition of Value.
    -//
    -// If a Value is addressable (CanAddr returns true), then the Internal
    -// interface value holds a pointer to the actual field data, and Set stores
    -// through that pointer.  If a Value is not addressable (CanAddr returns false),
    -// then the Internal interface value holds the actual value.
    -//
    -// In addition to whether a value is addressable, we track whether it was
    -// obtained by using an unexported struct field.  Such values are allowed
    -// to be read, mainly to make fmt.Print more useful, but they are not
    -// allowed to be written.  We call such values read-only.
    -//
    -// A Value can be set (via the Set, SetUint, etc. methods) only if it is both
    -// addressable and not read-only.
    -//
    -// The two permission bits - addressable and read-only - are stored in
    -// the bottom two bits of the type pointer in the interface value.
    -//
    -//
    -//	ordinary value: Internal = value
    -//	addressable value: Internal = value, Internal.typ |= flagAddr
    -//	read-only value: Internal = value, Internal.typ |= flagRO
    -//	addressable, read-only value: Internal = value, Internal.typ |= flagAddr | flagRO
    -//
    -// It is important that the read-only values have the extra bit set
    -// (as opposed to using the bit to mean writable), because client code
    -// can grab the interface field and try to use it.  Having the extra bit
    -// set makes the type pointer compare not equal to any real type,
    -// so that a client cannot, say, write through v.Internal.(*int).
    -// The runtime routines that access interface types reject types with
    -// low bits set.
    -//
    -// If a Value fv = v.Method(i), then fv = v with the InternalMethod
    -// field set to i+1.  Methods are never addressable.
    -//
    -// All in all, this is a lot of effort just to avoid making this new API
    -// depend on a language change we'll probably do anyway, but
    -// it's helpful to keep the two separate, and much of the logic is
    -// necessary to implement the Interface method anyway.
    -
    -const (
    -	flagAddr uint32 = 1 << iota // holds address of value
    -	flagRO                      // read-only
    -
    -	reflectFlags = 3
    -)
    -
    -// An internalValue is the unpacked form of a Value.
    -// The zero Value unpacks to a zero internalValue
    -type internalValue struct {
    -	typ       *commonType // type of value
    -	kind      Kind        // kind of value
    -	flag      uint32
    -	word      iword
    -	addr      unsafe.Pointer
    -	rcvr      iword
    -	method    bool
    -	nilmethod bool
    -}
    -
    -func (v Value) internal() internalValue {
    -	var iv internalValue
    -	eface := *(*emptyInterface)(unsafe.Pointer(&v.Internal))
    -	p := uintptr(unsafe.Pointer(eface.typ))
    -	iv.typ = toCommonType((*runtime.Type)(unsafe.Pointer(p &^ reflectFlags)))
    -	if iv.typ == nil {
    -		return iv
    -	}
    -	iv.flag = uint32(p & reflectFlags)
    -	iv.word = eface.word
    -	if iv.flag&flagAddr != 0 {
    -		iv.addr = unsafe.Pointer(iv.word)
    -		iv.typ = iv.typ.Elem().common()
    -		if iv.typ.size <= ptrSize {
    -			iv.word = loadIword(iv.addr, iv.typ.size)
    -		}
    -	} else {
    -		if iv.typ.size > ptrSize {
    -			iv.addr = unsafe.word
    -		}
    -	}
    -	iv.kind = iv.typ.Kind()
    -
    -	// Is this a method?  If so, iv describes the receiver.
    -	// Rewrite to describe the method function.
    -	if v.InternalMethod != 0 {
    -		// If this Value is a method value (x.Method(i) for some Value x)
    -		// then we will invoke it using the interface form of the method,
    -		// which always passes the receiver as a single word.
    -		// Record that information.
    -		i := v.InternalMethod - 1
    -		if iv.kind == Interface {
    -			it := (*interfaceType)(unsafe.Pointer(iv.typ))
    -			if i < 0 || i >= len(it.methods) {
    -				panic("reflect: broken Value")
    -			}
    -			m := &it.methods[i]
    -			if m.pkgPath != nil {
    -				iv.flag |= flagRO
    -			}
    -			iv.typ = toCommonType(m.typ)
    -			iface := (*nonEmptyInterface)(iv.addr)
    -			if iface.itab == nil {
    -				iv.word = 0
    -				iv.nilmethod = true
    -			} else {
    -				iv.word = iword(iface.itab.fun[i])
    -			}
    -			iv.rcvr = iface.word
    -		} else {
    -			ut := iv.typ.uncommon()
    -			if ut == nil || i < 0 || i >= len(ut.methods) {
    -				panic("reflect: broken Value")
    -			}
    -			m := &ut.methods[i]
    -			if m.pkgPath != nil {
    -				iv.flag |= flagRO
    -			}
    -			iv.typ = toCommonType(m.mtyp)
    -			iv.rcvr = iv.word
    -			iv.word = iword(m.ifn)
    -		}
    -		iv.kind = Func
    -		iv.method = true
    -		iv.flag &^= flagAddr
    -		iv.addr = nil
    -	}
    -
    -	return iv
    -}
    -
    -// packValue returns a Value with the given flag bits, type, and interface word.
    -func packValue(flag uint32, typ *runtime.Type, word iword) Value {
    -	if typ == nil {
    -		panic("packValue")
    +
    +// mustBe panics if f's kind is not expected.
    +// Making this a method on flag instead of on Value
    +// (and embedding flag in Value) means that we can write
    +// the very clear v.mustBe(Bool) and have it compile into
    +// v.flag.mustBe(Bool), which will only bother to copy the
    +// single important word for the receiver.
    +func (f flag) mustBe(expected Kind) {
    +	k := f.kind()
    +	if k != expected {
    +		panic(&ValueError{methodName(), k})
     	}
    -	t := uintptr(unsafe.Pointer(typ))
    -	t |= uintptr(flag)
    -	eface := emptyInterface{(*runtime.Type)(unsafe.Pointer(t)), word}
    -	return Value{Internal: *(*interface{})(unsafe.Pointer(&eface))}
    -}
    -
    -var dummy struct {
    -	b bool
    -	x interface{}
    -}
    -
    -// Dummy annotation marking that the value x escapes,
    -// for use in cases where the reflect code is so clever that
    -// the compiler cannot follow.
    -func escapes(x interface{}) {
    -	if dummy.b {
    -		dummy.x = x
    -	}
    -}
    -
    -// valueFromAddr returns a Value using the given type and address.
    -func valueFromAddr(flag uint32, typ Type, addr unsafe.Pointer) Value {
    -	// TODO(rsc): Eliminate this terrible hack.
    -	// The escape analysis knows that addr is a pointer
    -	// but it doesn't see addr get passed to anything
    -	// that keeps it.  packValue keeps it, but packValue
    -	// takes a uintptr (iword(addr)), and integers (non-pointers)
    -	// are assumed not to matter.  The escapes function works
    -	// because return values always escape (for now).
    -	escapes(addr)
    -
    -	if flag&flagAddr != 0 {
    -		// Addressable, so the internal value is
    -		// an interface containing a pointer to the real value.
    -		return packValue(flag, PtrTo(typ).runtimeType(), iword(addr))
    -	}
    -
    -	var w iword
    -	if n := typ.Size(); n <= ptrSize {
    -		// In line, so the interface word is the actual value.
    -		w = loadIword(addr, n)
    -	} else {
    -		// Not in line: the interface word is the address.
    -		w = iword(addr)
    -	}
    -	return packValue(flag, typ.runtimeType(), w)
    -}
    -
    -// valueFromIword returns a Value using the given type and interface word.
    -func valueFromIword(flag uint32, typ Type, w iword) Value {
    -	if flag&flagAddr != 0 {
    -		panic("reflect: internal error: valueFromIword addressable")
    -	}
    -	return packValue(flag, typ.runtimeType(), w)
    -}
    -
    -func (iv internalValue) mustBe(want Kind) {
    -	if iv.kind != want {
    -		panic(&ValueError{methodName(), iv.kind})
    -	}
    -}
    -
    -func (iv internalValue) mustBeExported() {
    -	if iv.kind == 0 {
    -		panic(&ValueError{methodName(), iv.kind})
    +
    +// mustBeExported panics if f records that the value was obtained using
    +// an unexported field.
    +func (f flag) mustBeExported() {
    +	if f == 0 {
    +		panic(&ValueError{methodName(), 0})
     	}
    -	if iv.flag&flagRO != 0 {
    +	if f&flagRO != 0 {
     		panic(methodName() + " using value obtained using unexported field")
     	}
     }
    
    -func (iv internalValue) mustBeAssignable() {
    -	if iv.kind == 0 {
    -		panic(&ValueError{methodName(), iv.kind})
    +// mustBeAssignable panics if f records that the value is not assignable,
    +// which is to say that either it was obtained using an unexported field
    +// or it is not addressable.
    +func (f flag) mustBeAssignable() {
    +	if f == 0 {
    +		panic(&ValueError{methodName(), Invalid})
     	}
     	// Assignable if addressable and not read-only.
    -	if iv.flag&flagRO != 0 {
    +	if f&flagRO != 0 {
     		panic(methodName() + " using value obtained using unexported field")
     	}
    -	if iv.flag&flagAddr == 0 {
    +	if f&flagAddr == 0 {
     		panic(methodName() + " using unaddressable value")
     	}
     }
    
  3. Value メソッドの実装変更: Value の各メソッドが、internalValue を介さずに、Value 構造体の typ, val, flag フィールドに直接アクセスするように書き換えられました。これにより、コードが簡素化され、パフォーマンスが向上しました。

    例: Value.Kind() メソッドの変更

    --- a/src/pkg/reflect/value.go
    +++ b/src/pkg/reflect/value.go
    @@ -960,8 +883,8 @@ func (iv internalValue) IsNil() bool {
     // Most functions and methods never return an invalid value.
     // If one does, its documentation states the conditions explicitly.
     func (v Value) IsValid() bool {
    -	return v.Internal != nil
    +	return v.flag != 0
     }
    
     // Kind returns v's Kind.
     // If v is the zero Value (IsValid returns false), Kind returns Invalid.
     func (v Value) Kind() Kind {
    -	return v.internal().kind
    +	return v.kind()
     }
    

    例: Value.Bool() メソッドの変更

    --- a/src/pkg/reflect/value.go
    +++ b/src/pkg/reflect/value.go
    @@ -406,31 +271,31 @@ func (iv internalValue) mustBeAssignable() {
     // or slice element in order to call a method that requires a
     // pointer receiver.
     func (v Value) Addr() Value {
    -	iv := v.internal()
    -	if iv.flag&flagAddr == 0 {
    +	if v.flag&flagAddr == 0 {
     		panic("reflect.Value.Addr of unaddressable value")
     	}
    -	return valueFromIword(iv.flag&flagRO, PtrTo(iv.typ.toType()), iword(iv.addr))
    +	return Value{v.typ.ptrTo(), v.val, (v.flag & flagRO) | flag(Ptr)<<flagKindShift}
     }
    
     // Bool returns v's underlying value.
     // It panics if v's kind is not Bool.
     func (v Value) Bool() bool {
    -	iv := v.internal()
    -	iv.mustBe(Bool)
    -	return *(*bool)(unsafe.Pointer(&iv.word))
    +	v.mustBe(Bool)
    +	if v.flag&flagIndir != 0 {
    +		return *(*bool)(v.val)
    +	}
    +	return *(*bool)(unsafe.Pointer(&v.val))
     }
    
  4. Kind 型の変更: src/pkg/reflect/type.goKind 型が uint8 から uint に変更されました。

    --- a/src/pkg/reflect/type.go
    +++ b/src/pkg/reflect/type.go
    @@ -188,7 +188,7 @@ type Type interface {
    
     // A Kind represents the specific kind of type that a Type represents.
     // The zero Kind is not a valid kind.
    -type Kind uint8
    +type Kind uint
    
     const (
     	Invalid Kind = iota
    

コアとなるコードの解説

このコミットの最も重要な変更は、reflect.Value の内部表現が interface{} から、typ, val, flag という3つの非公開フィールドを持つ構造体へと変更された点です。

  • typ *commonType: これは、reflect.Type の内部表現である commonType へのポインタです。値の具体的な型情報(名前、サイズ、アラインメント、メソッドなど)を保持します。
  • val unsafe.Pointer: ここに実際の値のデータが格納されます。
    • Goのプリミティブ型(bool, int, float32 など)のように、ポインタサイズ(通常4バイトまたは8バイト)以下の値は、val に直接格納されます。この場合、flagflagIndir ビットはセットされません。
    • string, slice, map, chan, interface、およびポインタサイズより大きい構造体や配列などの値は、ヒープ上に確保されたデータへのポインタが val に格納されます。この場合、flagflagIndir ビットがセットされます。
    • unsafe.Pointer を使用することで、Goの型システムを迂回して任意の型のデータを扱うことが可能になりますが、ガベージコレクタは unsafe.Pointer が指すメモリを適切に管理します。
  • flag flag: これは、値に関する様々なメタデータをビットフィールドとして格納する新しい型です。
    • flaguintptr を基盤としているため、効率的なビット操作が可能です。
    • flagRO (Read-Only): 値が非公開フィールドから取得された場合など、書き込みが許可されないことを示します。
    • flagIndir (Indirect): val フィールドがデータそのものではなく、データへのポインタであることを示します。
    • flagAddr (Addressable): Value.Addr() メソッドが呼び出し可能である、つまり値のアドレスが取得可能であることを示します。これは、変数のアドレスや構造体のフィールドのアドレスなど、メモリ上に実体がある場合に true になります。
    • flagMethod (Method Value): Value がレシーバがバインドされたメソッドを表す場合にセットされます。
    • flagKindShift, flagKindWidth, flagKindMask: flag の中に Kind(値の種類)を格納するためのビットマスクとシフト量です。これにより、v.Kind()flag から直接 Kind を抽出する非常に高速な操作になります。
    • flagMethodShift: flag の中にメソッドのインデックスを格納するためのビットマスクとシフト量です。

この新しい構造により、reflect.Value の各メソッドは、従来の interface{} を介した間接参照や、internalValue 構造体への展開といったオーバーヘッドなしに、直接 typ, val, flag フィールドにアクセスして処理を行うことができます。これにより、データアクセスが高速化され、特に Kind() のような頻繁に呼び出されるメソッドは、コンパイラによるインライン化が容易になり、実行時の性能が大幅に向上しました。

例えば、Value.Bool() メソッドは、以前は internal() を呼び出して internalValue を取得し、その word フィールドから bool 値を読み取っていましたが、変更後は v.mustBe(Bool)Kind をチェックした後、v.val が直接 bool 値を保持しているか、bool 値へのポインタであるかに応じて、直接 v.val から bool 値を読み取るようになりました。

この変更は、Goのリフレクションの性能を向上させる上で非常に重要なマイルストーンとなりました。

関連リンク

  • Go言語の reflect パッケージのドキュメント: https://pkg.go.dev/reflect
  • Go言語の unsafe パッケージのドキュメント: https://pkg.go.dev/unsafe
  • Goのインターフェースの内部構造に関する解説記事(例: "The Laws of Reflection" by Rob Pike, "Go Data Structures: Interfaces" by Dave Cheneyなど)

参考にした情報源リンク

  • Goのコミット履歴: https://github.com/golang/go/commits/master
  • Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている https://golang.org/cl/5373101 は、このGerritの変更リストへのリンクです。)
  • Goのインターフェースの内部構造に関する一般的な知識 (Web検索)
  • Goのリフレクションの性能に関する議論 (Web検索)
  • Goのコンパイラ最適化、特にインライン化に関する情報 (Web検索)
  • unsafe.Pointer の使用に関するGoのドキュメントと解説 (Web検索)