Generated banner for Golang Performance
Generated logo for Golang Performance

Golang Performance

Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe

  • Skill
  • Guidelines
  • Software development
  • MIT
  • Low risk

From the author

Golang performance optimization patterns and methodology - if X bottleneck, then apply Y. Covers allocation reduction, CPU efficiency, memory layout, GC tuning, pooling, caching, and hot-path optimization. Use when profiling or benchmarks have identified a bottleneck and you need the right optimization pattern to fix it. Also use when performing performance code review to suggest improvements or benchmarks that could help identify quick performance gains. Not for measurement methodology (→ See `samber/cc-skills-golang@golang-benchmark` skill) or debugging workflow (→ See `samber/cc-skills-golang@golang-troubleshooting` skill).
— the description by samber

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to identifying and resolving performance bottlenecks in Golang applications. It emphasizes a data-driven philosophy: profile first, hypothesize, apply a single optimization, and re-measure. It covers critical areas including memory allocation reduction, CPU efficiency, cache locality, runtime tuning, and algorithmic improvements. The skill is designed to guide developers through the iterative cycle of benchmarking and optimization, ensuring that changes are statistically significant and documented.

What it does

  • Provides a decision tree for mapping pprof signals to specific optimization patterns.
  • Defines an iterative workflow for benchmarking, diagnosing, and verifying performance improvements.
  • Offers deep-dive patterns for memory management, CPU hot-path optimization, and I/O efficiency.
  • Includes guidance on avoiding common Go performance pitfalls like lock contention and unnecessary allocations.
  • Supports architectural performance reviews through multi-agent orchestration.
  • Integrates with standard Go tooling like benchstat for statistical validation of changes.

When to use it

  • When profiling or benchmarks have identified a specific performance bottleneck.
  • When conducting a performance-focused code review to suggest structural improvements.
  • When you need to reduce GC pressure or latency in high-throughput Go services.
  • When you need to verify if a proposed optimization actually yields a measurable gain.

Advantages

  • Reduces reliance on intuition by enforcing a measurement-first methodology.
  • Provides actionable patterns for common Go-specific performance issues.
  • Ensures performance improvements are statistically validated using benchstat.
  • Helps prevent performance regressions by encouraging documented benchmarks.
  • Enables systematic identification of bottlenecks across memory, CPU, and I/O.

Usage examples

“My service has high GC overhead. How can I reduce allocations?”
The agent will guide you through identifying allocation hot-spots using pprof, suggest patterns like sync.Pool or pre-allocation, and help you verify the reduction in allocs/op using benchstat.
“Review this package for performance anti-patterns.”
The agent will perform an architectural scan, checking for issues like unbounded goroutines, missing connection pools, or inefficient data structures, and provide a report with optimization recommendations.
“I suspect a function is CPU-bound. How do I optimize it?”
The agent will help you establish a baseline benchmark, analyze the CPU profile to check for inlining or cache locality issues, and suggest specific code changes to improve execution speed.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/samber/golang-performance/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip golang-performance.zip -d .agents/skills

Documentation by samber

Persona: You are a Go performance engineer. You never optimize without profiling first — measure, hypothesize, change one thing, re-measure.

Thinking mode: Reason as thoroughly as possible for performance optimization — shallow analysis misidentifies bottlenecks and deep reasoning ensures the right optimization is applied to the right problem. On Claude Code, use ultrathink to trigger extended thinking explicitly.

Orchestration mode: Fan out the three sub-agents described in Review mode (architecture) (allocation and memory layout, I/O and concurrency, algorithmic complexity and caching) for a broad architectural performance review. A single hot-path review stays sequential; fan-out only pays off at package/service scope. On Claude Code, use ultracode to opt into multi-agent orchestration explicitly.

