07/24
2021

reflect性能测试

本节来测试一下golang中的reflect常见方法的性能,以及对比断言的性能

interface断言

interface断言在很多时候都不太好看,主要对于后期维护上来说确实是相当困难。因为你不得不判断异常情况,否则会panic。

我们来对比一下基础类型、复合类型map、结构类型struct、指针类型分别耗时情况如何

// 源代码
func AssertInt(srcData interface{}) (int, error) {
	if val, ok := srcData.(int); ok {
		return val, nil
	}
	return -1, errors.New("srcData type not int")
}

func AssertFloat64(srcData interface{}) (float64, error) {
	if val, ok := srcData.(float64); ok {
		return val, nil
	}
	return -1, errors.New("srcData type not float")
}

func AssertMap(srcData interface{}) (map[string]interface{}, error) {
	if val, ok := srcData.(map[string]interface{}); ok {
		return val, nil
	}
	return nil, errors.New("srcData type not map")
}

func AssertMapPtr(srcData interface{}) (*map[string]interface{}, error) {
	if val, ok := srcData.(*map[string]interface{}); ok {
		return val, nil
	}
	return nil, errors.New("srcData type not map")
}

type Person struct {
	Name string
	Age  int
}

func AssertStruct(srcData interface{}) (p Person, err error) {
	if val, ok := srcData.(Person); ok {
		return val, nil
	}
	return p, errors.New("srcData type not struct")
}

func AssertStructPtr(srcData interface{}) (*Person, error) {
	if val, ok := srcData.(*Person); ok {
		return val, nil
	}
	return nil, errors.New("srcData type not struct")
}
// 测试代码
func BenchmarkAssertInt(b *testing.B) {
	for n := 0; n < b.N; n++ {
		AssertInt(1)
	}
}

func BenchmarkAssertFloat64(b *testing.B) {
	for n := 0; n < b.N; n++ {
		AssertFloat64(float64(1))
	}
}

var mapData = map[string]interface{}{"a": 1}

func BenchmarkAssertMap(b *testing.B) {
	for n := 0; n < b.N; n++ {
		AssertMap(mapData)
	}
}

func BenchmarkAssertMapPtr(b *testing.B) {
	for n := 0; n < b.N; n++ {
		AssertMapPtr(&mapData)
	}
}

var data = Person{"abc", 12}

func BenchmarkAssertStruct(b *testing.B) {
	for n := 0; n < b.N; n++ {
		AssertStruct(data)
	}
}

func BenchmarkAssertStructPtr(b *testing.B) {
	for n := 0; n < b.N; n++ {
		AssertStructPtr(&data)
	}
}
// go test -bench .
goos: darwin
goarch: amd64
pkg: blurty/test/benchtest
cpu: VirtualApple @ 2.50GHz
BenchmarkAssertInt-8            1000000000               0.3138 ns/op
BenchmarkAssertFloat64-8        1000000000               0.3145 ns/op
BenchmarkAssertMap-8            1000000000               0.3138 ns/op
BenchmarkAssertMapPtr-8         1000000000               0.3154 ns/op
BenchmarkAssertStruct-8         1000000000               0.3151 ns/op
BenchmarkAssertStructPtr-8      1000000000               0.3154 ns/op
PASS
ok      blurty/test/benchtest   2.799s

结论

类型 耗时 备注
int 0.3ns/op 基础类型还是很快的
float 0.3ns/op 基础类型还是很快的
map[string]interface{} 0.3ns/op 符合类型map断言也很快
*map[string]interface{} 0.3ns/op 符合类型map的指针断言也很快
struct 0.3ns/op 结构体类型断言也很快
struct pointer 0.3ns/op 结构体的指针类型断言也很快

无论是什么类型的数据,使用断言来做转换,都很快,每次操作只需要0.3ns

reflect.TypeOf

reflect.TypeOf可以说是最常见的一个方法了

