类型断言/类型开关是否具有不良性能/在Go中速度慢?

Ory*_*and 41 casting type-conversion go type-assertion

在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")
    }
}
Run Code Online (Sandbox Code Playgroud)

sir*_*nga 33

编写基准测试来检查它非常容易: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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

  • 刚刚在强劲的MacBook Pro上使用go1.7进行测试,结果:BenchmarkIntmethod-8 2000000000 1.97 ns/op BenchmarkInterface-8 1000000000 2.27 ns/op BenchmarkTypeSwitch-8 2000000000 1.90 ns/op BenchmarkTypeAssertion-8 2000000000 1.89 ns/op PASS - ---------好像,几乎没有开销了:-) (23认同)
  • 我会从你的答案中删除"cast".类型转换不同于类型转换,如果不使用`unsafe`包,则不允许.您还忘记了一个裸型断言,它比类型开关略快. (2认同)
  • @physphun:类型 switch 语句中的案例数不影响此基准测试的性能。这个 Playground 示例包含两种实现 Inccer 接口的类型 https://play.golang.org/p/s4SEk6Ell6,性能与在类型切换中仅使用一种类型时的性能相同。新的编译器必须独立于类型断言开关中的案例数量做聪明的事情。 (2认同)

Ted*_*Ted 12

我想自己验证siritinga的答案,并检查删除TypeAssertion中的检查是否会使它更快.我在其基准测试中添加了以下内容:

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)
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

所以看起来做类型切换的成本从Go 1.4(我假设siritinga使用)到Go 1.6(我正在使用)显着下降:从一个类型慢5-6倍到小于2倍切换,类型断言没有减速(有或没有检查).


zer*_*r09 9

我的结果使用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
Run Code Online (Sandbox Code Playgroud)

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

  • @Seer 你的评论意味着什么?抱歉我没明白。 (2认同)

Pat*_*ick 5

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
Run Code Online (Sandbox Code Playgroud)

这里要注意的一件重要事情是,只有一个分支的类型开关与使用接口的比较不是很公平。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()
                        }
                }
        }
}
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

类型越多,分布越随机,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()
                        }
                }
        }
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

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
Run Code Online (Sandbox Code Playgroud)