Modes:

  • Review mode (architecture) — broad scan of a package or service for structural anti-patterns (missing connection pools, unbounded goroutines, wrong data structures). Use up to 3 parallel sub-agents split by concern: (1) allocation and memory layout, (2) I/O and concurrency, (3) algorithmic complexity and caching.

  • Review mode (hot path) — focused analysis of a single function or tight loop identified by the caller. Work sequentially; one sub-agent is sufficient.

  • Optimize mode — a bottleneck has been identified by profiling. Follow the iterative cycle (define metric → baseline → diagnose → improve → compare) sequentially — one change at a time is the discipline.

Dependencies:

  • benchstat: go install golang.org/x/perf/cmd/benchstat@latest

Go Performance Optimization

Core Philosophy

  1. Profile before optimizing — intuition about bottlenecks is wrong ~80% of the time. Use pprof to find actual hot spots (→ See samber/cc-skills-golang@golang-troubleshooting skill)

  2. Allocation reduction yields the biggest ROI — Go's GC is fast but not free. Reducing allocations per request often matters more than micro-optimizing CPU

  3. Document optimizations — add code comments explaining why a pattern is faster, with benchmark numbers when available. Future readers need context to avoid reverting an "unnecessary" optimization

Rule Out External Bottlenecks First

Before optimizing Go code, verify the bottleneck is in your process — if 90% of latency is a slow DB query or API call, reducing allocations won't help.

Diagnose: 1- fgprof — captures on-CPU and off-CPU (I/O wait) time; if off-CPU dominates, the bottleneck is external 2- go tool pprof (goroutine profile) — many goroutines blocked in net.(*conn).Read or database/sql = external wait 3- Distributed tracing (OpenTelemetry) — span breakdown shows which upstream is slow

When external: optimize that component instead — query tuning, caching, connection pools, circuit breakers (→ See samber/cc-skills-golang@golang-database skill, Caching Patterns).

Iterative Optimization Methodology

The cycle: Define Goals → Benchmark → Diagnose → Improve → Benchmark
  1. Define your metric — latency, throughput, memory, or CPU? Without a target, optimizations are random

  2. Write an atomic benchmark — isolate one function per benchmark to avoid result contamination (→ See samber/cc-skills-golang@golang-benchmark skill)

  3. Measure baseline — go test -bench=BenchmarkMyFunc -benchmem -count=6 ./pkg/... | tee /tmp/report-1.txt

  4. Diagnose — use the Diagnose lines in each deep-dive section to pick the right tool

  5. Improve — apply ONE optimization at a time with an explanatory comment

  6. Compare — benchstat /tmp/report-1.txt /tmp/report-2.txt to confirm statistical significance

  7. Commit — paste the benchstat output in the commit body so reviewers and future readers see the exact improvement; follow the perf(scope): summary commit type

  8. Repeat — increment report number, tackle next bottleneck

Refer to library documentation for known patterns before inventing custom solutions. Keep all /tmp/report-*.txt files as an audit trail.

When multiple candidate optimizations compete for the same bottleneck, implement each in an isolated worktree via a separate sub-agent — then → See samber/cc-skills-golang@golang-benchmark skill for comparing the variants and its serial-measurement caveat (concurrent benchmark runs on shared CPU contaminate results, even when the implementations themselves were built in parallel).

Decision Tree: Where Is Time Spent?

BottleneckSignal (from pprof)Action
Too many allocationsalloc_objects high in heap profileMemory optimization
CPU-bound hot loopfunction dominates CPU profileCPU optimization
GC pauses / OOMhigh GC%, container limitsRuntime tuning
Network / I/O latencygoroutines blocked on I/OI/O & networking
Repeated expensive worksame computation/fetch multiple timesCaching patterns
Wrong algorithmO(n²) where O(n) existsAlgorithmic complexity
Lock contentionmutex/block profile hot→ See samber/cc-skills-golang@golang-concurrency skill
Slow queriesDB time dominates traces→ See samber/cc-skills-golang@golang-database skill

Common Mistakes

MistakeFix
Optimizing without profilingProfile with pprof first — intuition is wrong ~80% of the time
Default http.Client without TransportMaxIdleConnsPerHost defaults to 2; set to match your concurrency level
Logging in hot loopsLog calls prevent inlining and allocate even when the level is disabled. Use slog.LogAttrs
panic/recover as control flowpanic allocates a stack trace and unwinds the stack; use error returns
unsafe without benchmark proofOnly justified when profiling shows >10% improvement in a verified hot path
No GC tuning in containersSet GOMEMLIMIT to 80-90% of container memory to prevent OOM kills
reflect.DeepEqual in production50-200x slower than typed comparison; use slices.Equal, maps.Equal, bytes.Equal

Deep Dives

  • Memory Optimization — allocation patterns, backing array leaks, sync.Pool, struct alignment

  • CPU Optimization — inlining, cache locality, false sharing, ILP, reflection avoidance

  • I/O & Networking — HTTP transport config, streaming, JSON performance, cgo, batch operations

  • Runtime Tuning — GOGC, GOMEMLIMIT, GC diagnostics, GOMAXPROCS, PGO

  • Caching Patterns — algorithmic complexity, compiled patterns, singleflight, work avoidance

  • Production Observability — Prometheus metrics, PromQL queries, continuous profiling, alerting rules

CI Regression Detection

Automate benchmark comparison in CI to catch regressions before they reach production. → See samber/cc-skills-golang@golang-benchmark skill for benchdiff and cob setup.

Cross-References

  • → See samber/cc-skills-golang@golang-benchmark skill for benchmarking methodology, benchstat, and b.Loop() (Go 1.24+)

  • → See samber/cc-skills-golang@golang-troubleshooting skill for pprof workflow, escape analysis diagnostics, and performance debugging

  • → See samber/cc-skills-golang@golang-data-structures skill for slice/map preallocation and strings.Builder

  • → See samber/cc-skills-golang@golang-concurrency skill for worker pools, sync.Pool API, goroutine lifecycle, and lock contention

  • → See samber/cc-skills-golang@golang-safety skill for defer in loops, slice backing array aliasing

  • → See samber/cc-skills-golang@golang-database skill for connection pool tuning and batch processing

  • → See samber/cc-skills-golang@golang-observability skill for continuous profiling in production

Files

references/cpu.md

CPU Optimization

CPU-bound bottlenecks show up as functions dominating the CPU profile. The patterns below target the most common causes: missed inlining opportunities, poor cache utilization, and unnecessary computation.

Table of Contents

  • Function Inlining

    • Value receivers enable inlining

  • Cache Locality

    • Row-major traversal

    • Contiguous 2D allocation

    • Struct of Arrays (SoA) vs Array of Structs (AoS)

    • Pointer-heavy vs value-heavy data

  • False Sharing

  • Instruction-Level Parallelism

  • SIMD (Single Instruction, Multiple Data)

    • Handling CPU-specific instruction sets

  • Tight Loops and the Scheduler

  • Reflection and Type Assertions

  • Monotonic Time

Function Inlining

Diagnose: 1- go tool pprof (CPU profile) — look for hot functions with high cumulative CPU time; if a small helper dominates the profile, it's likely not being inlined 2- go build -gcflags="-m" — grep for "cannot inline" on your hot-path functions; the reason (e.g., "function too complex", "unhandled op") tells you what to simplify

The Go compiler inlines small functions, eliminating call overhead. Functions that are too complex (loops, many statements, or calls to non-inlineable functions) won't be inlined — this matters in tight loops called millions of times.

// Bad — log call prevents inlining
func abs(x int) int {
    if x < 0 {
        log.Printf("negative: %d", x) // blocks inlining
        return -x
    }
    return x
}

// Good — simple enough to inline
func abs(x int) int {
    if x < 0 { return -x }
    return x
}

Check inlining decisions:

go build -gcflags="-m" ./... 2>&1 | grep "can inline"
go build -gcflags="-m" ./... 2>&1 | grep "inlining call"

Move side effects (logging, metrics) outside hot-path functions or guard them with conditional checks.

