类型断言/类型切换在 Go 中是否性能不佳/速度慢?

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

在 Go 中使用类型断言/类型开关作为 运行 时间类型发现的方法有多慢?

我听说,例如在 C/C++ 中,在 运行 时发现类型的性能很差。要绕过它,您通常将类型成员添加到 类,这样您就可以与这些成员进行比较而不是强制转换。

我在整个 www.

上都没有找到明确的答案

这是我要问的一个例子 - 与其他类型检查方法(如上所述,或其他我不知道的方法)相比,这是否被认为是快速 )?

func question(anything interface{}) {
    switch v := anything.(type) {
        case string:
            fmt.Println(v)
        case int32, int64:
            fmt.Println(v)
        case SomeCustomType:
            fmt.Println(v)
        default:
            fmt.Println("unknown")
    }
}

编写基准测试来检查它非常容易:http://play.golang.org/p/E9H_4K2J9-

package main

import (
    "testing"
)

type myint int64

type Inccer interface {
    inc()
}

func (i *myint) inc() {
    *i = *i + 1
}

func BenchmarkIntmethod(b *testing.B) {
    i := new(myint)
    incnIntmethod(i, b.N)
}

func BenchmarkInterface(b *testing.B) {
    i := new(myint)
    incnInterface(i, b.N)
}

func BenchmarkTypeSwitch(b *testing.B) {
    i := new(myint)
    incnSwitch(i, b.N)
}

func BenchmarkTypeAssertion(b *testing.B) {
    i := new(myint)
    incnAssertion(i, b.N)
}

func incnIntmethod(i *myint, n int) {
    for k := 0; k < n; k++ {
        i.inc()
    }
}

func incnInterface(any Inccer, n int) {
    for k := 0; k < n; k++ {
        any.inc()
    }
}

func incnSwitch(any Inccer, n int) {
    for k := 0; k < n; k++ {
        switch v := any.(type) {
        case *myint:
            v.inc()
        }
    }
}

func incnAssertion(any Inccer, n int) {
    for k := 0; k < n; k++ {
        if newint, ok := any.(*myint); ok {
            newint.inc()
        }
    }
}

编辑 2019 年 10 月 9 日

看起来上面演示的方法是平等的,没有优势。这是我的机器(AMD R7 2700X,Golang v1.12.9)的结果:

BenchmarkIntmethod-16           2000000000           1.67 ns/op
BenchmarkInterface-16           1000000000           2.03 ns/op
BenchmarkTypeSwitch-16          2000000000           1.70 ns/op
BenchmarkTypeAssertion-16       2000000000           1.67 ns/op
PASS

又一次:

BenchmarkIntmethod-16           2000000000           1.68 ns/op
BenchmarkInterface-16           1000000000           2.01 ns/op
BenchmarkTypeSwitch-16          2000000000           1.66 ns/op
BenchmarkTypeAssertion-16       2000000000           1.67 ns/op

2015 年 1 月 19 日的先前结果

在我的 amd64 机器上,我得到以下时间:

$ go test -bench=.
BenchmarkIntmethod  1000000000           2.71 ns/op
BenchmarkInterface  1000000000           2.98 ns/op
BenchmarkTypeSwitch 100000000           16.7 ns/op
BenchmarkTypeAssertion  100000000       13.8 ns/op

所以看起来通过类型切换或类型断言访问方法比直接或通过接口调用方法慢 5-6 倍。

我不知道 C++ 是否更慢,或者您的应用程序是否可以容忍这种减慢。

在你的

