Golang各种测试总结

909 阅读4分钟
1.类型判断和类型转换这两个操作都比直接操作多几倍的消耗。
func TypeSwitch(v interface{}) {
	switch v.(type) {
	case InterfaceA:
		v.(InterfaceA).AA()
	case InterfaceB:
		v.(InterfaceB).BB()
	}
}

func NormalSwitch(a *A) {
	a.AA()
}

func InterfaceSwitch(v interface{}) {
	v.(InterfaceA).AA()
}

 Benchmark_TypeSwitch 50000000 33.0 ns/op

Benchmark_NormalSwitch 2000000000 1.99 ns/op

Benchmark_InterfaceSwitch 100000000 18.4 ns/op


2.指针传参比值传参效率高很多。

指针传参 Benchmark_Invoke1 2000000000 0.52 ns/op

值传参 Benchmark_Invoke2 100000000 12.8 ns/op


3.对象创建的效率。用new一个指针或者直接初始化更快.

func NewStruct1() *BigStruct {
	return new(BigStruct)
}

func NewStruct2() *BigStruct {
	return &BigStruct{}
}

func NewStruct3() *BigStruct {
	var r = new(BigStruct)
	*r = BigStruct{}
	return r
}

func NewStruct4(r *BigStruct) {
	*r = BigStruct{}
}

func NewStruct5(r *BigStruct) {
	r.C01 = 0
	r.C02 = 0
	r.C03 = 0
	r.C04 = 0
}

Benchmark_NewStruct1 100000000 13.0 ns/op

Benchmark_NewStruct2 100000000 24.5 ns/op

Benchmark_NewStruct3 50000000 37.8 ns/op

Benchmark_NewStruct4 100000000 25.1 ns/op

Benchmark_NewStruct5 200000000 8.65 ns/op


4.对结构体列表的range循环最消耗性能,因为数据要重复复制,Loop2是因为复制指针比较快。

func Loop1(a []*BigStruct) int {
	var n = 0

	for i := 0; i < len(a); i++ {
		n += a[i].C30
	}

	return n
}

func Loop2(a []*BigStruct) int {
	var n = 0

	for _, item := range a {
		n += item.C30
	}

	return n
}

func Loop3(a []BigStruct) int {
	var n = 0

	for i := 0; i < len(a); i++ {
		n += a[i].C30
	}

	return n
}

func Loop4(a []BigStruct) int {
	var n = 0

	for _, item := range a {
		n += item.C30
	}

	return n
}


Benchmark_Loop1 2000000 923 ns/op Benchmark_Loop2 2000000 819 ns/op

Benchmark_Loop3 2000000 825 ns/op Benchmark_Loop4 100000 26230 ns/op


5.测试中浮点数除法比乘法慢,但是我测试的时候,发现是一样的,可能是新版本的Golang在这方面优化了.


6.硬编码 < 指针slice的range循环 < for循环,但是量级是一样的,看情况用。但是map差了一个量级,小数据量尽量少用map。


7.测试几种链表遍历查找的性能差异.


8.用slice替代链表结构,简单循环slice明显更快,但是加入查询后, slice的优势就可以忽略不计了。但是slice可以优化GC.


9.测试中用的是一个很简单的函数,所以很容易被新版的go做内联优化,禁用优化前后结果差别明显,如果不考虑优化,匿名函数和普通函数调用是一个量级的消耗,差别甚微。但是普通函数比较有可能被优化。在没有优化的情况下,闭包函数消耗又要再高一个量级。

func NormalFunc(i int) int {
	return i + 1
}
//普通函数调用
func Benchmark_NormalFuncCall(b *testing.B) {
	for i := 0; i < b.N; i++ {
		NormalFunc(i)
	}
}
//匿名函数
func Benchmark_VarFuncCall1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		VarFunc := func(i int) int {
			return i + 1
		}

		VarFunc(i)
	}
}
//闭包函数
func Benchmark_VarFuncCall2(b *testing.B) {
	for i := 0; i < b.N; i++ {
		VarFunc := func() int {
			return i + 1
		}
		VarFunc()
	}
}


禁用优化前: dada-imac:labs dada$ go test -test.bench="." labs09 Benchmark_NormalFuncCall-4 2000000000 0.33 ns/op

Benchmark_VarFuncCall1-4 2000000000 0.33 ns/op

Benchmark_VarFuncCall2-4 1000000000 2.31 ns/op