Value receivers enable inlining

Value receivers allow the compiler to fully inline fluent method chains. Pointer receivers add indirection that blocks inlining:

// Pointer receiver — indirection prevents inlining, constant overhead per call
func (c *config) WithTimeout(d time.Duration) *config { c.timeout = d; return c }

// Value receiver — fully inlined, -80% time in fluent chains
func (c config) WithTimeout(d time.Duration) config { c.timeout = d; return c }

Cache Locality

Diagnose: 1- go tool pprof (CPU profile) — look for loops over slices/matrices consuming disproportionate CPU; cache-miss-heavy code shows high runtime.memmove or flat time in simple index operations 2- go test -bench — benchmark row-first vs column-first traversal; expect 10-50x difference on large matrices purely from cache effects

Modern CPUs fetch data in 64-byte cache lines. Sequential memory access is dramatically faster than random access because the prefetcher can load the next cache line before you need it.

Row-major traversal

Go stores 2D arrays in row-major order. Column-first traversal jumps across memory, causing cache misses:

// Bad — column-first, jumps across memory (~10M cache misses)
for col := 0; col < 1024; col++ {
    for row := 0; row < 1024; row++ {
        sum += matrix[row][col]
    }
}

// Good — row-first, sequential access (~125K cache misses)
for row := 0; row < 1024; row++ {
    for col := 0; col < 1024; col++ {
        sum += matrix[row][col]
    }
}

Performance difference: 10-50x purely from cache effects.

Contiguous 2D allocation

Allocating each row separately scatters data across the heap:

// Bad — N separate allocations, poor cache locality
matrix := make([][]float64, rows)
for i := range matrix { matrix[i] = make([]float64, cols) }

// Good — single contiguous allocation, cache-friendly
data := make([]float64, rows*cols)
matrix := make([][]float64, rows)
for i := range matrix { matrix[i] = data[i*cols : (i+1)*cols] }
Struct of Arrays (SoA) vs Array of Structs (AoS)

When iterating over a single field of a struct, AoS wastes cache space loading unused fields:

// AoS — loading each Point (24 bytes) to read only x (8 bytes) = 66% cache waste
type Point struct { x, y, z float64 }
points := make([]Point, n)
for i := range points { sum += points[i].x }

// SoA — all x values contiguous, 100% cache utilization
type Points struct { xs, ys, zs []float64 }
for i := range ps.xs { sum += ps.xs[i] }

Use SoA when iterating over a subset of fields (physics, graphics, analytics). AoS is fine when accessing all fields together or for small structs.

Pointer-heavy vs value-heavy data

Index-based data structures (nodes stored in a contiguous array, referenced by index) beat pointer-based structures for cache locality:

// Pointer-based tree — each node scattered in heap, random cache misses
type Node struct { value int; left, right *Node }

// Index-based tree — nodes in contiguous array, cache-friendly
type Tree struct { nodes []Node }
type Node struct { value int; left, right int } // indices into nodes

False Sharing

Diagnose: 1- go tool pprof (CPU profile + mutex profile) — look for atomic operations or counter updates consuming unexpectedly high CPU; in the mutex profile, look for contention on variables that shouldn't need locking 2- go test -bench — benchmark concurrent counter increments; if adding goroutines makes it slower instead of faster, false sharing is likely

When goroutines update variables that share the same 64-byte CPU cache line, each write invalidates the other core's cache, causing severe degradation:

// Bad — a and b on same cache line, cores fight for it
type Counters struct { a, b int64 }

// Good — separate cache lines, no interference
type Counters struct {
    a int64    // 8 bytes
    _ [56]byte // 64 - 8 = 56 bytes padding
    b int64    // 8 bytes
}

Only apply cache-line padding when profiling confirms contention on concurrent counters/flags.

Instruction-Level Parallelism

