2️⃣
Go 1.25: reflect.TypeAssert vs reflect.Value#Interface + 型アサーション
reflect.TypeAssert
Go 1.25でreflectパッケージへ導入が予定されている関数でreflect.Value#Interface + 型アサーション を代替するためのものです
rv := reflect.ValueOf(new(time.Time)).Elem()
- v, ok = rv.Interface().(time.Time)
+ v, ok = reflect.TypeAssert[time.Time](rv)
元となったプロポーザル[1]によるとreflect.Value#Interfaceによるアロケーションを防ぐことがモチベーションとのことなので実際にベンチマークテストで比較していきます
検証
package reflect_type_assert_vs_type_assertion
import (
"reflect"
"testing"
"time"
)
func BenchmarkTypeAssertion(b *testing.B) {
rv := reflect.ValueOf(new(time.Time)).Elem()
b.ReportAllocs()
for b.Loop() {
_, ok := rv.Interface().(time.Time)
if !ok {
b.Fatal("expected true but got false")
}
}
}
func BenchmarkReflectTypeAssert(b *testing.B) {
rv := reflect.ValueOf(new(time.Time)).Elem()
b.ReportAllocs()
for b.Loop() {
_, ok := reflect.TypeAssert[time.Time](rv)
if !ok {
b.Fatal("expected true but got false")
}
}
}
プロポーザルに記載のサンプルコードを参考に、上記のテストコードで検証していきます
go test -bench '^BenchmarkTypeAssertion$|^BenchmarkReflectTypeAssert$' -count 6
goos: linux
goarch: amd64
pkg: github.com/miyamo2/reflect_type_assert_vs_type_assertion
cpu: AMD EPYC 7763 64-Core Processor
BenchmarkTypeAssertion-16 35453163 34.60 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertion-16 34472107 35.62 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertion-16 35326629 34.59 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertion-16 35174138 34.63 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertion-16 34724137 34.60 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertion-16 33512199 34.64 ns/op 24 B/op 1 allocs/op
BenchmarkReflectTypeAssert-16 294901711 4.058 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssert-16 293091184 4.113 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssert-16 295116627 4.052 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssert-16 296398741 4.044 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssert-16 296427202 4.047 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssert-16 296555486 4.043 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/miyamo2/reflect_type_assert_vs_type_assertion 14.460s
reflect.Value#Interface + 型アサーションではアロケーションが発生するのに対し、TypeAssertはゼロアロケーションであることが確認できました
次にbenchstatで処理時間の平均を取って比較していきます
go tool benchstat -filter ".unit:(ns/op)" benchstat.txt
goos: linux
goarch: amd64
pkg: github.com/miyamo2/reflect_type_assert_vs_type_assertion
cpu: AMD EPYC 7763 64-Core Processor
│ benchstat.txt │
│ sec/op │
TypeAssertion-16 34.62n ± 3%
ReflectTypeAssert-16 4.049n ± 2%
geomean 11.84n
reflect.TypeAssertがおおよそ8.5倍速いです
どちらもゼロアロケーションとなるケース
func BenchmarkTypeAssertionToPointer(b *testing.B) {
rv := reflect.ValueOf(new(time.Time))
b.ReportAllocs()
for b.Loop() {
_, ok := rv.Interface().(*time.Time)
if !ok {
b.Fatal("expected true but got false")
}
}
}
func BenchmarkReflectTypeAssertToPointer(b *testing.B) {
rv := reflect.ValueOf(new(time.Time))
b.ReportAllocs()
for b.Loop() {
_, ok := reflect.TypeAssert[*time.Time](rv)
if !ok {
b.Fatal("expected true but got false")
}
}
}
次はreflect.Value#Elemを介さず、*Tに型アサーションします
go test -bench '^.*Pointer$' -count 6
goos: linux
goarch: amd64
pkg: github.com/miyamo2/reflect_type_assert_vs_type_assertion
cpu: AMD EPYC 7763 64-Core Processor
BenchmarkTypeAssertionToPointer-16 192637333 6.229 ns/op 0 B/op 0 allocs/op
BenchmarkTypeAssertionToPointer-16 192209920 6.242 ns/op 0 B/op 0 allocs/op
BenchmarkTypeAssertionToPointer-16 192223932 6.237 ns/op 0 B/op 0 allocs/op
BenchmarkTypeAssertionToPointer-16 192543849 6.249 ns/op 0 B/op 0 allocs/op
BenchmarkTypeAssertionToPointer-16 192620155 6.228 ns/op 0 B/op 0 allocs/op
BenchmarkTypeAssertionToPointer-16 192099916 6.239 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssertToPointer-16 321093446 3.742 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssertToPointer-16 321307184 3.735 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssertToPointer-16 321660358 3.732 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssertToPointer-16 321054388 3.740 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssertToPointer-16 320267023 3.735 ns/op 0 B/op 0 allocs/op
BenchmarkReflectTypeAssertToPointer-16 320237029 3.736 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/miyamo2/reflect_type_assert_vs_type_assertion 14.402s
go tool benchstat -filter ".unit:(ns/op)" benchstat.txt
goos: linux
goarch: amd64
pkg: github.com/miyamo2/reflect_type_assert_vs_type_assertion
cpu: AMD EPYC 7763 64-Core Processor
│ benchstat.txt │
│ sec/op │
TypeAssertionToPointer-16 6.238n ± 0%
ReflectTypeAssertToPointer-16 3.736n ± 0%
geomean 4.827n
どちらもゼロアロケーションですが、処理時間に関しては若干reflect.TypeAssertに優位性があり、おおよそ1.6倍速いです
reflect.Value#Interface + 型アサーション に優位性があるケース
type ITime interface {
String() string
GoString() string
Format(layout string) string
...
}
func BenchmarkTypeAssertionToInterface(b *testing.B) {
rv := reflect.ValueOf(new(time.Time)).Elem()
b.ReportAllocs()
for b.Loop() {
_, ok := rv.Interface().(ITime)
if !ok {
b.Fatal("expected true but got false")
}
}
}
func BenchmarkReflectTypeAssertToInterface(b *testing.B) {
rv := reflect.ValueOf(new(time.Time)).Elem()
b.ReportAllocs()
for b.Loop() {
_, ok := reflect.TypeAssert[ITime](rv)
if !ok {
b.Fatal("expected true but got false")
}
}
}
最後はインターフェイスへ型アサーションします
go test -bench '^.*Interface$' -count 6
goos: linux
goarch: amd64
pkg: github.com/miyamo2/reflect_type_assert_vs_type_assertion
cpu: AMD EPYC 7763 64-Core Processor
BenchmarkTypeAssertionToInterface-16 31883131 35.70 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertionToInterface-16 33785035 35.60 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertionToInterface-16 33294741 35.43 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertionToInterface-16 34111364 35.60 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertionToInterface-16 33124920 35.32 ns/op 24 B/op 1 allocs/op
BenchmarkTypeAssertionToInterface-16 33972009 35.59 ns/op 24 B/op 1 allocs/op
BenchmarkReflectTypeAssertToInterface-16 29676243 39.54 ns/op 24 B/op 1 allocs/op
BenchmarkReflectTypeAssertToInterface-16 30570763 39.59 ns/op 24 B/op 1 allocs/op
BenchmarkReflectTypeAssertToInterface-16 30284253 39.46 ns/op 24 B/op 1 allocs/op
BenchmarkReflectTypeAssertToInterface-16 30373454 39.87 ns/op 24 B/op 1 allocs/op
BenchmarkReflectTypeAssertToInterface-16 29724702 39.51 ns/op 24 B/op 1 allocs/op
BenchmarkReflectTypeAssertToInterface-16 29738011 39.72 ns/op 24 B/op 1 allocs/op
PASS
ok github.com/miyamo2/reflect_type_assert_vs_type_assertion 14.267s
go tool benchstat -filter ".unit:(ns/op)" benchstat.txt
goos: linux
goarch: amd64
pkg: github.com/miyamo2/reflect_type_assert_vs_type_assertion
cpu: AMD EPYC 7763 64-Core Processor
│ benchstat.txt │
│ sec/op │
TypeAssertionToInterface-16 35.59n ± 1%
ReflectTypeAssertToInterface-16 39.57n ± 1%
geomean 37.53n
どちらも1アロケーション発生しますが、このパターンではreflect.Value#Interface + 型アサーションの方がおおよそ1.1倍速いです
まとめ
今回は
-
reflect.Value#Elemを介してTに型アサーションするケース -
reflect.Value#Elemを介さず*Tに型アサーションするケース -
T implements U->Uに型アサーションするケース
の3パターンを検証し、1と2でreflect.TypeAssertに優位性があることが確認できました
これを機にreflectパッケージの実装を深堀したかったのですが挫折してしまったのでまたの別の機会にリベンジしたいです
Discussion