Skip to content

[Code Quality] Add Slice Pre-allocation for Known-Size Collections #13279

@github-actions

Description

@github-actions

Description

The codebase has widespread use of make([]T, 0) without capacity hints, even when the final size is known. This causes multiple reallocations, memory copying, and increased GC pressure.

Problem

Metrics:

  • Hundreds of instances of make([]T, 0) without capacity argument
  • Zero uses of slices.Grow() for pre-allocation (Go 1.20+ feature)
  • Most common in loop-heavy code paths processing known-size collections

Impact:

  • Multiple reallocations as slices grow (typical growth: 0→1→2→4→8→16...)
  • Memory copying on each reallocation
  • Increased GC pressure from intermediate allocations
  • Most impactful in loops processing known-size collections

Example Pattern

Current (inefficient):

results := make([]Result, 0)  // ❌ No capacity hint
for _, item := range items {   // len(items) is known!
    results = append(results, process(item))
}

Should be:

results := make([]Result, 0, len(items))  // ✅ Pre-allocated
for _, item := range items {
    results = append(results, process(item))
}

Focus Areas

High-allocation hot paths (prioritize these):

  • pkg/workflow/ - compiler and processing pipelines
  • pkg/cli/trial_command.go (1,002 lines)
  • pkg/cli/mcp_inspect.go (1,011 lines)
  • pkg/workflow/safe_outputs_config_generation.go (978 lines)

Implementation Patterns

Pattern 1 - Known Size

// Before
var builder []string
for _, x := range data {
    builder = append(builder, x.Name)
}

// After
builder := make([]string, 0, len(data))
for _, x := range data {
    builder = append(builder, x.Name)
}

Pattern 2 - Estimated Size

// Before
var builder []string
for _, x := range data {
    for _, y := range x.Children {
        builder = append(builder, y.Name)
    }
}

// After
estimatedSize := len(data) * 5  // Based on avg children
builder := make([]string, 0, estimatedSize)
for _, x := range data {
    for _, y := range x.Children {
        builder = append(builder, y.Name)
    }
}

Pattern 3 - Using slices.Grow (Go 1.20+)

import "slices"

var results []Result
for _, batch := range batches {
    // Grow slice efficiently before processing batch
    results = slices.Grow(results, len(batch))
    for _, item := range batch {
        results = append(results, process(item))
    }
}

Success Criteria

  • Identify high-allocation files: go test -bench=. -benchmem ./... | grep alloc
  • Apply pre-allocation to top 10 hot spots
  • Benchmark before/after (measure allocs/op reduction)
  • Full test suite passes: go test ./...
  • Use pprof to validate: go test -memprofile=mem.out
  • Document pattern in code review guidelines
  • make agent-finish passes

Priority

Medium - Performance improvement with low implementation risk

Estimated Effort: Large (10-15 hours across many files, but low risk per change)

Source

Extracted from Sergo Performance Optimization Analysis - Discussion #11840

Analysis Quote:

"Missing slice pre-allocation - Widespread make([]T, 0) without capacity hint, even when final size is known"

Performance Impact:

"Reduce memory allocations and GC pressure"

References:

AI generated by Discussion Task Miner - Code Quality Improvement Agent

  • expires on Feb 16, 2026, 1:28 PM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions