go-performance

Original🇺🇸 English
Translated

Go performance patterns including efficient string handling, type conversions, and container capacity hints. Use when optimizing Go code or writing performance-critical sections.

8installs
Added on

NPX Install

npx skill4agent add cxuu/golang-skills go-performance

Go Performance Patterns

Source: Uber Go Style Guide
Performance-specific guidelines apply only to the hot path. Don't prematurely optimize—focus these patterns where they matter most.

Prefer strconv over fmt

When converting primitives to/from strings,
strconv
is faster than
fmt
.
Source: Uber Go Style Guide
Bad:
go
for i := 0; i < b.N; i++ {
    s := fmt.Sprint(rand.Int())
}
Good:
go
for i := 0; i < b.N; i++ {
    s := strconv.Itoa(rand.Int())
}
Benchmark comparison:
ApproachSpeedAllocations
fmt.Sprint
143 ns/op2 allocs/op
strconv.Itoa
64.2 ns/op1 allocs/op

Avoid Repeated String-to-Byte Conversions

Do not create byte slices from a fixed string repeatedly. Instead, perform the conversion once and capture the result.
Source: Uber Go Style Guide
Bad:
go
for i := 0; i < b.N; i++ {
    w.Write([]byte("Hello world"))
}
Good:
go
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
    w.Write(data)
}
Benchmark comparison:
ApproachSpeed
Repeated conversion22.2 ns/op
Single conversion3.25 ns/op
The good version is ~7x faster because it avoids allocating a new byte slice on each iteration.

Prefer Specifying Container Capacity

Specify container capacity where possible to allocate memory up front. This minimizes subsequent allocations from copying and resizing as elements are added.
Source: Uber Go Style Guide

Map Capacity Hints

Provide capacity hints when initializing maps with
make()
.
go
make(map[T1]T2, hint)
Note: Unlike slices, map capacity hints do not guarantee complete preemptive allocation—they approximate the number of hashmap buckets required.
Bad:
go
files, _ := os.ReadDir("./files")

m := make(map[string]os.DirEntry)
for _, f := range files {
    m[f.Name()] = f
}
// Map resizes dynamically, causing multiple allocations
Good:
go
files, _ := os.ReadDir("./files")

m := make(map[string]os.DirEntry, len(files))
for _, f := range files {
    m[f.Name()] = f
}
// Map is right-sized at initialization, fewer allocations

Slice Capacity

Provide capacity hints when initializing slices with
make()
, particularly when appending.
go
make([]T, length, capacity)
Unlike maps, slice capacity is not a hint—the compiler allocates exactly that much memory. Subsequent
append()
operations incur zero allocations until capacity is reached.
Bad:
go
for n := 0; n < b.N; n++ {
    data := make([]int, 0)
    for k := 0; k < size; k++ {
        data = append(data, k)
    }
}
Good:
go
for n := 0; n < b.N; n++ {
    data := make([]int, 0, size)
    for k := 0; k < size; k++ {
        data = append(data, k)
    }
}
Benchmark comparison:
ApproachTime (100M iterations)
No capacity2.48s
With capacity0.21s
The good version is ~12x faster due to zero reallocations during append.

Pass Values

Source: Go Wiki CodeReviewComments (Advisory)
Don't pass pointers as function arguments just to save a few bytes. If a function refers to its argument
x
only as
*x
throughout, then the argument shouldn't be a pointer.
Common instances where values should be passed directly:
  • Pointer to a string (
    *string
    ) — strings are already small fixed-size headers
  • Pointer to an interface value (
    *io.Reader
    ) — interfaces are fixed-size (type + data pointers)
Bad:
go
func process(s *string) {
	fmt.Println(*s)  // only dereferences, never modifies
}
Good:
go
func process(s string) {
	fmt.Println(s)
}
Exceptions:
  • Large structs where copying is expensive
  • Small structs that might grow in the future

Quick Reference

PatternBadGoodImprovement
Int to string
fmt.Sprint(n)
strconv.Itoa(n)
~2x faster
Repeated
[]byte
[]byte("str")
in loop
Convert once outside~7x faster
Map initialization
make(map[K]V)
make(map[K]V, size)
Fewer allocs
Slice initialization
make([]T, 0)
make([]T, 0, cap)
~12x faster
Small fixed-size args
*string
,
*io.Reader
string
,
io.Reader
No indirection

See Also

  • For core style principles:
    go-style-core
  • For naming conventions:
    go-naming