Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interface function invocation is horribly slow? #20116

Closed
avinoamr opened this issue Apr 25, 2017 · 1 comment
Closed

Interface function invocation is horribly slow? #20116

avinoamr opened this issue Apr 25, 2017 · 1 comment

Comments

@avinoamr
Copy link

avinoamr commented Apr 25, 2017

I've had several types implementing the same interface. Invoking functions on the interface had horrible performance compared to running them on the concrete type:

import "testing"

type D interface {
    Append(D)
}

type Strings []string

func (s Strings) Append(d D) {}

func BenchmarkInterface(b *testing.B) {
    s := D(Strings{})
    for i := 0 ; i < b.N ; i += 1 {
        s.Append(Strings{""})
    }
}

func BenchmarkConcrete(b *testing.B) {
    s := Strings{} // only difference is that I'm not casting it to the generic interface
    for i := 0 ; i < b.N ; i += 1 {
        s.Append(Strings{""})
    }
}

These are the results I'm seeing:

BenchmarkInterface-4     	20000000	        74.6 ns/op
BenchmarkConcrete-4   	2000000000	         1.67 ns/op

Does it make any sense that using the concrete type is 45x faster? The only solution I could find is to use a type-switch everywhere I run this function.

When I remove the argument from the function definition (or replace it with an int), the runtime drops significantly. There's still a big difference but one that makes more sense to me. I suspect that it might be related to mem-copies?


$ go version
go version go1.8.1 darwin/amd64
$ go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/avinoamr/go"
GORACE=""
GOROOT="/usr/local/Cellar/go/1.8.1/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.8.1/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/01/m48g6qr573b8_dvmdfr_nw1h0000gn/T/go-build557172691=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
@bradfitz
Copy link
Contributor

This is due to inlining and escape analysis, not the call overhead itself. The compiler can prove to itself more about code with concrete types.

Compare:

$ go tool compile -m concrete_test.go
concrete_test.go:11:6: can inline Strings.Append
concrete_test.go:18:11: inlining call to Strings.Append
concrete_test.go:11:27: Strings.Append s does not escape
concrete_test.go:11:27: Strings.Append d does not escape
concrete_test.go:15:27: BenchmarkConcrete b does not escape
concrete_test.go:16:14: BenchmarkConcrete Strings literal does not escape
concrete_test.go:18:19: BenchmarkConcrete Strings literal does not escape
concrete_test.go:18:19: BenchmarkConcrete Strings literal does not escape
<autogenerated>:1:0: leaking param: .anon0
<autogenerated>:1:0: leaking param: .this
<autogenerated>:1:0: inlining call to Strings.Append
<autogenerated>:1:0: (*Strings).Append .this does not escape
<autogenerated>:1:0: (*Strings).Append d does not escape
<autogenerated>:1:0: leaking param: .this
<autogenerated>:1:0: leaking param: .this

"".BenchmarkConcrete STEXT nosplit size=98 args=0x8 locals=0x30
        0x0000 00000 (concrete_test.go:15)      TEXT    "".BenchmarkConcrete(SB), NOSPLIT, $48-8
        0x0000 00000 (concrete_test.go:15)      SUBQ    $48, SP
        0x0004 00004 (concrete_test.go:15)      MOVQ    BP, 40(SP)
        0x0009 00009 (concrete_test.go:15)      LEAQ    40(SP), BP
        0x000e 00014 (concrete_test.go:15)      FUNCDATA        $0, gclocals·a36216b97439c93dafebe03e7f0808b5(SB)                                     
        0x000e 00014 (concrete_test.go:15)      FUNCDATA        $1, gclocals·24b0aee1021c20d1590e75b99691b0e0(SB)
        0x000e 00014 (concrete_test.go:17)      MOVQ    "".b+56(SP), AX
        0x0013 00019 (concrete_test.go:16)      MOVQ    $0, CX
        0x0015 00021 (concrete_test.go:17)      JMP     76
        0x0017 00023 (concrete_test.go:18)      MOVQ    "".statictmp_0(SB), DX
        0x001e 00030 (concrete_test.go:18)      MOVQ    "".statictmp_0+8(SB), BX
        0x0025 00037 (concrete_test.go:18)      MOVQ    DX, ""..autotmp_12(SP)
        0x0029 00041 (concrete_test.go:18)      MOVQ    BX, ""..autotmp_12+8(SP)
        0x002e 00046 (concrete_test.go:18)      LEAQ    ""..autotmp_12(SP), DX
        0x0032 00050 (concrete_test.go:18)      MOVQ    DX, ""..autotmp_15+16(SP)                                                                     
        0x0037 00055 (concrete_test.go:18)      MOVQ    $1, ""..autotmp_15+24(SP)
        0x0040 00064 (concrete_test.go:18)      MOVQ    $1, ""..autotmp_15+32(SP)
        0x0049 00073 (concrete_test.go:17)      INCQ    CX
        0x004c 00076 (concrete_test.go:17)      MOVQ    240(AX), DX
        0x0053 00083 (concrete_test.go:17)      CMPQ    CX, DX
        0x0056 00086 (concrete_test.go:17)      JLT     23
        0x0058 00088 (concrete_test.go:20)      MOVQ    40(SP), BP
        0x005d 00093 (concrete_test.go:20)      ADDQ    $48, SP
        0x0061 00097 (concrete_test.go:20)      RET

