[インデックス 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/json や database/sql など)の性能に悪影響を与えます。
従来の reflect.Value の実装は、Goの interface 型の内部構造に依存していました。Goの interface は、内部的に「型情報(itabまたは_type)」と「値(data)」の2つのポインタ(またはワード)で構成されます。reflect.Value はこの interface{} を直接内部に持つことで、任意の型の値を保持していました。しかし、このアプローチには以下のような課題がありました。
- オーバーヘッド:
interfaceの内部構造を介して値にアクセスするためには、間接参照や型アサーションなどの操作が必要となり、これがパフォーマンスのボトルネックとなる可能性がありました。 - 複雑性:
Valueのアドレス可能性(CanAddr)や読み取り専用(read-only)といったプロパティを管理するために、interfaceの型情報ポインタの下位ビットを「ごまかし(kludges)」として利用していました。これは、Goの型システムを迂回するようなハックであり、コードの可読性や保守性を損ねる要因となっていました。 - インライン化の妨げ:
Valueのメソッド、特にKind()のような基本的なメソッドが、interfaceの間接参照を伴うため、コンパイラによるインライン化が困難でした。インライン化は、関数呼び出しのオーバーヘッドを削減し、パフォーマンスを向上させる重要な最適化手法です。
このコミットは、これらの課題を解決し、reflect.Value の性能と内部実装の健全性を向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と内部実装に関する知識が役立ちます。
-
Goの
interface型の内部構造:- Goの
interface{}(空インターフェース) は、内部的に2つのワード(ポインタ)で構成されます。_typeポインタ: 格納されている値の具体的な型情報(runtime._type構造体へのポインタ)を指します。dataポインタ: 格納されている値のデータ自体を指します。値がポインタサイズより小さい場合は直接dataに値が格納され、大きい場合はヒープ上のデータへのポインタが格納されます。
- メソッドを持つインターフェースは、
itab(interface table) と呼ばれる構造体へのポインタと、値のデータへのポインタで構成されます。itabは、インターフェースの型情報と、そのインターフェースが実装するメソッドのポインタの配列を含みます。
- Goの
-
reflectパッケージの役割:reflectパッケージは、Goのプログラムが自身の構造(型、フィールド、メソッドなど)を検査し、実行時に値を操作するための機能を提供します。reflect.TypeはGoの型のメタデータを表し、reflect.ValueはGoの変数の値を表します。reflect.ValueOf(i interface{}) Value関数は、任意のGoの値をreflect.Value型に変換します。reflect.Valueのメソッド(例:Kind(),Int(),SetInt(),Call()など)を通じて、値の型を調べたり、値を読み書きしたり、関数を呼び出したりできます。
-
不透明な(Opaque)構造体:
- プログラミングにおいて「不透明な(opaque)構造体」とは、その内部実装の詳細が外部から隠蔽されており、外部からはその構造体のフィールドに直接アクセスできないように設計されたデータ構造を指します。
- Goでは、構造体のフィールドを小文字で始めることで、そのフィールドがパッケージ外からアクセスできない「非公開(unexported)」フィールドになります。これにより、構造体の内部表現を変更しても、外部のコードに影響を与えずに済みます。
reflect.Valueを不透明な構造体にすることで、Goのコンパイラやランタイムがその内部表現をより効率的に最適化できるようになります。
-
unsafeパッケージ:unsafeパッケージは、Goの型システムを迂回して、メモリを直接操作するための機能を提供します。これには、ポインタとuintptrの相互変換や、任意の型へのポインタ変換などが含まれます。unsafe.Pointerは、任意の型のポインタを保持できる特別なポインタ型です。Goのガベージコレクタはunsafe.Pointerが指すメモリを追跡します。unsafeパッケージの使用は、Goの型安全性を損なう可能性があるため、非常に慎重に行われるべきであり、通常は標準ライブラリや低レベルのシステムプログラミングでのみ使用されます。このコミットでは、reflect.Valueの内部表現を最適化するためにunsafeが活用されています。
-
インライン化 (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 // 値のデータへのポインタ(ポインタサイズより大きい場合)
// ... その他
}
変更前は、Value の Internal フィールドが 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: 値に関する様々なメタデータを保持する新しい型です。flagはuintptrを基盤とする型で、ビットフィールドとして複数の情報を格納します。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 に集中しています。
-
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) +} -
internalValue構造体とその関連ロジックの削除: 従来のValueの内部表現であったinternalValue構造体と、ValueをinternalValueに変換する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") } } -
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)) } -
Kind型の変更:src/pkg/reflect/type.goでKind型が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に直接格納されます。この場合、flagのflagIndirビットはセットされません。 string,slice,map,chan,interface、およびポインタサイズより大きい構造体や配列などの値は、ヒープ上に確保されたデータへのポインタがvalに格納されます。この場合、flagのflagIndirビットがセットされます。unsafe.Pointerを使用することで、Goの型システムを迂回して任意の型のデータを扱うことが可能になりますが、ガベージコレクタはunsafe.Pointerが指すメモリを適切に管理します。
- Goのプリミティブ型(
flag flag: これは、値に関する様々なメタデータをビットフィールドとして格納する新しい型です。flagはuintptrを基盤としているため、効率的なビット操作が可能です。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検索)