GOLANG 编译期的`常量折叠`的研究
·
发现了奇怪的代码:
func (m *Child) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.ChildId != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.ChildId))
}
l = len(m.ChildName)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
return n
}
这个函数的作用是:计算一个类型序列化后,要占多大的空间。
其中可以发现两个常量值 1.
我再翻翻我自己的实现,我是这样写的:
func (m *Child) ProtobufSize() int {
size := 0
if m.ChildName != "" {
n := len(m.ChildName)
size += TagSize(ChildChildNameTag, LenDelim) + utils.VarintSize(uint64(n)) + n
}
if m.ChildID != 0 {
size += TagSize(ChildChildIDTag, Varint) + utils.VarintSize(uint64(m.ChildID))
}
return size
}
原来,这个 1 是 tagID + 数据类型编码后占 1 字节。
难怪他的库更快,他相当于做了“编译期计算”,提前得到了函数运算的结果。
我好奇的是,如果函数的参数都是常量,为什么编译器没有为我计算好?
我收集了如下知识:
编译器常量折叠通常受哪些因素影响?
条件 1:函数是否能内联
- 函数很小,能被内联:
func A(a byte, b byte) byte {
return (a & 0x0f) | (b & 0xf0)
}
- 如果函数太复杂,不能内联,编译器一般不会跨函数直接求值。
func A(a byte, b byte) byte {
for i := 0; i < 10; i++ {
a ^= byte(i)
}
return (a & 0x0f) | (b & 0xf0)
}
函数如果没有被内联,调用点就不会被折叠成常量。
条件 2:实参是否是编译期已知值
- 可以折叠的情况:
func A(a byte, b byte) byte{
return (a&0x0f) | (b&0xf0)
}
a := A(0x1f, 0x2f)
- 不能折叠:
x := byte(time.Now().Unix())
a := A(x, 0x2f)
畅享:希望golang 提供的编译期计算能力
- const 的值可以调用一个函数来得到
const name = function_call(123)
- constexpr 关键字
constexpr func A(...){}
a := A(12345) // 对于 constexpr 函数,都尽可能在编译期完成计算更多推荐
所有评论(0)