vs.

$ go tool compile -m interface_test.go
interface_test.go:11:6: can inline Strings.Append
interface_test.go:11:27: Strings.Append s does not escape
interface_test.go:11:27: Strings.Append d does not escape
interface_test.go:18:19: Strings literal escapes to heap
interface_test.go:18:19: Strings literal escapes to heap
interface_test.go:16:8: D(Strings literal) escapes to heap
interface_test.go:16:16: Strings literal escapes to heap
interface_test.go:15:28: BenchmarkInterface b does not escape
<autogenerated>:1:0: leaking param: .anon0
<autogenerated>:1:0: leaking param: .this
<autogenerated>:1:0: inlining call to Strings.Append
<autogenerated>:1:0: (*Strings).Append .this does not escape
<autogenerated>:1:0: (*Strings).Append d does not escape
<autogenerated>:1:0: leaking param: .this
<autogenerated>:1:0: leaking param: .this

"".BenchmarkInterface STEXT size=392 args=0x8 locals=0x80
        0x0000 00000 (interface_test.go:15)     TEXT    "".BenchmarkInterface(SB), $128-8                                                             
        0x0000 00000 (interface_test.go:15)     MOVQ    (TLS), CX
        0x0009 00009 (interface_test.go:15)     CMPQ    SP, 16(CX)
        0x000d 00013 (interface_test.go:15)     JLS     382
        0x0013 00019 (interface_test.go:15)     SUBQ    $128, SP
        0x001a 00026 (interface_test.go:15)     MOVQ    BP, 120(SP)
        0x001f 00031 (interface_test.go:15)     LEAQ    120(SP), BP
        0x0024 00036 (interface_test.go:15)     FUNCDATA        $0, gclocals·7c2b9c64f1281cf71d4e337ac2ec0e62(SB)
        0x0024 00036 (interface_test.go:15)     FUNCDATA        $1, gclocals·1c3074cffd902186c29a0dd59f96868f(SB)
        0x0024 00036 (interface_test.go:16)     LEAQ    type.[0]string(SB), AX
        0x002b 00043 (interface_test.go:16)     MOVQ    AX, (SP)
        0x002f 00047 (interface_test.go:16)     PCDATA  $0, $0
        0x002f 00047 (interface_test.go:16)     CALL    runtime.newobject(SB)
        0x0034 00052 (interface_test.go:16)     MOVQ    8(SP), AX
        0x0039 00057 (interface_test.go:16)     MOVQ    AX, ""..autotmp_3+96(SP)
        0x003e 00062 (interface_test.go:16)     MOVQ    $0, ""..autotmp_3+104(SP)
        0x0047 00071 (interface_test.go:16)     MOVQ    $0, ""..autotmp_3+112(SP)
        0x0050 00080 (interface_test.go:16)     LEAQ    go.itab."".Strings,"".D(SB), AX
        0x0057 00087 (interface_test.go:16)     MOVQ    AX, (SP)
        0x005b 00091 (interface_test.go:16)     LEAQ    ""..autotmp_3+96(SP), CX
        0x0060 00096 (interface_test.go:16)     MOVQ    CX, 8(SP)
        0x0065 00101 (interface_test.go:16)     PCDATA  $0, $1
        0x0065 00101 (interface_test.go:16)     CALL    runtime.convT2Islice(SB)                                                                      
        0x006a 00106 (interface_test.go:16)     MOVQ    16(SP), AX
        0x006f 00111 (interface_test.go:16)     MOVQ    AX, "".s.itab+48(SP)                                                                          
        0x0074 00116 (interface_test.go:16)     MOVQ    24(SP), CX
        0x0079 00121 (interface_test.go:16)     MOVQ    CX, "".s.data+56(SP)
        0x007e 00126 (interface_test.go:16)     MOVQ    $0, DX
        0x0080 00128 (interface_test.go:17)     JMP     314
        0x0085 00133 (interface_test.go:17)     MOVQ    DX, "".i+32(SP)
        0x008a 00138 (interface_test.go:18)     LEAQ    type.[1]string(SB), AX
        0x0091 00145 (interface_test.go:18)     MOVQ    AX, (SP)
        0x0095 00149 (interface_test.go:18)     PCDATA  $0, $2
        0x0095 00149 (interface_test.go:18)     CALL    runtime.newobject(SB)
        0x009a 00154 (interface_test.go:18)     MOVQ    "".statictmp_0+8(SB), AX
        0x00a1 00161 (interface_test.go:18)     MOVQ    8(SP), CX
        0x00a6 00166 (interface_test.go:18)     MOVQ    "".statictmp_0(SB), DX
        0x00ad 00173 (interface_test.go:18)     MOVQ    AX, 8(CX)
        0x00b1 00177 (interface_test.go:18)     MOVL    runtime.writeBarrier(SB), AX
        0x00b7 00183 (interface_test.go:18)     TESTL   AX, AX
        0x00b9 00185 (interface_test.go:18)     JNE     340
        0x00bf 00191 (interface_test.go:18)     MOVQ    DX, (CX)
        0x00c2 00194 (interface_test.go:18)     TESTB   AL, (CX)
        0x00c4 00196 (interface_test.go:18)     MOVQ    CX, ""..autotmp_4+72(SP)
        0x00c9 00201 (interface_test.go:18)     MOVQ    $1, ""..autotmp_4+80(SP)
        0x00d2 00210 (interface_test.go:18)     MOVQ    $1, ""..autotmp_4+88(SP)
        0x00db 00219 (interface_test.go:18)     MOVQ    "".s.itab+48(SP), AX
        0x00e0 00224 (interface_test.go:18)     MOVQ    32(AX), CX
        0x00e4 00228 (interface_test.go:18)     MOVQ    CX, ""..autotmp_12+40(SP)
        0x00e9 00233 (interface_test.go:16)     LEAQ    go.itab."".Strings,"".D(SB), DX
        0x00f0 00240 (interface_test.go:18)     MOVQ    DX, (SP)
        0x00f4 00244 (interface_test.go:18)     LEAQ    ""..autotmp_4+72(SP), BX                                                                      
        0x00f9 00249 (interface_test.go:18)     MOVQ    BX, 8(SP)
        0x00fe 00254 (interface_test.go:18)     PCDATA  $0, $3
        0x00fe 00254 (interface_test.go:18)     CALL    runtime.convT2Islice(SB)
        0x0103 00259 (interface_test.go:18)     MOVQ    16(SP), AX
        0x0108 00264 (interface_test.go:18)     MOVQ    24(SP), CX
        0x010d 00269 (interface_test.go:18)     MOVQ    AX, 8(SP)
        0x0112 00274 (interface_test.go:18)     MOVQ    CX, 16(SP)
        0x0117 00279 (interface_test.go:18)     MOVQ    "".s.data+56(SP), AX
        0x011c 00284 (interface_test.go:18)     MOVQ    AX, (SP)
        0x0120 00288 (interface_test.go:18)     MOVQ    ""..autotmp_12+40(SP), CX
        0x0125 00293 (interface_test.go:18)     PCDATA  $0, $3
        0x0125 00293 (interface_test.go:18)     CALL    CX
        0x0127 00295 (interface_test.go:17)     MOVQ    "".i+32(SP), AX
        0x012c 00300 (interface_test.go:17)     LEAQ    1(AX), DX
        0x0130 00304 (interface_test.go:18)     MOVQ    "".s.itab+48(SP), AX
        0x0135 00309 (interface_test.go:18)     MOVQ    "".s.data+56(SP), CX
        0x013a 00314 (interface_test.go:17)     MOVQ    "".b+136(SP), BX
        0x0142 00322 (interface_test.go:17)     MOVQ    240(BX), SI
        0x0149 00329 (interface_test.go:17)     CMPQ    DX, SI
        0x014c 00332 (interface_test.go:17)     JLT     133
        0x0152 00338 (interface_test.go:17)     JMP     369
        0x0154 00340 (interface_test.go:18)     MOVQ    CX, ""..autotmp_13+64(SP)
        0x0159 00345 (interface_test.go:18)     MOVQ    CX, (SP)
        0x015d 00349 (interface_test.go:18)     MOVQ    DX, 8(SP)
        0x0162 00354 (interface_test.go:18)     PCDATA  $0, $4
        0x0162 00354 (interface_test.go:18)     CALL    runtime.writebarrierptr(SB)
        0x0167 00359 (interface_test.go:18)     MOVQ    ""..autotmp_13+64(SP), CX
        0x016c 00364 (interface_test.go:18)     JMP     194
        0x0171 00369 (interface_test.go:20)     MOVQ    120(SP), BP
        0x0176 00374 (interface_test.go:20)     ADDQ    $128, SP
        0x017d 00381 (interface_test.go:20)     RET
        0x017e 00382 (interface_test.go:20)     NOP
        0x017e 00382 (interface_test.go:15)     PCDATA  $0, $-1
        0x017e 00382 (interface_test.go:15)     CALL    runtime.morestack_noctxt(SB)
        0x0183 00387 (interface_test.go:15)     JMP     0

Closing as a dup of #19361 #18822 #19165 and other such bugs.

@golang golang locked and limited conversation to collaborators Apr 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants