Перейти к содержимому

Runtime Tuning в Go

Зачем знать: Go runtime имеет ряд параметров (GOGC, GOMEMLIMIT, GOMAXPROCS), которые на дефолтах подходят 80% случаев. Но в production — особенно в контейнерах, low-latency сервисах, или приложениях с большим heap — дефолты могут быть катастрофой: OOM kill, частые GC pauses, CPU starvation. Знание runtime tuning отличает инженера, способного запустить Go-сервис в Kubernetes без сюрпризов, от инженера, который удивляется почему kubectl describe pod показывает OOMKilled. С GOMEMLIMIT (Go 1.19+) и frame pointers (Go 1.20+) подход к tuning изменился — это знания 2026 года.


  1. Базовая концепция (повторение)
  2. Production-практики
  3. Gotchas (10+)
  4. Реальные кейсы
  5. Вопросы (30)
  6. Practice (5-8)
  7. Источники

Go runtime — это код, который выполняется вместе с программой и обеспечивает:

  • Goroutine scheduling
  • Garbage collection
  • Memory allocation (mcache, mcentral, mheap)
  • Network polling (netpoll)
  • Stack management (growing/shrinking)
  • Channel/sync primitives

Runtime управляется через environment variables и runtime/debug API.

ПараметрПо умолчаниюЧто делает
GOGC100Trigger следующего GC при росте heap на N% от live
GOMEMLIMITбез лимита (Go 1.19+)Soft limit на total memory
GOMAXPROCSruntime.NumCPU()Кол-во P (logical processors)
GOTRACEBACKsingleУровень traceback при panic
GODEBUGпустоРазличные debug флаги (gctrace, schedtrace)

Go использует concurrent tri-color mark-sweep GC:

  1. Sweep termination (STW, очень короткая)
  2. Mark phase (concurrent с user code)
  3. Mark termination (STW, короткая — обычно <100μs)
  4. Sweep phase (concurrent)

GC запускается когда heap достигает trigger heap = live_heap * (1 + GOGC/100).

При GOGC=100, после GC heap = 100MB live, следующий GC при heap = 200MB.


Окно терминала
GOGC=100 # default
GOGC=200 # GC реже, больше memory, выше throughput
GOGC=50 # GC чаще, меньше memory, меньше throughput
GOGC=off # GC отключён (НЕ для production без GOMEMLIMIT!)

Trade-off:

  • Higher GOGC → меньше CPU тратится на GC, больше памяти
  • Lower GOGC → меньше памяти, больше CPU на GC, потенциально больше pauses

Когда увеличить GOGC:

  • Throughput более важен чем memory
  • Достаточно RAM, низкая утилизация
  • GC overhead >10% CPU (видно в gctrace)
  • Latency не критична

Когда уменьшить GOGC:

  • Memory constrained
  • Большие spikes из-за late GC
  • Если используется ballast/GOMEMLIMIT alternative
Окно терминала
GODEBUG=gctrace=1 ./myapp

Output (одна строка на каждый GC):

gc 1 @0.005s 3%: 0.018+1.3+0.076 ms clock, 0.14+0.14/1.1/2.4+0.61 ms cpu, 4->4->2 MB, 5 MB goal, 8 P

Расшифровка:

  • gc 1 — номер GC
  • @0.005s — время от старта программы
  • 3% — процент CPU, потраченный на GC с начала
  • 0.018+1.3+0.076 ms clock — STW + concurrent + STW по wall clock
  • 4->4->2 MB — heap до GC → heap во время GC → heap после
  • 5 MB goal — trigger для следующего GC
  • 8 P — количество P

Если % >25 — GC жрёт слишком много CPU.

Soft limit на total memory use процесса. Включает:

  • Heap (allocated)
  • Stacks
  • Runtime internal structures
  • Off-heap (mmap)
Окно терминала
GOMEMLIMIT=4GiB ./myapp
# или в коде:
debug.SetMemoryLimit(4 * 1024 * 1024 * 1024)

Как работает:

  • Runtime пытается удержать total memory ≤ GOMEMLIMIT
  • При приближении к лимиту — запускает GC чаще
  • При превышении — продолжает работать (soft), но GC становится агрессивным
  • Если GC не может освободить — программа продолжается с превышением (не OOM от runtime, но может быть kernel OOM)

Best practice для контейнеров:

Окно терминала
GOMEMLIMIT = container_memory_limit * 0.9

Например, при k8s limit 2Gi: GOMEMLIMIT=1843MiB (90%).

10% headroom для:

  • Off-heap (cgo, mmap)
  • Spike в моментах GC
  • Kernel page cache

Extreme low-memory:

Окно терминала
GOGC=off GOMEMLIMIT=4GiB ./myapp
  • GC не запускается по heap growth
  • GC запускается только когда приближаемся к GOMEMLIMIT
  • Минимизирует GC overhead, максимизирует use of allowed memory

⚠️ Опасно: если GC не успевает — memory pressure → OOM. Только для well-behaved apps.

GOMAXPROCS — количество P (logical processors) в шедулере. Это потолок параллелизма.

Default: runtime.NumCPU() — количество CPU на хосте.

В Docker/Kubernetes с CPU limits:

resources:
limits:
cpu: 2 # 2 cores

Но runtime.NumCPU() возвращает количество CPU на ноде (например, 32). Go запускает 32 P, шедулер пытается использовать 32 потока, но kernel cgroup throttles до 2.

Результат: thread contention, частые preemptions, низкий throughput.

import _ "go.uber.org/automaxprocs"

Один импорт — и GOMAXPROCS автоматически читается из cgroup CPU quota.

Как работает:

  • Читает /sys/fs/cgroup/cpu/cpu.cfs_quota_us (cgroup v1)
  • Или /sys/fs/cgroup/cpu.max (cgroup v2)
  • Вычисляет quota / period = effective CPUs
  • Устанавливает GOMAXPROCS соответственно

В Go 1.16+ есть partial cgroup awareness, но automaxprocs остаётся стандартом.

runtime.GOMAXPROCS(4)
  • При тестировании concurrency
  • При single-CPU pinning
  • При custom scheduling logic

Контролирует, что выводится при panic:

ЗначениеЧто выводится
noneНичего
single (default)Только current goroutine
allВсе user goroutines
systemВсе, включая runtime goroutines
crashВсе + dump core
Окно терминала
GOTRACEBACK=all ./myapp

Production практика:

  • single для уменьшения лога
  • all для debug
  • crash для core dump → analyze через viewcore

В 2026 году в основном legacy:

  • GOROOT — где установлен Go (auto-determined)
  • GOPATH — для GOPATH-mode, до Go modules (Go 1.11+)

С Go modules: GOPATH используется только для go install binary path и pkg/mod cache. Не нужно настраивать вручную.

Современный API метрик (Go 1.16+), заменяющий runtime.MemStats:

import "runtime/metrics"
samples := []metrics.Sample{
{Name: "/sched/latencies:seconds"},
{Name: "/gc/pauses:seconds"},
{Name: "/memory/classes/heap/objects:bytes"},
{Name: "/memory/classes/heap/free:bytes"},
{Name: "/memory/classes/heap/released:bytes"},
{Name: "/memory/classes/total:bytes"},
{Name: "/cpu/classes/gc/total:cpu-seconds"},
{Name: "/cgo/go-to-c-calls:calls"},
{Name: "/sync/mutex/wait/total:seconds"},
}
metrics.Read(samples)
for _, s := range samples {
fmt.Printf("%s: %v\n", s.Name, s.Value)
}

Преимущества:

  • Strongly-typed (Histogram, Counter, Gauge)
  • Стабильный API (vs MemStats fields appearing/disappearing)
  • Больше детальных метрик
/sched/latencies:seconds - histogram задержек шедулера (goroutine wait time)
/gc/pauses:seconds - histogram GC pauses
/gc/cycles/total:gc-cycles - количество GC циклов
/cpu/classes/gc/total:cpu-seconds - CPU time в GC
/memory/classes/total:bytes - total memory
/memory/classes/heap/objects:bytes - heap live objects
/memory/classes/heap/free:bytes - heap free (готово к reuse)
/memory/classes/heap/released:bytes - heap released to OS
/memory/classes/os-stacks:bytes - stacks памяти
/memory/classes/other:bytes - runtime overhead
/sync/mutex/wait/total:seconds - total wait на mutex
/sched/goroutines:goroutines - current goroutine count

Экспортировать в Prometheus:

var goroutinesGauge = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "go_goroutines",
})
go func() {
for range time.Tick(15 * time.Second) {
samples := []metrics.Sample{{Name: "/sched/goroutines:goroutines"}}
metrics.Read(samples)
goroutinesGauge.Set(float64(samples[0].Value.Uint64()))
}
}()

Manual GC trigger. Использовать только для:

  • Debug (получить чистое состояние)
  • Testing memory leaks
  • Возврат памяти OS (с debug.FreeOSMemory)

⚠️ В production — НЕ вызывать regularly. Runtime знает лучше.

import "runtime/debug"
old := debug.SetGCPercent(200) // GOGC=200
defer debug.SetGCPercent(old)

Можно динамически менять. Возвращает старое значение.

debug.SetMemoryLimit(4 * 1024 * 1024 * 1024) // 4GB

Возвращает previous limit. math.MaxInt64 = unlimited.

debug.FreeOSMemory()

Заставляет runtime вернуть память OS (через madvise(MADV_DONTNEED) или madvise(MADV_FREE)).

Когда использовать:

  • После большой batch operation, когда heap shrinks
  • Для измерения “истинного” RSS

В Go 1.13+ runtime автоматически возвращает память через madvise(MADV_FREE) (lazy) — поэтому ручной вызов реже нужен.

До Go 1.19 (без GOMEMLIMIT) использовался ballast pattern для уменьшения GC frequency:

ballast := make([]byte, 10*1024*1024*1024) // 10GB
runtime.KeepAlive(ballast)

Идея:

  • GC trigger = live * (1 + GOGC/100) = live * 2 (при GOGC=100)
  • Если live = 10GB ballast + 100MB real → next GC при 20.1GB
  • GC становится редким → меньше overhead

Twitch case study (2019):

  • Сервис чата на Go, p99 latency скакала из-за частых GC
  • Добавили 10GB ballast
  • Latency упала, GC стало происходить редко
  • Подход стал популярным паттерном

После Go 1.19: заменено на GOMEMLIMIT:

Окно терминала
GOMEMLIMIT=20GiB ./myapp

GOMEMLIMIT делает то же самое более явно и без фиктивных аллокаций.

Default max stack size per goroutine = ~1GB (на 64-bit).

debug.SetMaxStack(64 * 1024 * 1024) // 64MB max stack

Когда уменьшать:

  • Защита от stack overflow при глубоком recursion
  • Limit на user-controllable recursion (avoid runaway)

Когда увеличивать:

  • Очень глубокая recursion (parsers, tree traversal)

Default initial = 8KB (в Go 1.4+ был 8KB, потом 2KB в Go 1.6+, currently ~8KB но варьируется).

Не настраивается через public API.

# Deployment
spec:
template:
spec:
containers:
- name: myapp
image: myapp:v1
resources:
requests:
cpu: "1"
memory: "1Gi"
limits:
cpu: "2"
memory: "2Gi"
env:
- name: GOMEMLIMIT
value: "1843MiB" # 90% от 2Gi
- name: GOGC
value: "100" # или больше, по бенчмарку
- name: GOTRACEBACK
value: "all" # для лучшего debug

В коде:

import (
_ "go.uber.org/automaxprocs"
)
func main() {
// automaxprocs автоматически выставит GOMAXPROCS из cgroup
// ...
}

После запуска проверить:

Окно терминала
# Внутри pod
kubectl exec myapp-xyz -- env | grep GO
kubectl exec myapp-xyz -- ./myapp -version # check GOMAXPROCS log
# Через pprof
curl http://localhost:6060/debug/pprof/cmdline
curl 'http://localhost:6060/debug/vars' | jq .cmdline

В Go-приложении на старте можно вывести:

log.Printf("GOMAXPROCS=%d GOMEMLIMIT=%d GOGC=%d",
runtime.GOMAXPROCS(0),
debug.SetMemoryLimit(-1),
debug.SetGCPercent(-1))

Go runtime в 2026 году имеет partial cgroup awareness:

  • Go 1.16+ — читает cgroup для memory checks (NOT GOMAXPROCS automatically)
  • Полная cgroup-awareness требует automaxprocs или custom code

Для memory:

  • GOMEMLIMIT нужно установить вручную (или через env)
  • В будущем (proposal): automatic detection

cgroup v1 vs v2:

  • v1: /sys/fs/cgroup/cpu/cpu.cfs_quota_us, /sys/fs/cgroup/memory/memory.limit_in_bytes
  • v2: /sys/fs/cgroup/cpu.max, /sys/fs/cgroup/memory.max

В 2026 году большинство k8s clusters используют cgroup v2.

При проблемах с performance — алгоритм диагностики:

Признаки:

  • CPU usage 80-100%
  • Latency drops при добавлении CPU
  • pprof CPU profile показывает hot functions

Действия:

  1. go tool pprof -http=:8080 http://app/debug/pprof/profile?seconds=30
  2. top — топ-10 CPU consumers
  3. list FuncName — annotated source
  4. Оптимизировать или scale horizontally

Признаки:

  • CPU usage low (10-30%)
  • Latency высокая
  • pprof CPU profile — runtime.epollwait, syscall.Syscall в top

Действия:

  1. Проверить network/disk latency
  2. Connection pool size
  3. Возможно нужны больше goroutines (concurrent IO)
  4. Или batching (меньше syscalls)

Признаки:

  • gctrace показывает >25% CPU в GC
  • Heap большой
  • Periodic latency spikes

Действия:

  1. Сравнить inuse_space и alloc_space (allocation rate)
  2. Найти hot allocations (heap profile + zero-alloc patterns)
  3. Увеличить GOGC если throughput > memory
  4. Использовать GOMEMLIMIT в контейнерах

Признаки:

  • CPU low, latency high
  • goroutines blocked высокий
  • mutex profile shows hotspots

Действия:

  1. SetMutexProfileFraction(100) на время
  2. go tool pprof http://app/debug/pprof/mutex
  3. Refactor — sharded maps, RWMutex → Mutex или vice versa
  4. Avoid critical section work (move out of lock)

В low-latency сервисах average не показатель. p99 / p99.9 / max — что важно.

Источники tail latency в Go:

  1. GC pauses — обычно <1ms в Go 1.20+, но возможны spikes
  2. Goroutine scheduling — если много CPU-bound goroutines
  3. Lock contention — несколько мс в worst case
  4. System calls — slow disk, network
Окно терминала
GOGC=50 # Чаще GC, меньше каждая pause
GOMEMLIMIT=4GiB # Не превышать

Или:

Окно терминала
GOGC=200 # Реже GC, но каждая дольше (больше heap to scan)

Trade-off зависит от рабочей нагрузки.

При CPU spinning (busy waiting в шедулере) GOMAXPROCS высокий = меньше latency, но больше CPU. Низкий GOMAXPROCS = больше latency но энергоэффективно.

Для low-latency: GOMAXPROCS = NumCPU().

В Go 1.5+ GC concurrent, STW pause ~100μs. Но assist phase (mutator assist) может занять CPU на user goroutines.

В Go 1.20+ есть улучшения для tail latency:

  • Soft memory limit (GOMEMLIMIT)
  • Pacer improvements
  • Faster mark termination

Go 1.21+ добавил профиль /cpu/classes/scavenge/... — для off-OS memory release.

□ GOMEMLIMIT установлен (90% от container limit)
□ automaxprocs импортирован
□ GOGC по бенчмарку (often default OK)
□ pprof enabled на admin port
□ runtime/metrics экспортированы в Prometheus
□ GODEBUG=gctrace=1 на canary deployments
□ GOTRACEBACK=all для лучшего debug
□ Continuous profiling (Pyroscope/Parca)
□ Goroutine count мониторится
□ p99 latency мониторится
□ Memory growth alerts настроены

В Docker/k8s runtime.NumCPU() возвращает количество CPU на ноде, НЕ container limit. Без automaxprocs — GOMAXPROCS неправильный.

Симптом: в k8s с CPU limit 2, GOMAXPROCS=32 (если нода 32 ядра). Тяжёлые thread contention.

Fix: import _ "go.uber.org/automaxprocs".

GOMEMLIMIT — это soft limit. Runtime пытается удержать, но если не может (GC не освобождает) — программа превышает limit, и kernel может убить через OOM killer.

Fix: GOMEMLIMIT = 90% от container limit, чтобы было buffer.

Окно терминала
GOGC=off ./myapp # GC никогда не запускается → memory bloat

Использовать только с GOMEMLIMIT.

Лимит — на одну goroutine, не на total. 1000 goroutines с 64MB stack — суммарно 64GB potential.

debug.FreeOSMemory блокирует и форсит GC + scavenging. Может занять секунды. Не вызывать на hot path.

limits:
cpu: 2
env:
- name: GOMAXPROCS
value: "32" # ПЛОХО

kernel CFS будет throttle, latency пострадает. Always match cgroup limit.

runtime.ReadMemStats сама STW (короткая) и считается legacy. Использовать runtime/metrics.

В современном Go (1.19+) — GOMEMLIMIT делает то же самое. Если видите ballast в коде — это legacy, можно убрать.

Каждый GC = строка лога. В busy app — много логов. Использовать только для investigation.

Manual runtime.GC() — STW pause + полный GC cycle. Если вызывать часто — latency катастрофа.

