GOLANG编码小技巧]对数组元素赋值时,先赋值尾部再赋值头部就会变快
性能非常强悍,我自己写的版本始终没干过它。(在我的新版推出以前)vtprotobuf 可以算是 golang 领域最快的 protobuf 库。
为什么我就比不过它呢?我看到了这样的看不懂的代码:
func (m *Child) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.ChildName) > 0 {
i -= len(m.ChildName)
copy(dAtA[i:], m.ChildName)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ChildName)))
i–
dAtA[i] = 0x12
}
if m.ChildId != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ChildId))
i–
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
可以发现,这个库的特点是: 先对数组的尾部赋值,然后下标向前偏移,然后再对数组首部进行赋值。
难道这样就会变快?
Yes!
下面我就拆解一下变快的原因:
先看下面的两个函数:
func f1(arr []byte) {
arr[0] = 1
arr[9] = 2
}
func f2(arr []byte) {
arr[9] = 2
arr[0] = 1
}
功能完全一样,只是顺序不同。
下面用命令行来检查数组越界检查:
go tool compile -d=ssa/check_bce/debug=1 bce.go
可以发现:
func f1(arr []byte) {
arr[0] = 1 // 仍有 bounds check
arr[9] = 2 // 仍有 bounds check
}
func f2(arr []byte) {
arr[9] = 2 // 仍有 bounds check
arr[0] = 1 // 没有 Found,说明这个检查被消掉了
}
由此说明:如果先出现了比较大的下标,再出现小的下标,那么编译器就能推断后续的数组访问一定没越界,由此便不再产生越界检查的代码。
从 golang 源码本身也能发现证据:
Go 编译器源码证据主要在 cmd/compile/internal/ssa/prove.go。OpIsInBounds 表示