PASS ok labs09 3.949s

禁用优化后: dada-imac:labs dada$ go test -test.bench="." -gcflags '-N' labs09

testing: warning:

no tests to run PASS

Benchmark_NormalFuncCall-4 1000000000 2.63 ns/op

Benchmark_VarFuncCall1-4 1000000000 2.64 ns/op

Benchmark_VarFuncCall2-4 500000000 3.98 ns/op

PASS ok labs09 8.216s


10.尝试绕过gc扫描,实际用到项目里发现会导致在其他地方出现内存分配死锁的错误,所以不敢实际使用,并且经过测试发现mallocgc还是不能绕过gc扫描,反而C.malloc更简单有效。


11.测试json序列化支持的类型


12.测试jemalloc和malloc在go程序中是否有性能差别。

var sizeof_BigStruct = C.size_t(unsafe.Sizeof(BigStruct{}))

func NewBigStruct() *BigStruct {
	return (*BigStruct)(C.calloc(1, sizeof_BigStruct))
}
func AllocAndFree() {
	var ptr = C.malloc(sizeof_BigStruct)

	C.free(ptr)
}

func JeAllocAndFree() {
	var ptr = C.je_malloc(sizeof_BigStruct)

	C.je_free(ptr)
}

func Benchmark_AllocAndFree(b *testing.B) {
	for i := 0; i < b.N; i++ {
		AllocAndFree()
	}
}

func Benchmark_JeAllocAndFree(b *testing.B) {
	for i := 0; i < b.N; i++ {
		JeAllocAndFree()
	}
}


实验结果: dada-imac:labs dada$ go test -test.bench=".*" labs12 PASS Benchmark_AllocAndFree 10000000 190 ns/op

Benchmark_JeAllocAndFree 20000000 135 ns/op

ok labs12 4.971s

实验结论:性能还是有差别的


13.

a. 存对象指针的map和存结构体的map,每条数据都占用一个对象数量,两种数据结构无差异.

b. 存对象的slice和存结构体的slice有明显差异,存结构体的slice不重复占用对象数量.

c. 未初始化的对象类型字段是不占用对象数量的


14.测试用cgo复制go的内存数据和还原数据


15.看不懂,略


16.测试平方根算法的效率,总结就是多用math库


17.

a.把正则编译提取到外部,避免反复初始化

b.把map的key换成rune,避免反复生成字符串


18.测试switch和回调函数效率差异,回调函数快了一点

func New() *MyObject {
	return &MyObject{
		n:    4,
		head: make([]byte, 4),
		bo:   binary.LittleEndian,
	}
}

func NewCallback() *MyObject {
	return &MyObject{
		n:    4,
		head: make([]byte, 4),
		callback: func(head []byte) int {
			return int(binary.LittleEndian.Uint32(head)) + 10
		},
	}
}

func (obj *MyObject) UseSwitch() int {
	size := 0

	switch obj.n {
	case 1:
		size = int(obj.head[0])
	case 2:
		size = int(obj.bo.Uint16(obj.head))
	case 4:
		size = int(obj.bo.Uint32(obj.head))
	case 8:
		size = int(obj.bo.Uint64(obj.head))
	default:
		panic("unsupported packet head size")
	}

	return size + 10
}

func (obj *MyObject) UseCallback() int {
	return obj.callback(obj.head)
}


dada-imac:labs18 dada$ go test -bench=".*"

testing: warning: no tests to run PASS

Benchmark_UseSwitch 200000000 9.58 ns/op

Benchmark_UseCallback 200000000 8.05 ns/op

ok github.com/idada/go-labs/labs18 5.323s


19.测试缓存反射信息对效率的影响

```

dada-imac:src dada$ go test labs19 --bench=".*" -v

=== RUN Test_Verify

--- PASS: Test_Verify (0.00 seconds)

=== RUN Test_FastVerify

--- PASS: Test_FastVerify (0.00 seconds)

=== RUN Test_VeryFastVerify

--- PASS: Test_VeryFastVerify (0.00 seconds) PASS

Benchmark_________Verify 1000000 1399 ns/op

Benchmark_____FastVerify 10000000 178 ns/op

Benchmark_VeryFastVerify 20000000 93.9 ns/op

ok labs19 5.364s

```

第一种跟第三种差了十倍左右的速度



------------------------------------------------------

转达达的代码链接:https://github.com/bg5sbk/go-labs