字节跳动 Go RPC 框架 KiteX 性能优化实践( 四 )

生成代码中也做相应改造:
func (p *Demo) BLength() int {        l := 0        l += bthrift.Binary.StructBeginLength("Demo")        if p != nil {                l += p.field1Length()                l += p.field2Length()                l += p.field3Length()    ...        }        l += bthrift.Binary.FieldStopLength()        l += bthrift.Binary.StructEndLength()        return l}func (p *Demo) FastWrite(buf []byte) int {        offset := 0        offset += bthrift.Binary.WriteStructBegin(buf[offset:], "Demo")        if p != nil {                offset += p.fastWriteField2(buf[offset:])                offset += p.fastWriteField4(buf[offset:])                offset += p.fastWriteField1(buf[offset:])                offset += p.fastWriteField3(buf[offset:])        }        offset += bthrift.Binary.WriteFieldStop(buf[offset:])        offset += bthrift.Binary.WriteStructEnd(buf[offset:])        return offset}使用 SIMD 优化 Thrift 编码公司内广泛使用 list<i64/i32> 类型来承载 ID 列表,并且 list<i64/i32> 的编码方式十分符合向量化的规律,于是我们用了 SIMD 来优化 list<i64/i32> 的编码过程 。
我们使用了 avx2,优化后的结果比较显著,在大数据量下针对 i64 可以提升 6 倍性能,针对 i32 可以提升 12 倍性能;在小数据量下提升更明显,针对 i64 可以提升 10 倍,针对 i32 可以提升 20 倍 。
减少函数调用inlineinline 是在编译期间将一个函数调用原地展开,替换成这个函数的实现,它可以减少函数调用的开销以提高程序的性能 。
在 Go 中并不是所有函数都能 inline,使用参数-gflags="-m"运行进程,可显示被 inline 的函数 。以下几种情况无法内联:

  1. 包含循环的函数;
  2. 包含以下内容的函数:闭包调用,select,for,defer,go 关键字创建的协程;
  3. 超过一定长度的函数,默认情况下当解析 AST 时,Go 申请了 80 个节点作为内联的预算 。每个节点都会消耗一个预算 。比如,a = a + 1 这行代码包含了 5 个节点:AS, NAME, ADD, NAME, LITERAL 。当一个函数的开销超过了这个预算,就无法内联 。
编译时通过指定参数-l可以指定编译器对代码内联的强度(go 1.9+),不过这里不推荐大家使用,在我们的测试场景下是 buggy 的,无法正常运行:
// The debug['l'] flag controls the aggressiveness. Note that main() swaps level 0 and 1, making 1 the default and -l disable. Additional levels (beyond -l) may be buggy and are not supported.//      0: disabled//      1: 80-nodes leaf functions, oneliners, panic, lazy typechecking (default)//      2: (unassigned)//      3: (unassigned)//      4: allow non-leaf functions内联虽然可以减少函数调用的开销,但是也可能因为存在重复代码,从而导致 CPU 缓存命中率降低,所以并不能盲目追求过度的内联,需要结合 profile 结果来具体分析 。
go test -gcflags='-m=2' -v -test.run TestNewCodec 2>&1 | grep "function too complex" | wc -l48go test -gcflags='-m=2 -l=4' -v -test.run TestNewCodec 2>&1 | grep "function too complex" | wc -l25


推荐阅读