Go语言对象跟闭包性能比较

2015-03-09

上篇文章中提到了一下这个问题,但我之前只是写“直觉上认为”,并且还不解释。因为我了解Go的底层,知道方法和闭包分别是怎么实现的,然后用直觉做的判断。但是事后觉得写博客不能这么不严谨,所以做了个测试。

结论大致是对的,但结果也有一些出乎之间意料的地方。

假设我们要实现下面这个接口:

type I interface {
    XXX()
}

分别用闭包和对象的方式实现。闭包或是对象,在这里都是想封装一些状态。用对象的写法:

type Object struct {
    A int
}

func (obj *Object) XXX() {
    obj.A++
}

用闭包的写法:

func Closure() func() {
    var A int
    return func() {
        A++
    }
}

为了实现接口I,闭包写法需要一点辅助:

type IFunc func()

func (f IFunc) XXX() {
    f()
}

现在测试:

var (
    obj I
    clo I
)

obj = &Object{}
clo = IFunc(Closure())

for i := 0; i < 10000000; i++ {
    obj.XXX()
}
for i := 0; i < 10000000; i++ {
    clo.XXX()
}

在我的机器上得到的结果:

object: 30.026664ms
closure: 54.920985ms

所以说结论大致是对的。


我又测试了一下直接调用Object.XXX()跟Closure()(),两者性能其实没差异。主要问题是在闭包写法多了一层转换步骤,直觉地认为闭包比方法慢是有偏见的。这是我所说的有点出乎意料的地方。

Go语言中,方法在底层的实现,实际上就是普通函数多加了一个参数,将对象作为这个参数。比如上面的

func (obj *Object) XXX() {
    obj.A++
}

变换为底层的处理就是

func XXX(obj *Object) {
    obj.A++
}

至于闭包,则是会将一个函数指针和各个upvalue打包到一起。

func Closure() func() {
    var A int
    return func() {
        A++
    }
}

转化之后等价于

func fun001(A *int) {
    (*A)++
}
func Closure() struct {
    return &struct{
        fun001,
        new(int),
    }
}

应该说,性能上真的没有差异。可能对象写法比闭包写法对内存更友好一点点。

如果用两种方式都可以实现时,到底是选对象还是闭包呢?如果纠结的话,我还是更推荐对象写法一些。如果不纠结的话,都可以,无所谓。

golangclosureobject