Anmerkung im Jahr 2018 hinzugefügt
Ab Go 1.10 gibt es einen strings.Builder
Typ, bitte schauen Sie sich diese Antwort für weitere Details an .
Antwort vor 201x
Der Benchmark-Code von @ cd1 und andere Antworten sind falsch. b.N
soll nicht in der Benchmark-Funktion gesetzt werden. Es wird vom Go-Test-Tool dynamisch festgelegt, um festzustellen, ob die Ausführungszeit des Tests stabil ist.
Eine Benchmark-Funktion sollte dieselben Testzeiten ausführen b.N
und der Test innerhalb der Schleife sollte für jede Iteration gleich sein. Also behebe ich es, indem ich eine innere Schleife hinzufüge. Ich füge auch Benchmarks für einige andere Lösungen hinzu:
package main
import (
"bytes"
"strings"
"testing"
)
const (
sss = "xfoasneobfasieongasbg"
cnt = 10000
)
var (
bbb = []byte(sss)
expected = strings.Repeat(sss, cnt)
)
func BenchmarkCopyPreAllocate(b *testing.B) {
var result string
for n := 0; n < b.N; n++ {
bs := make([]byte, cnt*len(sss))
bl := 0
for i := 0; i < cnt; i++ {
bl += copy(bs[bl:], sss)
}
result = string(bs)
}
b.StopTimer()
if result != expected {
b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
}
}
func BenchmarkAppendPreAllocate(b *testing.B) {
var result string
for n := 0; n < b.N; n++ {
data := make([]byte, 0, cnt*len(sss))
for i := 0; i < cnt; i++ {
data = append(data, sss...)
}
result = string(data)
}
b.StopTimer()
if result != expected {
b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
}
}
func BenchmarkBufferPreAllocate(b *testing.B) {
var result string
for n := 0; n < b.N; n++ {
buf := bytes.NewBuffer(make([]byte, 0, cnt*len(sss)))
for i := 0; i < cnt; i++ {
buf.WriteString(sss)
}
result = buf.String()
}
b.StopTimer()
if result != expected {
b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
}
}
func BenchmarkCopy(b *testing.B) {
var result string
for n := 0; n < b.N; n++ {
data := make([]byte, 0, 64) // same size as bootstrap array of bytes.Buffer
for i := 0; i < cnt; i++ {
off := len(data)
if off+len(sss) > cap(data) {
temp := make([]byte, 2*cap(data)+len(sss))
copy(temp, data)
data = temp
}
data = data[0 : off+len(sss)]
copy(data[off:], sss)
}
result = string(data)
}
b.StopTimer()
if result != expected {
b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
}
}
func BenchmarkAppend(b *testing.B) {
var result string
for n := 0; n < b.N; n++ {
data := make([]byte, 0, 64)
for i := 0; i < cnt; i++ {
data = append(data, sss...)
}
result = string(data)
}
b.StopTimer()
if result != expected {
b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
}
}
func BenchmarkBufferWrite(b *testing.B) {
var result string
for n := 0; n < b.N; n++ {
var buf bytes.Buffer
for i := 0; i < cnt; i++ {
buf.Write(bbb)
}
result = buf.String()
}
b.StopTimer()
if result != expected {
b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
}
}
func BenchmarkBufferWriteString(b *testing.B) {
var result string
for n := 0; n < b.N; n++ {
var buf bytes.Buffer
for i := 0; i < cnt; i++ {
buf.WriteString(sss)
}
result = buf.String()
}
b.StopTimer()
if result != expected {
b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
}
}
func BenchmarkConcat(b *testing.B) {
var result string
for n := 0; n < b.N; n++ {
var str string
for i := 0; i < cnt; i++ {
str += sss
}
result = str
}
b.StopTimer()
if result != expected {
b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
}
}
Umgebung ist OS X 10.11.6, 2,2 GHz Intel Core i7
Testergebnisse:
BenchmarkCopyPreAllocate-8 20000 84208 ns/op 425984 B/op 2 allocs/op
BenchmarkAppendPreAllocate-8 10000 102859 ns/op 425984 B/op 2 allocs/op
BenchmarkBufferPreAllocate-8 10000 166407 ns/op 426096 B/op 3 allocs/op
BenchmarkCopy-8 10000 160923 ns/op 933152 B/op 13 allocs/op
BenchmarkAppend-8 10000 175508 ns/op 1332096 B/op 24 allocs/op
BenchmarkBufferWrite-8 10000 239886 ns/op 933266 B/op 14 allocs/op
BenchmarkBufferWriteString-8 10000 236432 ns/op 933266 B/op 14 allocs/op
BenchmarkConcat-8 10 105603419 ns/op 1086685168 B/op 10000 allocs/op
Fazit:
CopyPreAllocate
ist der schnellste Weg; AppendPreAllocate
ist ziemlich nah an Nr. 1, aber es ist einfacher, den Code zu schreiben.
Concat
hat eine wirklich schlechte Leistung sowohl für die Geschwindigkeit als auch für die Speichernutzung. Benutze es nicht.
Buffer#Write
und Buffer#WriteString
sind im Grunde gleich schnell, im Gegensatz zu dem, was @ Dani-Br im Kommentar gesagt hat. Wenn string
man bedenkt, dass es tatsächlich []byte
in Go ist, macht es Sinn.
- bytes.Buffer verwenden grundsätzlich die gleiche Lösung wie
Copy
bei zusätzlicher Buchhaltung und anderen Dingen.
Copy
und Append
verwenden Sie eine Bootstrap-Größe von 64, die mit bytes.Buffer identisch ist
Append
Verwenden Sie mehr Speicher und Zuweisungen. Ich denke, dies hängt mit dem verwendeten Wachstumsalgorithmus zusammen. Der Speicher wächst nicht so schnell wie Bytes
Vorschlag:
- Für einfache Aufgaben wie das, was OP will, würde ich
Append
oder verwenden AppendPreAllocate
. Es ist schnell genug und einfach zu bedienen.
- Wenn Sie den Puffer gleichzeitig lesen und schreiben müssen, verwenden Sie
bytes.Buffer
natürlich. Dafür ist es konzipiert.