2015-10-25 4 views
10

Warum sind diese Benchmark-Ergebnisse so unterschiedlich?Golang: Mach die Schnittleistung

func Benchmark1(b *testing.B) { 
    for n := 0; n < b.N; n++ { 
     _ = make([]byte, 8) 
    } 
} 

func Benchmark2(b *testing.B) { 
    length := 1 
    for n := 0; n < b.N; n++ { 
     _ = make([]byte, 7+length) 
    } 
} 

Benchmark-Ergebnisse:

Benchmark1-8     500000000   3.37 ns/op 
Benchmark2-8     30000000   50.6 ns/op 
+0

Dies beantwortet nicht Ihre Frage, sondern zeichnet ein vollständigeres Bild: http://play.golang.org/p/WkvIZ5I1b1. Laufen auf dem Spielplatz gibt alle 0,00 ns/op aus irgendeinem Grund. Wenn auf meiner Maschine die Länge ein Literal 8, eine Konstante 8, eine Summe aus zwei Literalen ist, die zu 8 addieren, oder eine Summe aus einer Konstanten und einem Literal, das zu 8 addiert, sind sie ungefähr gleich, etwa 1.45 ns/op . Wenn die Länge eine Variable 8 oder eine Variable plus ein Literal ist, das zu 8 summiert, sind sie etwa gleich lang, viel langsamer als die vorherige Gruppe, etwa 91 ns/op. Um es auszuführen, fügen Sie den Inhalt in eine Datei ein und führen Sie einfach "PATH_TO_FILE" aus. –

Antwort

8

Der konstante Ausdruck 8 wird bei der Kompilierung ausgewertet. Die make ist auf einem Goroutine-Stack (billig) zugeordnet. Der Variablenausdruck 7 + length wird zur Laufzeit ausgewertet. Die make ist auf dem Programm-Heap (teuer) zugeordnet. Wenn die Größe make für eine Stapelzuweisung zu groß ist (z. B. Konstante (64*1024) und Variable (64*1024-1)+length), werden beide Zuordnungen auf dem Heap durchgeführt und die Benchmarkzeiten sind gleich.

$ go tool compile -m a_test.go 
a_test.go:5: Benchmark1 b does not escape 
a_test.go:7: Benchmark1 make([]byte, 8) does not escape 
a_test.go:14: make([]byte, 7 + length) escapes to heap 
a_test.go:11: Benchmark2 b does not escape 
$ 

a_test.go:

package a 

import "testing" 

func Benchmark1(b *testing.B) { 
    for n := 0; n < b.N; n++ { 
     _ = make([]byte, 8) 
    } 
} 

func Benchmark2(b *testing.B) { 
    length := 1 
    for n := 0; n < b.N; n++ { 
     _ = make([]byte, 7+length) 
    } 
} 

Go pseudo-Assembler:

$ go tool compile -S a_test.go 

Benchmark1:

"".Benchmark1 t=1 size=112 value=0 args=0x8 locals=0x20 
    0x0000 00000 (a_test.go:5) TEXT "".Benchmark1(SB), $32-8 
    0x0000 00000 (a_test.go:5) SUBQ $32, SP 
    0x0004 00004 (a_test.go:5) MOVQ "".b+40(FP), CX 
    0x0009 00009 (a_test.go:5) FUNCDATA $0, gclocals·87d20ce1b58390b294df80b886db78bf(SB) 
    0x0009 00009 (a_test.go:5) FUNCDATA $1, gclocals·790e5cc5051fc0affc980ade09e929ec(SB) 
    0x0009 00009 (a_test.go:6) MOVQ $0, AX 
    0x000b 00011 (a_test.go:6) NOP 
    0x000b 00011 (a_test.go:6) MOVQ 112(CX), BX 
    0x000f 00015 (a_test.go:6) CMPQ BX, AX 
    0x0012 00018 (a_test.go:6) JLE $0, 98 
    0x0014 00020 (a_test.go:7) MOVQ $0, BX 
    0x0016 00022 (a_test.go:7) MOVB BL, "".autotmp_0001(SP) 
    0x0019 00025 (a_test.go:7) MOVB BL, "".autotmp_0001+1(SP) 
    0x001d 00029 (a_test.go:7) MOVB BL, "".autotmp_0001+2(SP) 
    0x0021 00033 (a_test.go:7) MOVB BL, "".autotmp_0001+3(SP) 
    0x0025 00037 (a_test.go:7) MOVB BL, "".autotmp_0001+4(SP) 
    0x0029 00041 (a_test.go:7) MOVB BL, "".autotmp_0001+5(SP) 
    0x002d 00045 (a_test.go:7) MOVB BL, "".autotmp_0001+6(SP) 
    0x0031 00049 (a_test.go:7) MOVB BL, "".autotmp_0001+7(SP) 
    0x0035 00053 (a_test.go:7) LEAQ "".autotmp_0001(SP), BX 
    0x0039 00057 (a_test.go:7) CMPQ BX, $0 
    0x003d 00061 (a_test.go:7) JEQ $1, 103 
    0x003f 00063 (a_test.go:7) MOVQ $8, "".autotmp_0002+16(SP) 
    0x0048 00072 (a_test.go:7) MOVQ $8, "".autotmp_0002+24(SP) 
    0x0051 00081 (a_test.go:7) MOVQ BX, "".autotmp_0002+8(SP) 
    0x0056 00086 (a_test.go:6) INCQ AX 
    0x0059 00089 (a_test.go:6) NOP 
    0x0059 00089 (a_test.go:6) MOVQ 112(CX), BX 
    0x005d 00093 (a_test.go:6) CMPQ BX, AX 
    0x0060 00096 (a_test.go:6) JGT $0, 20 
    0x0062 00098 (a_test.go:9) ADDQ $32, SP 
    0x0066 00102 (a_test.go:9) RET 
    0x0067 00103 (a_test.go:7) MOVL AX, (BX) 
    0x0069 00105 (a_test.go:7) JMP 63 

