Challenge 3 - Performance
- Go course
- Advanced
- Lesson 67/68
- 8 min
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.
Practice 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.
Starter code
package main
import (
"fmt"
"strconv"
"testing"
)
func main() {
// buraya yaz
}Sample 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)
}Expected output
true true
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 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 Correct answer
- It's shorter to write
- It also supports decimals
Explanation: Itoa allocates a new string per number; AppendInt appends to the given buffer and doesn't allocate if the buffer is big enough.
2 Append to the buffer
buf = strconv.____(buf, int64(x), 10)
buf = strconv.AppendInt(buf, int64(x), 10)
Explanation: AppendInt appends the number's decimal form to an existing byte slice.
3 Method and cost
| Term | Meaning |
|---|---|
s += ... |
A new string each step |
strconv.Itoa |
One string per number |
AppendInt |
Writes into a buffer |
make(..., 0, cap) |
Capacity up front |