awesome-everything EN
↑ Обратно к восхождению

Производительность

Cache-oblivious алгоритмы, PGO и production failures

Суть Cache-oblivious рекурсия выигрывает на всех уровнях cache одновременно. PGO bake-ит branch layout в binary для 10–30% free speedup. Production failures от Discord/Mojang/Bun — одна форма: ''''маленький'''' code change тихо пересёк cache-line boundary.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 18 min

Rust struct рефактор переместил один горячий field в конец 96-байтного struct. Никакого изменения алгоритма. Никакой новой работы. p99 latency на message-render пути вырос на 40%. Production outage, замаскированный под code cleanup.

Cache-oblivious алгоритмы

Cache-oblivious алгоритм performs well на каждом cache level — L1, L2, L3 — без знания конкретных размеров. Канонический пример — recursive blocked matrix multiply.

Наивный three-nested-loop matrix multiply имеет плохое cache behaviour, потому что inner loop exhausts cache level перед тем, как переходит к следующему. Recursive blocked версия делит матрицу на 4 sub-matrices и рекурсирует до base case. На каждом recursion level активная sub-matrix влезает в какой-то cache level естественно, хотя алгоритм не знает, в какой именно. Hardware adapts.

Тот же принцип применяется к:

  • Cache-oblivious mergesort: рекурсивное разделение пополам держит active data в наименьшем доступном cache level.
  • Cache-oblivious B-trees: van Emde Boas layout располагает уровни дерева так, что каждое half-tree влезает в cache level — нет pointer chasing за пределы level.
  • FFT: recursive Cooley-Tukey естественно выравнивает butterfly groups с cache lines.
  • Matrix transposition: наивный transpose cache-hostile; recursive blocked transposition достигает хорошей spatial locality на всех уровнях.

2–4x speedup над naive iteration часто доминирует cost любой сложности рекурсии.

Profile-guided optimisation (PGO)

Компиляторы улучшаются кардинально при наличии runtime frequency данных. Процесс:

  1. Build с instrumentation (-fprofile-generate / clang -fprofile-instr-generate).
  2. Run representative workload — это заполняет .profdata файл.
  3. Rebuild с profile (-fprofile-use / -fprofile-instr-use).

Что PGO меняет:

  • Inlining decisions: inline hot callees, не cold. Без PGO компилятор inline-ит по code size heuristics и часто ошибается.
  • Branch layout: predicted-taken branches размещаются последовательно в памяти. Не-taken branches прыгают вперёд. Это держит hot path contiguous в instruction cache.
  • Basic block reordering: hot basic blocks группируются в соседних pages, улучшая I-cache hit rate для common path.
  • Function placement: functions, вызываемые вместе, размещаются рядом в binary — меньше I-TLB miss-ов.

Real workload speedups от PGO:

  • Chrome: 10–15%.
  • Firefox: 12–18% на JIT warmup.
  • Postgres: 8–15% на OLTP benchmarks.
  • Linux kernel (BOLT post-link optimisation): 3–8%.

PGO cost: build pipeline complexity и необходимость representative profiling workload. Profiling workload должен соответствовать production traffic — toy benchmark даёт плохие profiles и может ухудшить производительность.

Real production failures

Все три failure-а имеют одну форму: «маленький» code change переместил байты без изменения алгоритма. CPU заметил.

Discord 2023 — struct field reorder. Rust struct рефактор переместил горячий field (message_timestamp) с byte offset 0 на byte offset 72 в 96-байтном struct. Field пересёк cache-line boundary. Каждый render message list — горячий path в client — теперь загружал две cache lines вместо одной для этого field. p99 latency на message-render path вырос 40%. Rollback подтвердил причину. Фикс: переупорядочить struct fields так, чтобы горячие fields жили в первых 64 байтах (одна cache line). Правило: профилируй struct access patterns и ставь top-3 most-read fields первыми.

Mojang Minecraft Java Edition 2018 — HashMap → contiguous array. Внутренний entity tracker использовал HashMap<EntityID, EntityData> для хранения всех world entities. Entity simulation (movement, AI, collision) обращалась к этим entries в tight game-tick loop. HashMap-ные chained buckets разбрасывали EntityData structs по heap — каждый entity lookup был pointer chase в random allocation. Замена entity store на contiguous array (Entity Component System, ECS) дала 3–5x throughput на game-tick loop. Все entity component data стала sequential; prefetcher загружал следующую entity данные, пока CPU обрабатывал текущую. Discord 2024 game-server расширил этот паттерн на SIMD-aware ECS structs: ещё 2x на определённых simulation paths.

Bun 2024 — startup cold-path regression. Новая dependency добавила HashMap, инициализируемый при module load time. Из-за allocation order во время JIT warmup, backing array HashMap-а находился в середине hot-code region, evict-уя frequently-used JIT-compiled functions из L1 во время startup. Симптом — 30 ms startup regression, незаметный в micro-benchmarks, видимый в automated startup regression tests. Фикс: lazy-init HashMap-а (создать при первом использовании, не при module load). Паттерн: всё, аллоцированное на cold path и растущее со временем, может засорять cache для hot path. Lazy-init — стандартная митигация.

Структурный паттерн. Во всех трёх случаях:

  1. «Рефактор» изменил byte offsets или allocation timing — алгоритм идентичен.
  2. Hot path тихо пересёк cache-line или TLB boundary.
  3. Regression появился как p99 growth или throughput drop, не crash или obvious error.
  4. Root cause потребовал perf c2c или perf stat; code review бы не поймал.

Observability для cache и branch производительности

Базовый perf stat вызов:

perf stat -e \
  cache-misses,cache-references,\
  branch-misses,branches,\
  cycles,instructions \
  -- ./your_binary
