Примітка додана у 2018 році
Від Go 1.10 є strings.Builder
тип, будь ласка, погляньте на цю відповідь для більш детальної інформації .
Відповідь до 201x
Код еталону @ cd1 та інші відповіді неправильні. b.N
не повинен бути встановлений у функції орієнтиру. Він встановлюється динамічним інструментом для тестування, щоб визначити, чи стабільний час виконання тесту.
Функція орієнтиру повинна запускати однакові b.N
рази, а тест всередині циклу повинен бути однаковим для кожної ітерації. Тому я це виправляю, додаючи внутрішню петлю. Я також додаю орієнтири для деяких інших рішень:
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)
}
}
Навколишнім середовищем є OS X 10.11.6, 2.2 GHz Intel Core i7
Результати тесту:
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
Висновок:
CopyPreAllocate
це найшвидший спосіб; AppendPreAllocate
досить близько до №1, але простіше написати код.
Concat
має дуже погані показники як для швидкості, так і для використання пам'яті. Не використовуйте його.
Buffer#Write
і Buffer#WriteString
в основному однакові за швидкістю, всупереч тому, що @ Dani-Br сказав у коментарі. Зважаючи на те, що string
це справді []byte
в роботі Go, це має сенс.
- bytes.Buffer в основному використовують те саме рішення, що
Copy
і для додаткового ведення книг та інших речей.
Copy
і Append
використовувати завантажувальний розмір 64, такий самий, як і байти.Buffer
Append
використовуйте більше пам'яті та алоків, я думаю, що це пов'язано з алгоритмом росту, який він використовує. Ця пам’ять не росте так швидко, як байти
Пропозиція:
- Для простих завдань, таких як те, що хоче ОП, я б використав
Append
або AppendPreAllocate
. Це досить швидко і просто у використанні.
- Якщо вам потрібно читати і писати буфер одночасно, використовуйте
bytes.Buffer
звичайно. Ось для чого він розрахований.