switch v := anything.(type) {
    case SomeCustomType:
        fmt.Println(v)
...

如果您不需要 SomeCustomType.Fieldsfmt.Println(v) 中的方法,请执行

switch anything.(type) { //avoid 'v:= ' interface conversion, only assertion
    case SomeCustomType:
        fmt.Println("anything type is SomeCustomType", anything)
...

应该快大约两倍

我想自己验证一下siritinga的回答,看看去掉TypeAssertion中的check会不会更快。我在他们的基准测试中添加了以下内容:

func incnAssertionNoCheck(any Inccer, n int) {
    for k := 0; k < n; k++ {
        any.(*myint).inc()
    }
}

func BenchmarkTypeAssertionNoCheck(b *testing.B) {
    i := new(myint)
    incnAssertionNoCheck(i, b.N)
}

并重新运行我机器上的基准测试。

BenchmarkIntmethod-12               2000000000           1.77 ns/op
BenchmarkInterface-12               1000000000           2.30 ns/op
BenchmarkTypeSwitch-12              500000000            3.76 ns/op
BenchmarkTypeAssertion-12           2000000000           1.73 ns/op
BenchmarkTypeAssertionNoCheck-12    2000000000           1.72 ns/op

因此,从 Go 1.4(我假设使用了 siritinga)到 Go 1.6(我正在使用),进行类型切换的成本似乎显着下降:对于类型切换,从慢 5-6 倍到不到 2 倍,并且对于类型断言(有或没有检查)没有减速。

我在笔记本电脑 (go1.7.3 linux/amd64) 中 运行 @siritinga 的工作台示例,得到了这个结果:

$ go test -bench .
BenchmarkIntmethod-4            2000000000               1.99 ns/op
BenchmarkInterface-4            1000000000               2.30 ns/op
BenchmarkTypeSwitch-4           2000000000               1.80 ns/op
BenchmarkTypeAssertion-4        2000000000               1.67 ns/op

我使用 Go 1.9 的结果

BenchmarkIntmethod-4                1000000000           2.42 ns/op
BenchmarkInterface-4                1000000000           2.84 ns/op
BenchmarkTypeSwitch-4               1000000000           2.29 ns/op
BenchmarkTypeAssertion-4            1000000000           2.14 ns/op
BenchmarkTypeAssertionNoCheck-4     1000000000           2.34 ns/op

类型断言现在快多了,但最有趣的是,删除类型检查会使它变慢。

TL;DR:它确实取决于类型分布,但接口是最安全的选择,除非您确定类型会出现在常规块中。还要考虑如果您的代码不经常执行,分支预测器也不会预热。

详细解释:

在 darwin/amd64

上进行 go1.9.2
BenchmarkIntmethod-4                2000000000           1.67 ns/op
BenchmarkInterface-4                2000000000           1.89 ns/op
BenchmarkTypeSwitch-4               2000000000           1.26 ns/op
BenchmarkTypeAssertion-4            2000000000           1.41 ns/op
BenchmarkTypeAssertionNoCheck-4     2000000000           1.61 ns/op

这里要注意的一件重要事情是,只有一个分支的类型切换与使用接口相比并不是一个非常公平的比较。 CPU 分支预测器将变得非常热、非常快并给出非常好的结果。更好的基准将使用伪随机类型和带有伪随机接收器的接口。显然,我们需要删除静态方法分派并坚持只使用接口而不是类型开关(类型断言也变得不那么有意义,因为它需要很多 if 语句,而且没有人会写它而不是使用类型开关)。这是代码:

package main

import (
        "testing"
)

type myint0 int64
type myint1 int64
type myint2 int64
type myint3 int64
type myint4 int64
type myint5 int64
type myint6 int64
type myint7 int64
type myint8 int64
type myint9 int64

type DoStuff interface {
        doStuff()
}

func (i myint0) doStuff() {
        i += 0
}

func (i myint1) doStuff() {
        i += 1
}

func (i myint2) doStuff() {
        i += 2
}

func (i myint3) doStuff() {
        i += 3
}

func (i myint4) doStuff() {
        i += 4
}

func (i myint5) doStuff() {
        i += 5
}

func (i myint6) doStuff() {
        i += 6
}

func (i myint7) doStuff() {
        i += 7
}

func (i myint8) doStuff() {
        i += 8
}

func (i myint9) doStuff() {
        i += 9
}

// Randomly generated
var input []DoStuff = []DoStuff{myint0(0), myint1(0), myint1(0), myint5(0), myint6(0), myint7(0), myint6(0), myint9(0), myint7(0), myint7(0), myint6(0), myint2(0), myint9(0), myint0(0), myint2(0), myint3(0), myint5(0), myint1(0), myint4(0), myint0(0), myi
nt4(0), myint3(0), myint9(0), myint3(0), myint9(0), myint5(0), myint0(0), myint0(0), myint8(0), myint1(0)}

func BenchmarkInterface(b *testing.B) {
        doStuffInterface(b.N)
}

func BenchmarkTypeSwitch(b *testing.B) {
        doStuffSwitch(b.N)
}

func doStuffInterface(n int) {
        for k := 0; k < n; k++ {
                for _, in := range input {
                        in.doStuff()
                }
        }
}

func doStuffSwitch(n int) {
        for k := 0; k < n; k++ {
                for _, in := range input {
                        switch v := in.(type) {
                        case *myint0:
                                v.doStuff()
                        case *myint1:
                                v.doStuff()
                        case *myint2:
                                v.doStuff()
                        case *myint3:
                                v.doStuff()
                        case *myint4:
                                v.doStuff()
                        case *myint5:
                                v.doStuff()
                        case *myint6:
                                v.doStuff()
                        case *myint7:
                                v.doStuff()
                        case *myint8:
                                v.doStuff()
                        case *myint9:
                                v.doStuff()
                        }
                }
        }
}

结果:

go test -bench .
goos: darwin
goarch: amd64
pkg: test
BenchmarkInterface-4        20000000            74.0 ns/op
BenchmarkTypeSwitch-4       20000000           119 ns/op
PASS
ok      test    4.067s

类型越多,分布越随机,win界面越大

为了显示这种差异,我将代码更改为基准随机选择与始终选择相同类型。在这种情况下,typeswitch 再次更快,而接口速度相同,代码如下:

package main

import (
        "testing"
)

type myint0 int64
type myint1 int64
type myint2 int64
type myint3 int64
type myint4 int64
type myint5 int64
type myint6 int64
type myint7 int64
type myint8 int64
type myint9 int64

type DoStuff interface {
        doStuff()
}

func (i myint0) doStuff() {
        i += 0
}

func (i myint1) doStuff() {
        i += 1
}

func (i myint2) doStuff() {
        i += 2
}

func (i myint3) doStuff() {
        i += 3
}

func (i myint4) doStuff() {
        i += 4
}

func (i myint5) doStuff() {
        i += 5
}

func (i myint6) doStuff() {
        i += 6
}

func (i myint7) doStuff() {
        i += 7
}

func (i myint8) doStuff() {
        i += 8
}

func (i myint9) doStuff() {
        i += 9
}

// Randomly generated
var randInput []DoStuff = []DoStuff{myint0(0), myint1(0), myint1(0), myint5(0), myint6(0), myint7(0), myint6(0), myint9(0), myint7(0), myint7(0), myint6(0), myint2(0), myint9(0), myint0(0), myint2(0), myint3(0), myint5(0), myint1(0), myint4(0), myint0(0),
 myint4(0), myint3(0), myint9(0), myint3(0), myint9(0), myint5(0), myint0(0), myint0(0), myint8(0), myint1(0)}

var oneInput []DoStuff = []DoStuff{myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), 
myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0), myint1(0)}