Если поставить GOMEMLIMIT=1GiB, реальный heap budget может быть 800-900MB (остальное — stacks, runtime, off-heap).

Code, который читает cgroup, должен поддерживать оба:

// v2
data, _ := os.ReadFile("/sys/fs/cgroup/memory.max")
// v1
data, _ := os.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes")

automaxprocs сам handle оба.

Core dump может быть 10GB+ при больших heap. Только при debug, нужен disk space.

runtime.LockOSThread() фиксирует goroutine к OS thread. Если все goroutines locked — GOMAXPROCS становится upper bound на real parallelism.

После большого heap shrink — runtime не сразу возвращает OS память. Использует MADV_FREE (lazy). RSS остаётся высоким, но kernel может reclaim under pressure.

debug.SetGCPercent(-1) // GC OFF — то же что GOGC=off
debug.SetGCPercent(0) // GC после КАЖДОЙ аллокации (брутально)

Не путать -1 и 0.


Twitch (компания Amazon) описала в блоге:

  • Chat сервис на Go, ~5GB live heap
  • GOGC=100 → GC при 10GB → каждые ~5 минут
  • p99 spike до 100ms из-за GC pause
  • Решение: 10GB ballast. Live heap 5GB + ballast 10GB = 15GB trigger, GC при 30GB
  • GC стало происходить редко, p99 упал до 10ms

В 2026 году: заменили бы на GOMEMLIMIT=30GiB GOGC=100.

Discord (был на Go до перехода на Rust):

  • k8s deployment с CPU limit 4
  • GOMAXPROCS не настроен → default 32 (нода)
  • 100% CPU usage, низкий throughput
  • Добавили automaxprocs → throughput +50%, CPU usage 70% (под limit)

Uber описывали о low-memory сервисах (mobile gateways):

  • Контейнеры 256MB RAM
  • Стандартный Go runtime — частые OOM kills
  • GOMEMLIMIT=230MiB GOGC=200 — больше памяти доступно, GC чаще
  • OOM пропали, latency приемлемая

Cloudflare boundary services:

  • Требование: p99 < 5ms
  • GC pauses ~1-2ms — приемлемо, но spikes до 10ms
  • Tuning:
    • GOGC=50 → чаще GC, меньше pause (~500μs)
    • GOMAXPROCS=NumCPU() (host CPU, single-tenant nodes)
    • Aggressive sync.Pool для buffers
  • Результат: p99 = 3ms стабильно

CockroachDB tuning:

  • GOMAXPROCS = NumCPU() * 1 — full parallelism
  • GOGC тщательно tuned — обычно 80
  • GOMEMLIMIT = 85% от node memory
  • Custom batching → fewer allocations
  • Result: p99 latency reduced 30% over default

Реальный case (hypothetical но реалистичный) для HFT на Go:

  • p99.9 < 100μs требование
  • GC pause = killer
  • Tuning:
    • GOGC=1000 или off — GC очень редко (если ballast/limit)
    • GOMEMLIMIT высокий (32GB+)
    • runtime.LockOSThread() для critical goroutines
    • Все аллокации в zero-alloc patterns
  • Часто комбинируется с CPU pinning (taskset) и isolcpus
  • Большинство HFT всё-таки на C++/Rust, но Go может работать с care

Wikipedia engineering описали тестирование Go service:

  • Production rollout, RSS растёт
  • gctrace показывает: heap stable, но “released” не идёт обратно
  • runtime/metrics: /memory/classes/heap/free:bytes высокий
  • Решение: периодический debug.FreeOSMemory() в idle moments
  • Современный fix: GOMEMLIMIT, runtime сам освобождает aggressivly

  1. Что такое GOGC и какое значение по умолчанию?
  2. Как GOGC=200 влияет на GC и memory?
  3. Что такое GOMEMLIMIT и в какой версии Go появился?
  4. Чем отличается soft limit (GOMEMLIMIT) от hard limit?
  5. Почему GOMEMLIMIT = 90% от container limit?
  6. Что делает комбинация GOGC=off GOMEMLIMIT=...?
  7. Что такое GOMAXPROCS и какое значение по умолчанию?
  8. Почему в k8s runtime.NumCPU() неправильный?
  9. Что делает go.uber.org/automaxprocs?
  10. Чем отличаются cgroup v1 и v2 для CPU limits?
  11. Что такое GOTRACEBACK и какие значения?
  12. Зачем GOTRACEBACK=crash и какие последствия?
  13. Что такое runtime/metrics и чем лучше runtime.MemStats?
  14. Какие ключевые метрики для production мониторинга?
  15. Что делает debug.SetGCPercent?
  16. Что делает debug.SetMemoryLimit?
  17. Что делает debug.FreeOSMemory и когда вызывать?
  18. Что такое memory ballast и почему устарел?
  19. Объясните Twitch ballast case.
  20. Что такое debug.SetMaxStack?
  21. Когда использовать runtime.LockOSThread()?
  22. Как читать GODEBUG=gctrace=1 output?
  23. Какие признаки CPU-bound сервиса?
  24. Какие признаки GC-bound сервиса?
  25. Какие признаки lock contention?
  26. Что такое tail latency и почему важна?
  27. Как уменьшить GC pause impact?
  28. Trade-off: GOGC высокий vs низкий, что лучше для latency?
  29. Какой production checklist для Go в Kubernetes?
  30. Что делать если RSS растёт при стабильном heap?

