Anmerkung im Jahr 2018 hinzugefügt
Ab Go 1.10 gibt es einen strings.BuilderTyp, 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.Nsoll 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.Nund 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:
CopyPreAllocateist der schnellste Weg; AppendPreAllocateist ziemlich nah an Nr. 1, aber es ist einfacher, den Code zu schreiben.
Concathat eine wirklich schlechte Leistung sowohl für die Geschwindigkeit als auch für die Speichernutzung. Benutze es nicht.
Buffer#Writeund Buffer#WriteStringsind im Grunde gleich schnell, im Gegensatz zu dem, was @ Dani-Br im Kommentar gesagt hat. Wenn stringman bedenkt, dass es tatsächlich []bytein Go ist, macht es Sinn.
- bytes.Buffer verwenden grundsätzlich die gleiche Lösung wie
Copybei zusätzlicher Buchhaltung und anderen Dingen.
Copyund Appendverwenden Sie eine Bootstrap-Größe von 64, die mit bytes.Buffer identisch ist
AppendVerwenden 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
Appendoder verwenden AppendPreAllocate. Es ist schnell genug und einfach zu bedienen.
- Wenn Sie den Puffer gleichzeitig lesen und schreiben müssen, verwenden Sie
bytes.Buffernatürlich. Dafür ist es konzipiert.