func BenchmarkRandomInterface(b *testing.B) {
        doStuffInterface(randInput, b.N)
}

func BenchmarkRandomTypeSwitch(b *testing.B) {
        doStuffSwitch(randInput, b.N)
}

func BenchmarkOneInterface(b *testing.B) {
        doStuffInterface(oneInput, b.N)
}

func BenchmarkOneTypeSwitch(b *testing.B) {
        doStuffSwitch(oneInput, b.N)
}

func doStuffInterface(input []DoStuff, n int) {
        for k := 0; k < n; k++ {
                for _, in := range input {
                        in.doStuff()
                }
        }
}

func doStuffSwitch(input []DoStuff, n int) {
        for k := 0; k < n; k++ {
                for _, in := range input {
                        switch v := in.(type) {
                        case *myint0:
                                v.doStuff()
                        case *myint1:
                                v.doStuff()
                        case *myint2:
                                v.doStuff()
                        case *myint3:
                                v.doStuff()
                        case *myint4:
                                v.doStuff()
                        case *myint5:
                                v.doStuff()
                        case *myint6:
                                v.doStuff()
                        case *myint7:
                                v.doStuff()
                        case *myint8:
                                v.doStuff()
                        case *myint9:
                                v.doStuff()
                        }
                }
        }
}

结果如下:

BenchmarkRandomInterface-4      20000000            76.9 ns/op
BenchmarkRandomTypeSwitch-4     20000000           115 ns/op
BenchmarkOneInterface-4         20000000            76.6 ns/op
BenchmarkOneTypeSwitch-4        20000000            68.1 ns/op