Notiz hinzugefügt im Jahr 2018
Ab Go 1.10 gibt es einen strings.Builder
-Typ, bitte schauen Sie sich diese Antwort für weitere Details an.
Vor-201x-Antwort
Der Benchmark-Code von @cd1 und anderen Antworten ist falsch. b.N
sollte nicht im Benchmark-Code gesetzt werden. Es wird vom go test-Tool dynamisch gesetzt, um zu bestimmen, ob die Ausführungszeit des Tests stabil ist.
Ein Benchmark-Code sollte den gleichen Test b.N
-mal ausführen und der Test innerhalb der Schleife sollte für jede Iteration gleich sein. Also habe ich es durch das Hinzufügen einer inneren Schleife behoben. Ich habe auch Benchmarks für einige andere Lösungen hinzugefügt:
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)
}
}
...
Die 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
Schlussfolgerung:
CopyPreAllocate
ist der schnellste Weg; AppendPreAllocate
liegt ziemlich nah an Platz 1, aber es ist einfacher, den Code zu schreiben.
Concat
hat sowohl hinsichtlich Geschwindigkeit als auch Speicherverbrauch eine wirklich schlechte Leistung. Verwenden Sie es nicht.
Buffer#Write
und Buffer#WriteString
sind im Grunde in der Geschwindigkeit gleich, im Gegensatz zu dem, was @Dani-Br im Kommentar gesagt hat. Angesichts dessen, dass string
in der Tat []byte
in Go ist, macht es Sinn.
- bytes.Buffer verwendet im Wesentlichen die gleiche Lösung wie
Copy
mit zusätzlicher Buchführung und anderen Dingen.
Copy
und Append
verwenden eine Bootstrap-Größe von 64, die gleiche wie bei bytes.Buffer
Append
verwendet mehr Speicher und Allokationen, ich denke, es hängt mit dem Wachstumsalgorithmus zusammen, den es verwendet. Es wächst nicht so schnell wie bytes.Buffer
Vorschlag:
- Für einfache Aufgaben wie das, was OP möchte, würde ich
Append
oder AppendPreAllocate
verwenden. Es ist schnell genug und einfach zu verwenden.
- Wenn der Puffer gleichzeitig gelesen und geschrieben werden muss, verwenden Sie natürlich
bytes.Buffer
. Dafür ist es ausgelegt.
12 Stimmen
Noch eine Bank
1 Stimmen
Hinweis: Diese Frage und die meisten Antworten scheinen vor dem Hinzufügen von
append()
in die Sprache geschrieben worden zu sein, was eine gute Lösung dafür ist. Es wird genauso schnell wiecopy()
ausgeführt, aber das Slice wird zuerst erweitert, auch wenn dies bedeutet, dass ein neues zugrundeliegendes Array allokiert wird, wenn die Kapazität nicht ausreicht.bytes.Buffer
macht immer noch Sinn, wenn Sie seine zusätzlichen Komfortmethoden möchten oder wenn das Paket, das Sie verwenden, dies erwartet.10 Stimmen
Es ist nicht nur "sehr ineffizient"; es hat ein spezifisches Problem, mit dem jeder neue Nicht-CS-Mitarbeiter, den wir jemals eingestellt haben, in den ersten Wochen der Arbeit konfrontiert wird. Es ist quadratisch - O(n*n). Denken Sie an die Zahlenfolge:
1 + 2 + 3 + 4 + ...
. Es istn*(n+1)/2
, die Fläche eines Dreiecks mit der Basisn
. Sie reservieren die Größe 1, dann die Größe 2, dann die Größe 3 usw., wenn Sie unveränderliche Strings in einer Schleife anhängen. Diese quadratische Ressourcenverwendung äußert sich auf mehrere Weisen als nur diese.