Go
Challenge 3 - Performance
Write an analysis finding a bottleneck with benchmark and pprof.
Challenge: cut allocations
You have a slow function that joins numbers with commas:
func yavas(n []int) string {
s := ""
for i, x := range n {
if i > 0 { s += "," }
s += strconv.Itoa(x)
}
return s
}
Every += allocates a new string. Your task: produce the same output with far fewer allocations using strings.Builder and strconv.AppendInt, and measure the difference with testing.AllocsPerRun.
Plan
Reserve capacity first: about 20 bytes per number is enough, buf := make([]byte, 0, len(n)*21). In the loop append the comma and strconv.AppendInt(buf, int64(x), 10); return string(buf) at the end. To measure, run both functions inside testing.AllocsPerRun(100, ...) and compare. Also check that the outputs are equal.
Exercise Fast version
Write the yavas and hizli functions. For n := []int{1, 22, 333, 4444}, print on one line whether both outputs are equal and whether hizli allocates less than yavas.
Show example solution
package main
import (
"fmt"
"strconv"
"testing"
)
func yavas(n []int) string {
s := ""
for i, x := range n {
if i > 0 {
s += ","
}
s += strconv.Itoa(x)
}
return s
}
func hizli(n []int) string {
buf := make([]byte, 0, len(n)*21)
for i, x := range n {
if i > 0 {
buf = append(buf, ',')
}
buf = strconv.AppendInt(buf, int64(x), 10)
}
return string(buf)
}
var sink string
func main() {
n := []int{1, 22, 333, 4444}
a := testing.AllocsPerRun(100, func() { sink = yavas(n) })
b := testing.AllocsPerRun(100, func() { sink = hizli(n) })
fmt.Println(yavas(n) == hizli(n), b < a)
}
Test yourself
-
What makes <code>strconv.AppendInt</code> better than <code>Itoa</code> here?
- It runs concurrently
- It writes the number into an existing byte slice without an intermediate string
- It's shorter to write
- It also supports decimals
Show answer
It writes the number into an existing byte slice without an intermediate string — Itoa allocates a new string per number; AppendInt appends to the given buffer and doesn't allocate if the buffer is big enough.
-
Append to the buffer
buf = strconv.____(buf, int64(x), 10)
Show answer
buf = strconv.AppendInt(buf, int64(x), 10)
-
Method and cost
- s += ...
- A new string each step
- strconv.Itoa
- One string per number
- AppendInt
- Writes into a buffer
- make(..., 0, cap)
- Capacity up front
Next lesson: Challenge 4 - Database