Diagnose: 1- go tool pprof (CPU profile) — look for tight arithmetic loops (sum, dot product) where the loop body itself dominates CPU; these are candidates for multi-accumulator optimization 2- go test -bench — benchmark single vs multi-accumulator versions; expect 2-4x improvement when the loop is truly CPU-bound with a dependency chain

Modern CPUs execute multiple independent instructions simultaneously. A single accumulator creates a dependency chain — each addition waits for the previous one:

// Bad — sequential dependency, CPU pipeline stalls
var total int64
for _, v := range data { total += v }

// Good — 4 independent accumulators, CPU pipelines all 4 in parallel
var s0, s1, s2, s3 int64
limit := len(data) - len(data)%4
for i := 0; i < limit; i += 4 {
    s0 += data[i]; s1 += data[i+1]; s2 += data[i+2]; s3 += data[i+3]
}
for i := limit; i < len(data); i++ { s0 += data[i] }
total := s0 + s1 + s2 + s3

Expect 2-4x improvement for tight arithmetic loops. Only use when profiling shows the loop is a bottleneck.

SIMD (Single Instruction, Multiple Data)

Diagnose: 1- go tool pprof (CPU profile) — confirm a numeric inner loop consumes >20% of CPU; SIMD only helps CPU-bound numeric work, not allocation or I/O bottlenecks 2- go test -bench — measure the loop's baseline ns/op; provides the reference point to validate SIMD gains 3- go build -gcflags="-d=ssa/prove/debug=2" — check if the compiler already auto-vectorized the loop; look for "Proved" bounds-check eliminations that enable vectorization 4- GOSSAFUNC=MyFunc go build — generate SSA dump (ssa.html) to inspect whether the compiler produces vector instructions for the hot loop 5- go tool objdump -s MyFunc ./binary — verify the final assembly contains SIMD instructions (e.g., VMOVAPD, VADDPD on amd64) rather than scalar equivalents

Go 1.26+ includes an experimental simd/archsimd package (requires GOEXPERIMENT=simd flag) providing low-level, architecture-specific SIMD intrinsics — amd64 with 128/256/512-bit vectors, and arm64/wasm at 128-bit as of Go 1.27. Go 1.27 also adds a portable simd package (vector types like Int8s, Float32s) that works across architectures and falls back to scalar code without hardware support. For broader portability, the compiler auto-vectorizes simple loops, and several strategies exist.

Options for explicit SIMD in Go:

  • Experimental simd/simd/archsimd (Go 1.26+, speculative) — Direct SIMD intrinsics via vector types with CPU feature detection. simd (Go 1.27+) is portable across architectures; simd/archsimd is architecture-specific (amd64, plus arm64/wasm as of Go 1.27). Use with caution: both are experimental, in-progress APIs (GOEXPERIMENT=simd) whose package paths and type names are subject to change before stabilization. Not covered by Go 1 compatibility guarantees, and should never be exposed in public APIs. Verify the actual import path and API against the Go toolchain you are using.

    // Requires: GOEXPERIMENT=simd go build
    // WARNING: experimental API — package path and types may change
    import "simd/archsimd"
    
    v := archsimd.Int32x4{1, 2, 3, 4}
  • Let the compiler do it — write simple, idiomatic loops on []float64/[]int32 slices. Check auto-vectorization: go build -gcflags="-d=ssa/prove/debug=2" ./...

  • math/bits — operations like OnesCount, LeadingZeros, RotateLeft map directly to hardware instructions (POPCNT, CLZ, ROL)

  • Hand-written assembly — .s files with AVX2/NEON instructions for critical inner loops. Libraries like klauspost/compress and minio/sha256-simd use this approach

  • Third-party vectorized libraries — for common operations (hashing, compression, encoding), use libraries that already have optimized SIMD implementations rather than writing your own

Handling CPU-specific instruction sets

Hand-written assembly unlocks higher performance but couples code to specific CPU features (AVX2, NEON, etc.). Three strategies exist:

1. Compile on a production-similar machine

Build binaries on hardware matching your deployment target, so the compiler generates code for the exact CPU instruction set available at runtime:

# Compiling on production hardware ensures optimal code generation
# for that specific CPU architecture and generation
ssh prod-server "cd /path && go build -o app ."

Tradeoff: Simplest approach, but requires access to production hardware and different binaries per CPU type (Intel vs AMD vs Apple Silicon). Breaks CI/CD portability.

2. Runtime CPU feature detection + multiple implementations

Implement the function multiple times — one for each CPU capability — and dispatch at runtime:

// dispatch.go
var sumImpl func([]int64) int64

func init() {
    if cpu.X86.HasAVX2 {
        sumImpl = sumAVX2
    } else {
        sumImpl = sumGeneric
    }
}

func Sum(data []int64) int64 {
    return sumImpl(data)
}

// sum_generic.go
func sumGeneric(data []int64) int64 {
    var total int64
    for _, v := range data { total += v }
    return total
}

// sum_amd64.s
TEXT ·sumAVX2(SB), NOSPLIT, $0-32
    // AVX2 implementation
    VMOVAPD (SI), Y0
    // ...

Tradeoff: Single binary works everywhere; trades one function-call dispatch overhead for full CPU feature utilization. Libraries like encoding/base64 and sha256 use this pattern.

3. Compile-time selection with //go:build tags

Use conditional compilation to generate different code at build time for each target:

// sum_fast.go
//go:build amd64 && !nosimd

package mylib

// AVX2 assembly via cgo or inline
func Sum(data []int64) int64 {
    return sumAVX2(data) // or calls to .s file
}

// sum_generic.go
//go:build !amd64 || nosimd

package mylib

func Sum(data []int64) int64 {
    var total int64
    for _, v := range data { total += v }
    return total
}

Build different binaries per target:

GOOS=linux GOARCH=amd64 go build -o app-avx2 .     # Uses sum_fast.go
GOOS=darwin GOARCH=arm64 go build -o app-neon .    # Uses sum_generic.go
go build -tags=nosimd -o app-safe .                # Fallback everywhere

Tradeoff: Zero runtime overhead; each binary is fully optimized for its target. Requires shipping multiple binaries and coordinating which binary runs where.

When SIMD is NOT worth pursuing:

  • Go's lack of intrinsics means SIMD requires assembly — high maintenance burden, platform-specific, and harder to debug

  • Auto-vectorization covers the most common cases (simple numeric loops)

  • If your bottleneck is allocations or I/O, SIMD won't help

Recommendation:

  • Start with auto-vectorization.

  • For Go 1.27+, evaluate the portable simd package for cross-architecture code, or simd/archsimd for architecture-specific tuning (amd64, arm64, wasm) — remembering both are experimental.

  • Move to runtime detection (option 2 above) if profiling shows a bottleneck and the code needs to run on heterogeneous hardware.

  • Only use compile-time selection (option 3) if you control the deployment environment and can test each per-binary variant.

Only invest in hand-written SIMD when profiling shows a numeric inner loop consuming >20% of CPU and the compiler isn't auto-vectorizing it.

Tight Loops and the Scheduler

Diagnose: 1- go tool pprof (goroutine profile) — look for many goroutines stuck in "runnable" state (waiting for CPU) while one goroutine monopolizes execution 2- go tool trace — visualize goroutine scheduling over time; look for long uninterrupted execution spans on one goroutine while others show scheduling gaps 3- GODEBUG=schedtrace=1000 — print scheduler state every second; look for unbalanced runqueue counts across P's indicating one P is starved 4- runtime/metrics (/sched/latencies:seconds) — measure how long goroutines wait before getting CPU; high p99 latencies confirm starvation 5- Prometheus rate(process_cpu_seconds_total[2m]) — monitor if CPU usage hits GOMAXPROCS ceiling; if saturated while other goroutines are starved, a tight loop is monopolizing P's

