I ran into a case where I needed to prepend 10,000 strings. This is a bit different then the fastest way to prepend 10 strings but hasn't been answered here yet.
TL;DR Store the 10,000 strings in []string and then use strings.StringBuilder to assemble it in reverse order.
var strs []string
for i := 0; i < 10000; i++ {
strs = append(strs, "Put your string here")
}
var b strings.Builder
for i := len(strs) - 1; i >= 0; i -= 1 {
b.WriteString(strs[j])
}
A naive approach with + used 10 gigabytes of memory + 1 second. Putting a decent amount of time into custom prepend code that fills up a buffer from the back reduced that same test case to 10mb + 2 milliseconds. But it turns out just storing every string and then using string builder achieves almost the same result with less code. (14mb 2.4 ms).
Here's the benchmark results:
go test -benchtime=50x -benchmem -bench .
BenchmarkPrepend10kAddition-20 50 971398162 ns/op 9666950277 B/op 20325 allocs/op
BenchmarkPrepend10kPrepender-20 50 1924394 ns/op 10165544 B/op 29913 allocs/op
BenchmarkPrepend10kReorderThenStringBuilder-20 50 2436740 ns/op 14518100 B/op 19952 allocs/op
Here's the code (without the prepender class because that needs a bit more work to be public ready):
package main
import (
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
var teststrings []string = []string{
"Test test tses test test sets etsetsetsetsettesestsetet\n",
"TEST TEST TEST TEST TEST EST SET ET SET EST EST SET E TSE TES T STE ET SETT\n",
"ashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n", "asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
"asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
"asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
"asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
"asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
"asdhakjsdhaksjdhdskajhsdajkshdkajshdkjasdkjdhasfhjadshfashdasgfkjashdgfjhsadgfjashgdfkjhasgdfkjhgasdfkjghasdfkjhgasdkjfhgasdkjghfasdkjghfasdhjfgasjkghfsadhjgfaashkajshfksjhdfksjdhfklhjasdfkljhasdfljashdfkljahsdfkjshafdkjashdflkjhsdgkljhsdfklgjhsklfjghksdfjgkldsjfghldksjhfgkdsfjghkdsjhfgkldjsghkldsjfghdklsfgasdjhaksdhjaksdjhasdkjhfksdjhfkjashfdjldhjasfkljashdfkljsahdflkjashdfldhjasfkjsdhf\n",
}
func BenchmarkPrepend10kAddition(b *testing.B) {
for i := 0; i < b.N; i++ {
str := ""
for j := 0; j < 10000; j++ {
str = (strconv.Itoa(j) + teststrings[j%4]) + str
}
_ = string(str)
}
}
func BenchmarkPrepend10kPrepender(b *testing.B) {
for i := 0; i < b.N; i++ {
// Example using custom prepender code not included in this post
prepender := NewStringPrepender()
for j := 0; j < 10000; j++ {
prepender.Prepend(strconv.Itoa(j) + teststrings[j%4])
}
_ = string(prepender.String())
}
}
func BenchmarkPrepend10kReorderThenStringBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
// What if we have 10,000 strings and assemble them at the end?
var strs []string
for j := 0; j < 10000; j++ {
strs = append(strs, strconv.Itoa(j)+teststrings[j%4])
}
var b strings.Builder
for j := len(strs) - 1; j >= 0; j -= 1 {
b.WriteString(strs[j])
}
_ = string(b.String())
}
}
To generate the strings in the benchmark tests I'm using Itoa + a list of test strings. This might be unnecessary but I added them to make sure values in []string do not point to the same memory location. The randomish test strings vary the length of the strings concatenated.