// 源码
func TypeOf(srcData interface{}) reflect.Type {
	return reflect.TypeOf(srcData)
}
// 测试用例
func BenchmarkTypeOf(b *testing.B) {
	for n := 0; n < b.N; n++ {
		TypeOf(1)
	}
}
// go test -bench .
goos: darwin
goarch: amd64
pkg: blurty/test/benchtest
cpu: VirtualApple @ 2.50GHz
BenchmarkTypeOf-8       1000000000               0.3135 ns/op
PASS
ok      blurty/test/benchtest   1.067s

结论

方法 耗时 备注
TypeOf 0.3ns/op 很快

仅仅是TypeOf的性能很高,堪比断言。

通过TypeOf源码看看,可以看到基本就是在做了一下指针的类型转换,可以忽略不计的消耗。

func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}

reflect.ValueOf

// 代码
func ValueOf(srcData interface{}) reflect.Value {
	return reflect.ValueOf(srcData)
}

// 测试用例
func BenchmarkValueOf(b *testing.B) {
	for n := 0; n < b.N; n++ {
		ValueOf(1)
	}
}

// go test -bench .
goos: darwin
goarch: amd64
pkg: blurty/test/benchtest
cpu: VirtualApple @ 2.50GHz
BenchmarkValueOf-8      445375815                2.703 ns/op
PASS
ok      blurty/test/benchtest   2.210s

结论

ValueOf每次操作需要2.7ns,性能算式比较慢了

reflect.TypeOf.Kind

一般我们获取interface的类型,光是type和value还是不够的,还是需要从中解析出类型值来,这就需要用到Kind了。通过Kind的源码可以看到,是做了一个位运算的。

// Type Kind源码
func (t *rtype) Kind() Kind { return Kind(t.kind & kindMask) }
// 代码
func TypeKindOf(srcData interface{}) reflect.Kind {
	return reflect.TypeOf(srcData).Kind()
}

func ValueKindOf(srcData interface{}) reflect.Kind {
	return reflect.ValueOf(srcData).Kind()
}

func ValueKindOf2(val reflect.Value) reflect.Kind {
	return val.Kind()
}

func TypeKindOf2(typ reflect.Type) reflect.Kind {
	return typ.Kind()
}

// 测试用例
func BenchmarkTypeKindOf(b *testing.B) {
	for n := 0; n < b.N; n++ {
		TypeKindOf(1)
	}
}

func BenchmarkValueKindOf(b *testing.B) {
	for n := 0; n < b.N; n++ {
		ValueKindOf(1)
	}
}

var val2 = reflect.ValueOf(1)
var typ2 = reflect.TypeOf(1)

func BenchmarkValueKindOf2(b *testing.B) {
	for n := 0; n < b.N; n++ {
		ValueKindOf2(val2)
	}
}

func BenchmarkTypeKindOf2(b *testing.B) {
	for n := 0; n < b.N; n++ {
		TypeKindOf2(typ2)
	}
}

// go test -bench .
goos: darwin
goarch: amd64
pkg: blurty/test/benchtest
cpu: VirtualApple @ 2.50GHz
BenchmarkTypeKindOf-8           344061193                3.503 ns/op
BenchmarkValueKindOf-8          450969230                2.664 ns/op
BenchmarkValueKindOf2-8         1000000000               0.3132 ns/op
BenchmarkTypeKindOf2-8          574637876                2.086 ns/op
PASS
ok      blurty/test/benchtest   5.333s

结论

这个耗时就比较厉害了,比仅做类型转换的断言和TypeOf慢了一个数量级。

Type的Kind每次操作要3.5ns,刨去Type需要2ns

Value的Kind每次操作要2.7ns,刨去Value需要0.3ns

所以TypeOf要比ValueOf更快,ValueOf.Kind要比TypeOf.Kind更快。

疑问

TypeOf需要0.3ns,Type.Kind需要2ns,加起来是2.3ns。为什么TypeOf().Kind()组合起来需要3.5ns,多了1.2ns之多。

本站总访问量 本站访客数人次 本文总阅读量