A goroutine running a CPU-intensive tight loop without function calls may not yield to the scheduler, starving other goroutines. Go 1.14+ added asynchronous preemption, but very tight loops with fully inlined operations can still cause issues:

// Potential starvation — pure computation, no function calls
for { x = x*a + b }

// Safe — non-inlined call triggers preemption check
for item := range work {
    processBatch(item) // function call = preemption point
}

When to use non-inlined calls for scheduling: Use non-inlined function calls when:

  • The loop runs for a long time (hundreds of milliseconds or more of uninterrupted computation)

  • Other goroutines are waiting to run (e.g., handling requests, I/O completion, channel operations)

  • The loop contains only arithmetic or memory operations with no function calls

For short bursts of computation (< 10ms), preemption isn't critical and inlining for CPU efficiency takes priority.

Detecting scheduler starvation: Use these tools to confirm goroutines are being starved:

  • go tool pprof goroutine profile — shows goroutines stuck in "runnable" state (waiting for CPU). If many goroutines are runnable while one dominates CPU, starvation is happening

  • go tool trace — visualizes goroutine scheduling over time. Look for gaps where goroutines aren't running because one goroutine monopolized the scheduler

  • runtime/metrics (Go 1.19+) — measure /sched/latencies:seconds to quantify how long goroutines wait for CPU

  • Observable symptoms — high response latency, requests timing out, uneven request distribution, goroutine counts climbing

Preventing inlining with //go:noinline: If you have a function that's normally inlinable (small, hot) but you specifically want it to not inline to force scheduler preemption checks, use the //go:noinline compiler directive:

//go:noinline
func processBatch(item WorkItem) {
    // CPU-intensive work here
    // This call site will NOT be inlined, even if the function is small
    // The function call itself becomes a preemption point for the scheduler
}

// In tight loop
for item := range work {
    processBatch(item) // Guaranteed preemption point
}

Trade-off: Using //go:noinline prevents inlining, which:

  • Pros: Guarantees scheduler preemption checks; prevents goroutine starvation

  • Cons: Adds function call overhead (~10-30 CPU cycles); reduces instruction-level parallelism (ILP) in the caller

Only use //go:noinline if profiling shows that scheduler preemption starvation is actually blocking other goroutines. Unnecessary //go:noinline directives penalize throughput and latency.

Reflection and Type Assertions

Diagnose: 1- go tool pprof (CPU profile) — look for reflect.Value.*, reflect.DeepEqual, or fmt.Sprintf (which uses reflect internally) appearing in hot paths 2- go test -bench — compare reflection-based vs typed versions; expect 10-200x difference depending on the reflection operation

  • reflect in hot paths — 10-100x slower due to type introspection and boxing. Replace with generics or typed code

  • reflect.DeepEqual — 50-200x slower than typed comparisons. Use slices.Equal, maps.Equal, bytes.Equal (Go 1.21+)

  • Type switch vs repeated assertions — type switch dispatches in one evaluation:

// Bad — evaluates interface multiple times
if s, ok := v.(string); ok { return s }
if i, ok := v.(int); ok { return strconv.Itoa(i) }

// Good — single dispatch
switch v := v.(type) {
case string: return v
case int:    return strconv.Itoa(v)
}

Monotonic Time

Diagnose: 1- go test -bench — benchmark time.Since(start) vs time.Now().Sub(start); expect a small but consistent improvement from monotonic clock avoiding wall-clock syscall

time.Since(start) uses the monotonic clock, which is immune to wall-clock adjustments (NTP, DST) and slightly faster:

var appStart = time.Now() // captures monotonic time + wall-clock on program start

func myFunc() {
    // Compare durations, not wall-clock times
    elapsed := time.Since(appStart)
    if elapsed > threshold { ... }
}

Requirements and compatibility

Written for: Claude Code, OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Claude Code frontmatter

The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
samber
Publisher
Samuel Berthe (Community)
Distributed by
Samuel Berthe
Source
samber/cc-skills-golang · skills/golang-performance · commit bb1d43d
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-performance.zip