Uwaga dodana w 2018 r
Od wersji 1.10 istnieje strings.Builder
typ, spójrz na tę odpowiedź, aby uzyskać więcej szczegółów .
Odpowiedź sprzed 201x
Kod testu porównawczego @ cd1 i inne odpowiedzi są nieprawidłowe. b.N
nie powinien być ustawiony w funkcji testu porównawczego. Jest on ustawiany dynamicznie przez narzędzie testowe w celu ustalenia, czy czas wykonania testu jest stabilny.
Funkcja testu porównawczego powinna uruchamiać te same b.N
czasy testu, a test wewnątrz pętli powinien być taki sam dla każdej iteracji. Naprawiam to, dodając wewnętrzną pętlę. Dodam również testy porównawcze dla niektórych innych rozwiązań:
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)
}
}
Środowisko to OS X 10.11.6, 2,2 GHz Intel Core i7
Wyniki testów:
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
Wniosek:
CopyPreAllocate
jest najszybszym sposobem; AppendPreAllocate
jest dość blisko numeru 1, ale łatwiej jest napisać kod.
Concat
ma naprawdę niską wydajność zarówno pod względem szybkości, jak i zużycia pamięci. Nie używaj tego.
Buffer#Write
i Buffer#WriteString
są w zasadzie takie same pod względem prędkości, w przeciwieństwie do tego, co powiedział @ Dani-Br w komentarzu. Rozważanie string
jest rzeczywiście[]byte
w Go, ma to sens.
- bajty.Bufor zasadniczo używa tego samego rozwiązania, co w
Copy
przypadku dodatkowej księgowości i innych rzeczy.
Copy
i Append
użyj rozmiaru bootstrapu 64, takiego samego jak bytes.Buffer
Append
zużywają więcej pamięci i alokacji, myślę, że jest to związane z algorytmem wzrostu, którego używa. Nie rośnie pamięć tak szybko jak bajty. Bufor
Sugestia:
- Do prostych zadań, takich jak to, czego chce OP, użyłbym
Append
lub AppendPreAllocate
. Jest wystarczająco szybki i łatwy w użyciu.
- Jeśli musisz jednocześnie czytać i zapisywać bufor, skorzystaj
bytes.Buffer
oczywiście. Do tego jest przeznaczony.