陈哈哈的博客

Go语言重点笔记-类型断言(Type-Assertion)的性能

2018-04-11

本文为原创文章,转载请注明出处:http://yoojia.xyz。 如果觉得文章还不错,请分享到朋友圈吧,感谢支持。

最近一直在做公司数据处理底层框架go-messages项目的TPS优化。

首先是减少channel的传递层级,消息FanIn/FanOut的处理。在开发PC机中,TPS从10W+上升到30W+,然后再也无法提升。几经尝试,把Decode/Encode等大量消耗性能的操作Mock成空操作后,TPS提升到100W+。这是巨大的提升,但没什么实质意义,在实际生产环境中,必定需要这些耗时的序列化反序列化操作。

在偶然一个注释掉几行Setter函数时,发现TPS突然从100W+上升到120W+,这到底什么回事?


性能变化的代码如下(经过处理):

switch body.(type) {
case []byte:
slf.bytes = body.([]byte)
default:
slf.ref = body
}
slf.bodyType = reflect.TypeOf(body)

反射获取Body类型的操作,性能有所影响这没什么问题。注释后,TPS在105W左右波动,这也很正常,但上升到120W是什么原因造成的呢?

想来想去,觉得可能type-switch可能在语言层次上内置了特殊处理。注释了相关代码一看,果然如此。

发现对Go的类型转换思维,还停留在C/C++这种简单的类型断言方式上面。

一、type-switch 性能影响

为了查明 type-switch 对性能的影响,我写了如下测试代码:

定义两个接口,然后测试他们三种调用方式:

  1. 直接调用方法函数的性能;
  2. 通过直接类型断言来调用方法函数的性能;
  3. 通过type-switch方式判断类型来调用方法函数的性能;
  4. 通过带检查的类型断言来调用方法函数的性能;

测试结果如下:

$ go test -test.bench=".*" playground 
goos: linux
goarch: amd64
pkg: playground
Benchmark_TypeSwitchCall-8 100000000 17.4 ns/op
Benchmark_DirectlyCall-8 2000000000 0.26 ns/op
Benchmark_CastCall-8 200000000 10.0 ns/op
Benchmark_CheckCastCall-8 100000000 10.3 ns/op
PASS
ok playground 6.372s

其中:

  • Benchmark_TypeSwitchCall 是type-switch类型判断的代码性能测试
  • Benchmark_DirectlyCall 是直接调用的代码性能测试
  • Benchmark_CastCall 是类型断言调用的代码性能测试
  • Benchmark_CheckCastCall 是带检查的类型断言调用的代码性能测试

性能测试结论:

与直接调用方法函数相比:

  • 单次调用type-switch需要花费约17倍左右时间。
  • 单次调用类型断言需要10倍左右时间。

二、如何优化

先来看上例子的两种类型断言代码:

第一种:直接类型断言

any.(TypeA).FuncA()

第二种:带检查的类型断言

if cc, ok := any.(TypeA); ok {
cc.FuncA()
}

Benchmark_CastCallBenchmark_CheckCastCall对比可以看出,带检查的类型断言与直接类型断言所花费的时间是相同的。
类型断言会出现成功的失败两种情况。CheckCast返回一个断言是否成功的标志位。这是一种安全的类型转换,它提供了断言失败的判断。
直接断言后调用,在断言失败的情况下,会Panic。

在单个类型断言的情况下,如例子所示只有一种TypeA类型,使用 check-cast 肯定比 type-switch 性能要好。

但在多种类型检查的情况下,使用if-else嵌套来实现:最好的情况下第一个if完成判断,最坏的情况下走完所有if流程,所以使用type-switch依然是最好的方案。

但必须要记住一点是,在系统开发中,性能瓶颈往往都出现在业务逻辑而非底层逻辑上。

三、为什么类型断言会影响性能

类型转换涉及运行时安全检查

在Go运行时,为了确保类型安全,类型判断需要执行一系列的检查操作。
同样在A接口转换为B接口时性能影响更甚,因为它需要检查和确保一个接口是否实现了另一个接口。

参考

  1. Does a type assertion / type switch have bad performance / is slow in Go?

  2. golang: how to explain the type assertion efficiency? [closed]