Estágio 01 · 01-14
Locked01-01 cobriu modelo de computação de alto nível. Mas decisões reais de performance, por que esse loop é 10x mais lento, por que o profiler aponta cache misses, por que threads parecem rápidas em um core e lentas em outro, exigem entender CPU por dentro. Pipelining, branch prediction, out-of-order execution, store buffers, MESI, NUMA não são tópicos acadêmicos: governam latência de toda linha de código que você escreve.
Engenheiro Pleno geralmente trata CPU como caixa-preta. Senior+ que trabalha com performance, sistemas, baixo nível, ou ML inference precisa entender. Sem isso, profiler é caixa de caracteres incompreensível, e otimizações ficam superstição. Com isso, você lê flamegraph e sabe por quê sua hot loop não vetoriza.
Este módulo é mecânico-simpático: arquitetura Intel/ARM moderna, hierarquia de cache (L1/L2/L3, TLB), branch prediction, speculative execution, prefetching, SIMD, NUMA, store buffers, memory ordering em x86 vs ARM (conexão 01-11), memory consistency. Plus PMU (performance monitoring units) e como usar perf pra ler counters reais.
CPU clássica fetch → decode → execute → memory → writeback (5-stage RISC pipeline). Modernas têm 14-20+ stages.
Pipelining permite múltiplas instruções "em voo" simultaneamente. Throughput melhora; latência por instrução individual permanece similar.
Hazards:
Compilador e CPU coordenam pra reduzir hazards (instruction scheduling, register renaming).
CPUs modernas fazem out-of-order execution: reordenam instruções dinamicamente pra preencher pipeline. Reorder Buffer (ROB) commit em ordem original.
Superscalar: múltiplas instruções por ciclo via múltiplas execution units (ALUs, FPUs, load/store ports). x86 modern (Intel Golden Cove) faz 6+ instruções/ciclo em hot path.
Implicação: ILP (Instruction-Level Parallelism) é grátis se compilador e código permitem.
Branches custam ciclos se misspeculados (pipeline flush). CPU prediz direção via:
Misprediction = 10-20 ciclos perdidos em pipelines profundos. Reduce com:
std::min, ternary, bitops).Spectre (2018): exploit de branch predictor pra ler memória além de bounds. CPU specula past bounds check; embora rollback, traces ficam em cache. Mitigation patches custaram 5-30% performance.
Latency aproximada (Intel x86):
Cache organizada em lines (64 bytes x86). Set-associative (8-16 ways em L1).
Implicação: leitura de byte custa 64 bytes. Loop em array sequencial (stride 1) = quase free. Random access = penalty.
Profile reveal misses via perf stat -e cache-misses.
Virtual → physical address translation cached em TLB. Miss = page table walk (custo significativo).
Page sizes: 4KB default, 2MB / 1GB huge pages disponíveis. Huge pages reduzem TLB pressure pra workloads com large memory.
madvise(MADV_HUGEPAGE) em Linux. JVM, ClickHouse, Redis suportam huge pages.
Hardware prefetcher detecta padrões (stride) e busca cache lines antes de uso. Funciona pra acessos lineares.
Software prefetch: _mm_prefetch (intrinsic), __builtin_prefetch (GCC). Útil em estruturas com indireção (linked list, hash table).
Registers wide (128/256/512 bit) processam múltiplos valores. Sets x86: SSE, AVX, AVX2, AVX-512. ARM: NEON, SVE.
Auto-vectorization: compiladores tentam. Hand-written via intrinsics (_mm256_add_ps).
Speedup 4-16x em loops vetorizáveis (numeric, hashing, image, audio, ML inference). Wide use em libs (BLAS, SIMD-JSON, ClickHouse vectorized engine).
Two logical threads compartilham um core físico. Compartilham execution units; cada um seu register file e arquivo de estado.
Ganhos: ~20-30% em workloads heterogêneos (memory-bound thread libera ALUs pro outro). Não 2x.
Em workloads CPU-bound puros, SMT pode degradar. Em containerized, garbage collection threads se beneficiam.
x86 TSO (Total Store Order): stores não reordenam entre si; loads não reordenam entre si; mas store-then-load podem (StoreLoad).
ARM/POWER: relaxed; quase tudo pode reordenar. Programador insere barriers (DMB, DSB).
Implicação prática: código C++ que "funciona" em x86 frequentemente quebra em ARM se sync primitives mal usadas. Migrações cloud x86 → Graviton ARM expuseram bugs latentes.
Stores não vão direto a cache. Store buffer (10-50 entries) coalesce e escreve em batch.
Implicação: thread A store, thread B load same address, A pode estar no store buffer, B vê stale do cache. (Origem do exemplo §2.2 01-11.)
Servers multi-socket: cada socket com memory controller dedicado. Acesso a memory de outro socket via interconnect (UPI / Infinity Fabric) = penalty.
NUMA-aware:
Apps single-socket ignoram. DBs grandes (Postgres, ClickHouse, Cassandra) têm tuning NUMA.
perfCada core tem counters de hardware:
perf stat -d ./program: básico. perf record -F 99 -g; perf report: profiling.
perf top: live view.
Linux perf é canônico. Outros: VTune (Intel), AMD μProf, eBPF (Brendan Gregg's work).
Plot performance (FLOPS) vs arithmetic intensity (FLOPS/byte memory):
Use pra entender se workload está saturando memory ou compute. ML inference frequentemente memory-bound (large weights); ML training compute-bound (FLOPs heavy).
GPU é 1000s de cores SIMT (Single Instruction, Multiple Threads), high latency tolerated via massive parallelism.
CPU optimized for low-latency single thread + small parallelism.
Workloads diferem. ML training = GPU. Database OLTP = CPU. Mix = consciência arquitetural.
Modern CPUs throttle clocks sob calor. Boost clock breve > sustained clock. Server-room workloads sustained.
Energy: dynamic + static. Race-to-idle (rapid burst then sleep) frequently mais eficiente que sustained low.
ARM em mobile/cloud: melhor perf/watt. Datacenter shifts pra ARM (AWS Graviton, Azure Cobalt, GCP Axion) por economics.
Práticas concretas:
__builtin_expect, [[likely]]/[[unlikely]] C++20).Código que respeita arquitetura pode ser 10-100x mais rápido sem mudar algoritmo.
Sample command:
perf stat -e cycles,instructions,cache-misses,branch-misses,L1-dcache-load-misses ./mybench
Output útil:
PMU é fonte de verdade sobre o que CPU está fazendo. Profiling sem PMU é guess.
Você precisa, sem consultar:
perf stat mentalmente: que counters indicam o quê.Microbenchmarks instrumentados com PMU revelando cache, branch, NUMA effects.
Linguagem: Rust ou C (acesso direto a intrinsics).
if a[i] > threshold then x else y.f32. Versão escalar vs auto-vectorized vs intrinsics AVX2.analysis.md:
perf stat outputs.perf (Linux) ou Instruments (macOS) ou similar.-O2/-O3 -march=native).perf docs, eBPF docs.