2015-12-01 10 views
7

In meinem Code habe ich eine solche Benchmark:Warum strings.HasPrefix ist schneller als bytes.HasPrefix?

const STR = "abcd" 
const PREFIX = "ab" 
var STR_B = []byte(STR) 
var PREFIX_B = []byte(PREFIX) 

func BenchmarkStrHasPrefix(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     strings.HasPrefix(STR, PREFIX) 
    } 
} 

func BenchmarkBytHasPrefix(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     bytes.HasPrefix(STR_B, PREFIX_B) 
    } 
} 

Und ich bin wenig verwirrt über die Ergebnisse:

BenchmarkStrHasPrefix-4 300000000 4.67 ns/op 
BenchmarkBytHasPrefix-4 200000000 8.05 ns/op 

Warum dort ist Unterschied zu 2x?

Danke.

+1

Was sind die Werte von 'STR',' STR_B', 'PREFIX',' PREFIX_B'? –

+0

@IsmailBadawi aktualisiert Beispiel) – gobwas

+0

beachten Sie, dass auf meinem Core2Duo P8600 beide Benchmarks die gleiche Leistung (~ 20 ns/op), während auf i5-2400S sind sie ähnlich zu Ihnen - 5,5 vs 8.5 ns/op – tomasz

Antwort

13

Der Hauptgrund ist der Unterschied in den Anrufkosten von bytes.HasPrefix() und strings.HasPrefix(). Wie @tomasz in seinem Kommentar darauf hingewiesen hat, ist strings.HashPrefix() standardmäßig inline, bytes.HasPrefix() dagegen nicht.

Weitere Gründe sind die unterschiedlichen Parametertypen: bytes.HasPrefix() benötigt 2 Slices (2 Slice Deskriptoren). dauert 2 Zeichenfolgen (2 Zeichenfolgenheader). Slice-Deskriptoren enthalten einen Zeiger und 2 int s: Länge und Kapazität, siehe reflect.SliceHeader. String-Header enthalten nur einen Zeiger und eine int: die Länge, siehe reflect.StringHeader.

Wir können dies beweisen, wenn wir manuell die HasPrefix() Funktionen in den Benchmark-Funktionen inline, so dass wir die Anrufkosten (Null beide) beseitigen. Durch Inlinen wird kein Funktionsaufruf (an sie) gemacht.

HasPrefix() Implementierungen:

// HasPrefix tests whether the byte slice s begins with prefix. 
func HasPrefix(s, prefix []byte) bool { 
    return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix) 
} 

// HasPrefix tests whether the string s begins with prefix. 
func HasPrefix(s, prefix string) bool { 
    return len(s) >= len(prefix) && s[0:len(prefix)] == prefix 
} 

Benchmark-Funktionen nach ihnen inlining:

func BenchmarkStrHasPrefix(b *testing.B) { 
    s, prefix := STR, PREFIX 
    for i := 0; i < b.N; i++ { 
     _ = len(s) >= len(prefix) && s[0:len(prefix)] == prefix 
    } 
} 

func BenchmarkBytHasPrefix(b *testing.B) { 
    s, prefix := STR_B, PREFIX_B 
    for i := 0; i < b.N; i++ { 
     _ = len(s) >= len(prefix) && bytes.Equal(s[0:len(prefix)], prefix) 
    } 
} 

diese Laufen Sie ganz in der Nähe Ergebnisse erhalten:

BenchmarkStrHasPrefix-2 300000000    5.88 ns/op 
BenchmarkBytHasPrefix-2 200000000    6.17 ns/op 

Der Grund für den kleinen Unterschied in der Inline-Benchmarks können sein, dass beide Funktionen die Prese testen nce des Präfixes durch Schneiden des Operanden string und []byte. Und da strings vergleichbar sind, während Byte-Slices nicht vergleichbar sind, erfordert BenchmarkBytHasPrefix() einen zusätzlichen Funktionsaufruf an bytes.Equal() im Vergleich zu BenchmarkStrHasPrefix() (und der zusätzliche Funktionsaufruf umfasst auch das Erstellen von Kopien seiner Parameter: 2 Slice-Header).

Andere Dinge, die leicht zu den ursprünglichen unterschiedlichen Ergebnissen beitragen können: Die in BenchmarkStrHasPrefix() verwendeten Argumente sind Konstanten, während die in BenchmarkBytHasPrefix() verwendeten Parameter Variablen sind.

Sie sollten sich nicht viel Gedanken über den Leistungsunterschied machen, beide Funktionen sind in wenigen Nanosekunden erledigt.

Beachten Sie, dass die "Umsetzung" von bytes.Equal():

func Equal(a, b []byte) bool // ../runtime/asm_$GOARCH.s 

Dies kann ohne zusätzliche Gesprächskosten führt in einigen Plattformen inlined werden.

+0

Das ist nicht genug für eine Erklärung: Wenn Sie einen zusätzlichen nicht-inlining Funktionsaufruf einfügen, ist es immer noch langsamer. Leider verstehe ich nicht, was die Equal-Funktion in Assembly tatsächlich tut. Kann jemand das erklären? – 0x434D53

+1

@ 0x434D53 +1, zum Beispiel in asm_amd64.s (Zeile 1312 (Laufzeit · eqstring) und Zeile 1652 (Bytes · Equal)) - Was ist der Unterschied in diesem Code? – gobwas

+0

@icza danke, aber könnten Sie expliziter erklären? =) – gobwas

Verwandte Themen