发现了奇怪的代码:

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 函数,都尽可能在编译期完成计算

更多推荐