Запустить Go-приложение с heap-heavy workload (например, web crawler с in-memory caching). Эксперимент:

  • Без GOMEMLIMIT — измерить RSS, GC frequency, latency
  • GOMEMLIMIT=1GiB — что меняется?
  • GOGC=off GOMEMLIMIT=1GiB — как ведёт себя?

Записать выводы.

Развернуть Go-приложение в minikube с CPU limit. Сравнить:

  • Без automaxprocs: посмотреть runtime.GOMAXPROCS(0) в логе
  • С automaxprocs: что выводит
  • Сравнить throughput под нагрузкой (e.g., wrk benchmark)

Запустить production-like приложение с GODEBUG=gctrace=1. Собрать логи 10 минут. Парсить:

  • Среднее время между GC
  • Среднее STW pause
  • % CPU в GC
  • Heap размер до/после GC

Написать tool/script для парсинга gctrace.

Реализовать exporter для Prometheus, экспортирующий ключевые runtime/metrics:

  • /sched/latencies:seconds (histogram)
  • /gc/pauses:seconds (histogram)
  • /memory/classes/heap/objects:bytes
  • /sync/mutex/wait/total:seconds

Создать Grafana dashboard.

Взять реальный workload (HTTP API под нагрузкой). Запустить с GOGC = 50, 100, 200, 400, 800. Замерить:

  • p50, p95, p99 latency
  • Throughput (rps)
  • Memory footprint (RSS)
  • CPU usage

Построить trade-off график.

Найти проект с memory ballast pattern (или сделать самостоятельно). Заменить на GOMEMLIMIT. Сравнить behaviour.

Дано приложение с проблемой production. Определить:

  • CPU-bound? IO-bound? GC-bound? Lock contention?
  • На основе:
    • CPU usage
    • pprof CPU profile
    • gctrace
    • mutex profile
    • goroutine count

Предложить fix.

Создать Kubernetes manifest для Go-приложения с полным production tuning:

  • Resource limits/requests
  • GOMEMLIMIT env
  • GOGC env
  • automaxprocs импорт
  • pprof admin port (sidecar или localhost)
  • Prometheus metrics
  • Liveness/readiness probes

Validation: kubectl describe, env проверка.


  1. GC Guide (Go)https://go.dev/doc/gc-guide — официальный гид по GC и тюнингу.
  2. GOMEMLIMIT proposal & docshttps://go.dev/doc/go1.19#runtime
  3. runtime/metrics packagehttps://pkg.go.dev/runtime/metrics
  4. runtime/debug packagehttps://pkg.go.dev/runtime/debug
  5. uber-go/automaxprocshttps://github.com/uber-go/automaxprocs
  6. Twitch blog: “Go Memory Ballast”https://blog.twitch.tv/en/2019/04/10/go-memory-ballast/
  7. Cloudflare blog on Go runtimehttps://blog.cloudflare.com/tag/go/
  8. CockroachDB perf docshttps://www.cockroachlabs.com/docs/stable/performance-best-practices-overview
  9. Discord engineering bloghttps://discord.com/blog — story почему перешли с Go на Rust (включает tuning attempts).
  10. Dmitry Vyukov, “Go scheduler details”https://dvyukov.github.io/
  11. Russ Cox, “How fast is Go’s runtime?” — research.swtch.com
  12. Cgroup v2 docshttps://www.kernel.org/doc/Documentation/cgroup-v2.txt
  13. Aleksei Sergeev, “Go performance and GC” — talks на GopherCon.