Benchmark2:

"".Benchmark2 t=1 size=144 value=0 args=0x8 locals=0x58 
    0x0000 00000 (a_test.go:11) TEXT "".Benchmark2(SB), $88-8 
    0x0000 00000 (a_test.go:11) MOVQ (TLS), CX 
    0x0009 00009 (a_test.go:11) CMPQ SP, 16(CX) 
    0x000d 00013 (a_test.go:11) JLS 129 
    0x000f 00015 (a_test.go:11) SUBQ $88, SP 
    0x0013 00019 (a_test.go:11) FUNCDATA $0, gclocals·87d20ce1b58390b294df80b886db78bf(SB) 
    0x0013 00019 (a_test.go:11) FUNCDATA $1, gclocals·790e5cc5051fc0affc980ade09e929ec(SB) 
    0x0013 00019 (a_test.go:12) MOVQ $1, "".length+56(SP) 
    0x001c 00028 (a_test.go:13) MOVQ $0, AX 
    0x001e 00030 (a_test.go:13) MOVQ "".b+96(FP), BP 
    0x0023 00035 (a_test.go:13) NOP 
    0x0023 00035 (a_test.go:13) MOVQ 112(BP), BX 
    0x0027 00039 (a_test.go:13) MOVQ AX, "".n+48(SP) 
    0x002c 00044 (a_test.go:13) CMPQ BX, AX 
    0x002f 00047 (a_test.go:13) JLE $0, 124 
    0x0031 00049 (a_test.go:14) MOVQ "".length+56(SP), AX 
    0x0036 00054 (a_test.go:14) ADDQ $7, AX 
    0x003a 00058 (a_test.go:14) LEAQ type.[]uint8(SB), BX 
    0x0041 00065 (a_test.go:14) MOVQ BX, (SP) 
    0x0045 00069 (a_test.go:14) MOVQ AX, 8(SP) 
    0x004a 00074 (a_test.go:14) MOVQ AX, 16(SP) 
    0x004f 00079 (a_test.go:14) PCDATA $0, $0 
    0x004f 00079 (a_test.go:14) CALL runtime.makeslice(SB) 
    0x0054 00084 (a_test.go:14) MOVQ 24(SP), BX 
    0x0059 00089 (a_test.go:14) MOVQ BX, "".autotmp_0005+64(SP) 
    0x005e 00094 (a_test.go:14) MOVQ 32(SP), BX 
    0x0063 00099 (a_test.go:14) MOVQ BX, "".autotmp_0005+72(SP) 
    0x0068 00104 (a_test.go:14) MOVQ 40(SP), BX 
    0x006d 00109 (a_test.go:14) MOVQ BX, "".autotmp_0005+80(SP) 
    0x0072 00114 (a_test.go:13) MOVQ "".n+48(SP), AX 
    0x0077 00119 (a_test.go:13) INCQ AX 
    0x007a 00122 (a_test.go:13) NOP 
    0x007a 00122 (a_test.go:13) JMP 30 
    0x007c 00124 (a_test.go:16) ADDQ $88, SP 
    0x0080 00128 (a_test.go:16) RET 
    0x0081 00129 (a_test.go:11) CALL runtime.morestack_noctxt(SB) 
    0x0086 00134 (a_test.go:11) JMP 0 
+1

Wissen Sie, warum der Compiler im zweiten Fall keine konstante Propagation auf '7 + length 'durchführt und denselben Code erzeugt? Ist das etwas, was Go (momentan) nicht tut, oder ist es etwas, das nicht getan werden kann? – matt

+2

@matt: Da der Wert von '7 + length 'ein Laufzeitvariablenausdruck ist, muss der Compiler eine Datenflussanalyse durchführen, um zu beweisen, dass der einzige Wert für' make ([], 7 + length ') 8 ist. Wenn wir 'const length = 1 'schreiben, ist es einfach.Wenn wir schreiben, wie es hier der Fall ist,' var length = 1' ist es schwieriger und zeitraubender.Der erste Schritt ist ein SSA-Compiler: [Statisches Einzelzuweisungsformular] (https : //en.wikipedia.org/wiki/Static_single_assignment_form) Nun, da der 'gc' Compiler in Go geschrieben ist, ist ein SSA' gc' Compiler für Go in Entwicklung: [dev.ssa] (https: // go. googlesource.com/go/+/dev.ssa). – peterSO