МетрикаНормаПорог проблемыВероятная причина
IPC (instructions / cycles)2.0–4.0<1.0Memory stalls или branch mispredicts
L1 miss rate<1%>10%Плохая spatial/temporal locality
LLC miss rate<5%>20%Working set превышает L3 или pointer chasing
Branch miss rate1–2%>5%Непредсказуемый data-dependent branch в hot path

Дополнительные инструменты:

  • perf record + perf annotate: sample hot lines с source annotation.
  • perf c2c: cache-line contention между cores — прямой инструмент для false sharing.
  • Intel VTune / AMD uProf: per-line attribution, memory access patterns, NUMA topology view.
  • BCC cachestat / llcstat: BPF-based, zero-instrumentation, production-safe sampling.
  • ARM PMU + Apple Instruments: те же метрики на ARM; Instruments имеет “CPU Counters” template с MEMORY_CYCLES и LAST_LEVEL_CACHE_MISS.

Performance-engineering loop:

  1. Measure с perf stat — IPC, miss rates, branch miss rate.
  2. Identify dominant cost: cache miss (layout problem), branch miss (prediction problem), low IPC from bandwidth (data volume problem), low IPC from instruction issue (SIMD opportunity).
  3. Matching fix: layout → SoA / struct split; branches → sort / branchless; bandwidth → non-temporal stores / compression; SIMD → AoS-to-SoA + autovectorise.
  4. Re-measure — подтвердить улучшение, проверить новый bottleneck.
Почему это работает

Big-O остаётся правильной моделью для scalability с N. Молчит о constant factors, варьирующихся в 100x с layout. Production reality: constants доминируют well into millions для многих problem sizes. Performance-engineering loop начинается с measurement, не с algorithm selection.

Senior-tier cache и CPU числа
TLB miss page walk
5–50 циклов
Huge page (x86)
2 MB / 1 GB
L1 cache associativity
типично 8-way
MLP outstanding misses
8–16 типично
PGO typical speedup
10–30%
Non-temporal store bandwidth gain
20–30% streaming
Apple M3 pipeline depth
~10 stages
DDR5-6000 bandwidth
~50 GB/s per channel
Проследи
1/5

Perf-критичный сервис имеет IPC 0.6 и L3 miss rate 30%. Трасса пути оптимизации.

1
Step 1 of 5
Шаг 1: IPC 0.6, L3 miss 30%. Что это значит?
2
Locked
Шаг 2: куда смотреть первым?
3
Locked
Шаг 3: hot функция работает на vector<MyStruct>, где MyStruct 200 байт (mix hot и cold fields). Диагноз?
4
Locked
Шаг 4: после splitting, IPC до 1.5. Всё ещё memory-bound. Следующий шаг?
5
Locked
Шаг 5: долгосрочное управление?
Найди ошибку

Диагностируй perf stat output — какой bottleneck?

log
Performance counter stats for './hot_loop':

     5,238,194,672      cycles
     2,094,127,491      instructions             #  0.40 insn per cycle
       413,824,917      branches
       105,209,832      branch-misses            # 25.42% of all branches
       684,283,194      cache-references
       287,401,952      cache-misses             # 41.99% of all cache refs
        12,498,373      LLC-loads
         5,927,491      LLC-load-misses          # 47.43% of all LL-cache hits

         2.851 seconds time elapsed

IPC 0.40, branch miss 25%, cache miss 42%, LLC miss 47%. Какой dominant bottleneck и как фиксить?

Выбери лучший вариант

Выбери data structure для perf-критичной lookup table с 1М entries, mostly reads, occasional inserts, no range queries.

Викторина

Почему hardware prefetcher fails на graph traversals, даже когда граф влезает в L2?

Какой RFC?

Какая спецификация определяет cache-coherency протоколы (MESI / MOESI), используемые современными multi-core x86 CPUs?

Спроектируй

Спроектируй data layer для real-time analytics сервиса: 100М rows telemetry data, p99 query latency под 10 мс, 99% read, 1% write, mostly aggregation queries над column subsets.

  • Working set: 100M rows × 200 байт = 20 GB.
  • Server имеет 256 GB RAM, dataset влезает в память.
  • Большинство queries трогают 1–3 column из 20.
  • Нужна поддержка compressed-on-disk + uncompressed-in-RAM.
  • Throughput target: 100k queries/sec.
Вспомните перед уходом
  1. 01
    Объясни, как cache coherency (MESI/MOESI) создаёт 'false sharing' как multi-thread performance баг, и как детектировать и фиксить.
  2. 02
    'Быстрый' O(log N) алгоритм performs хуже 'медленного' O(N) в production. Список диагностических шагов для подтверждения, что cache-locality — причина.
Итог

Cache-oblivious алгоритмы (recursive blocked matrix multiply, van Emde Boas trees, recursive mergesort) достигают хорошего cache behaviour на всех уровнях без знания их размеров. PGO превращает representative runtime profile в inlining decisions, branch layout и function placement — типично 10–30% speedup без изменения кода. Реальные production failures — одна форма: struct reorder (Discord, +40% p99), HashMap вместо contiguous arrays (Mojang, 3–5x throughput drop), cold-path HashMap засоряет cache во время warmup (Bun startup regression). Observability loop: perf stat для IPC и miss rates, perf c2c для false sharing, perf record + annotate для per-line attribution, BPF tools (cachestat, llcstat) в production. IPC ниже 1.0 — memory-bound; branch miss rate выше 5% — непредсказуемый горячий branch. Fix matches the bottleneck: layout для cache misses, sort/branchless для branches, non-temporal stores для streaming bandwidth.

Связанные уроки
встречается в167
Продолжить восхождение ↑Cache vs big-O: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.