Your solution uses too many allocations due to string concatenations.
We will create alternative, faster and / or more elegant solutions. Note that the solutions below do not check if the node values ββcontain the quotation mark. " If that were the case, they would have to be somehow avoided (otherwise the result would be an invalid query string).
Full, executable code can be found on the Go Playground . The full test / benchmarking code can also be found on the Go Playground , but it will not run, save both in the Go workspace (for example, $GOPATH/src/query/query.go and $GOPATH/src/query/query_test.go ) and run it with go test -bench . .
Also be sure to check out this related question: How to efficiently concatenate strings in Go?
Alternatives
Genesis
Your logic can be written using the following function:
func buildOriginal(nodes []string) string { var query string for _, n := range nodes { query += fmt.Sprintf("\"node_name\":\"%s\",", n) } query = strings.TrimRight(query, ",") return fmt.Sprintf("where={%s}", query) }
Using bytes.Buffer
It would be much better to use one buffer, for example. bytes.Buffer , build a query in this and convert it to string at the end:
func buildBuffer(nodes []string) string { buf := &bytes.Buffer{} buf.WriteString("where={") for i, v := range nodes { if i > 0 { buf.WriteByte(',') } buf.WriteString(`"node_name":"`) buf.WriteString(v) buf.WriteByte('"') } buf.WriteByte('}') return buf.String() }
Using it:
nodes := []string{"node1", "node2"} fmt.Println(buildBuffer(nodes))
Output:
where={"node_name":"node1","node_name":"node2"}
bytes.Buffer improved
bytes.Buffer will still perform some redistributions, although much less than your original solution.
However, we can still reduce the selection to 1 if, when creating bytes.Buffer we pass a sufficiently large byte fragment using bytes.NewBuffer() . We can calculate the required size up to:
func buildBuffer2(nodes []string) string { size := 8 + len(nodes)*15 for _, v := range nodes { size += len(v) } buf := bytes.NewBuffer(make([]byte, 0, size)) buf.WriteString("where={") for i, v := range nodes { if i > 0 { buf.WriteByte(',') } buf.WriteString(`"node_name":"`) buf.WriteString(v) buf.WriteByte('"') } buf.WriteByte('}') return buf.String() }
Notice that in size calculating 8 is the size of the where={} , and 15 is the size of the string "node_name":"", .
Using text/template
We can also create a text template and use text/template to execute it, effectively generating the result:
var t = template.Must(template.New("").Parse(templ)) func buildTemplate(nodes []string) string { size := 8 + len(nodes)*15 for _, v := range nodes { size += len(v) } buf := bytes.NewBuffer(make([]byte, 0, size)) if err := t.Execute(buf, nodes); err != nil { log.Fatal(err) // Handle error } return buf.String() } const templ = `where={ {{- range $idx, $n := . -}} {{if ne $idx 0}},{{end}}"node_name":"{{$n}}" {{- end -}} }`
Using strings.Join()
This solution is interesting because of its simplicity. We can use strings.Join() to join the nodes with the static text ","node_name":" between them, the corresponding prefix and postfix.
It is important to note: strings.Join() uses the built-in copy() function with one pre-allocated buffer []byte , so it is very fast! "As a special case, it (the copy() function) will also copy bytes from the string to a piece of byte."
func buildJoin(nodes []string) string { if len(nodes) == 0 { return "where={}" } return `where={"node_name":"` + strings.Join(nodes, `","node_name":"`) + `"}` }
Test results
We compare the following values ββof nodes :
var nodes = []string{"n1", "node2", "nodethree", "fourthNode", "n1", "node2", "nodethree", "fourthNode", "n1", "node2", "nodethree", "fourthNode", "n1", "node2", "nodethree", "fourthNode", "n1", "node2", "nodethree", "fourthNode", }
And the comparison code is as follows:
func BenchmarkOriginal(b *testing.B) { for i := 0; i < bN; i++ { buildOriginal(nodes) } } func BenchmarkBuffer(b *testing.B) { for i := 0; i < bN; i++ { buildBuffer(nodes) } }
And now the results:
BenchmarkOriginal-4 200000 10572 ns/op BenchmarkBuffer-4 500000 2914 ns/op BenchmarkBuffer2-4 1000000 2024 ns/op BenchmarkBufferTemplate-4 30000 77634 ns/op BenchmarkJoin-4 2000000 830 ns/op
Some unsurprising facts: buildBuffer() 3.6 times faster than buildOriginal() , and buildBuffer2() (with a pre-calculated size) is about 30% faster than buildBuffer() because it does not need to redistribute (and copy) the internal buffer.
Some amazing facts: buildJoin() extremely fast, even surpasses buildBuffer2() by 2.4 times (due to using only []byte and copy() ). buildTemplate() , on the other hand, turned out to be rather slow: 7 times slower than buildOriginal() . The main reason for this is that it uses (should use) the reflection under the hood.