A hierarquia de memória que ninguém te explica direito
A parte mais rápida da casa é o registrador do processador. Não o cache, não a RAM, não o SSD. O registrador fica literalmente dentro do core da CPU e responde em um único ciclo de relógio. A maioria dos tutoriais por aí começa pelo cache L1 como se fosse o topo da pirâmide, mas isso é impreciso e causa confusão na hora de otimizar código.
Qual é a parte mais rápida da casa e por que o resto é lento
A hierarquia, do mais rápido ao mais devagar, funciona assim: Registradores da CPU — acesso em 1 ciclo de relógio. Isso significa que quando seu código faz uma operação aritmética simples, os operandos já estão lá dentro, sem precisar "sair" para qualquer outro lugar.
Cache L1 — dividido em L1d (dados) e L1i (instruções), com latência de cerca de 3 a 4 ciclos. É pequeno, tipicamente 32 a 64 KB por core, mas é onde os dados mais quentes vivem antes de entrar nos registradores. Cache L2 — entre 256 KB e 1 MB por core, latência de 10 a 15 ciclos. Muitos processadores modernos têm L2 dedicado por core, o que é diferente dos primeiros i7 que compartilhavam L2 entre threads.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Cache L3 — compartilhado entre todos os cores, tipicamente de 8 a 96 MB em CPUs consumer atuais. Latência varia de 30 a 50 ciclos, dependendo da arquitetura e de quantos você precisa atravessar para chegar ao dado. RAM — aqui a coisa despenca. DDR4 opera na casa dos 50 a 100 nanosegundos de latência, o que equivale a aproximadamente 150 a 300 ciclos de relógio em um processador de 3 GHz. DDR5 melhora um pouco, mas ainda está ordens de grandeza abaixo do cache.
SSD e HDD — latências na casa dos microssegundos a milissegundos. NVMe consegue algo em torno de 50 a 100 microssegundos de latência de leitura aleatória, enquanto HDDs podem levar 5 a 10 milissegundos só para fazer seek. Eu trabalhei num projeto de otimização de um engine de renderização onde o gargalo não era a GPU, como todo mundo presume. Era o acesso à memória. Os vértices eram carregados da RAM direto pra rotina de transformação, e cada draw call sofria cache miss em L1 porque os dados eram espalhados de forma não consecutiva na memória. A solução foi implementar um SoA (Structure of Arrays) em vez de AoS, alinhando os dados para que o prefetcher do hardware conseguisse e pré-carregar os blocos certos. Isso reduziu o tempo médio de processamento de vértices de 2,3 microssegundos para 0,4 microssegundos por frame. Sem tocar no shader, sem mudar a GPU, só reorganizando a disposição dos dados na memória.
O que poucos desenvolvedores entendem é que o cache não funciona como um armário onde você coloca e pega coisas aleatoriamente. Ele opera com linhas de cache, tipicamente de 64 bytes. Quando um dado não está no cache, o hardware não busca apenas aquele byte ou aquella palavra — ele carrega um bloco inteiro de 64 bytes. Isso significa que o spatial locality importa mais do que você imagina. Acessar um array sequencialmente é drasticamente mais rápido do que acessar o mesmo array com stride, mesmo que o número total de acessos seja idêntico. Outro ponto que causa problemas é o false sharing. Duas threads em cores diferentes podem estar modificando variáveis que moram na mesma linha de cache, mesmo que sejam variáveis completamente independentes. O cache coherency protocol vai fazer as linhas invalidarem umas às outras a cada escrita, transformando um problema que deveria ser paralelo em um gargalo sequencial. Já vi isso acontecer em uma simulação física onde cada thread processava um pedaço diferente da cena, mas as estruturas de resultado estavam alinhadas de forma que variáveis vizinhas caíam na mesma linha de cache. A solução foi adicionar padding entre as estruturas para forçar o alinhamento em linhas de cache diferentes.
A desvantagem óbvia dessa hierarquia é que ela exige que o desenvolvedor pense em termos de como os dados são acessados, não apenas em termos lógicos. Um programador que escreve código considerando apenas a correção funcional frequentemente gera padrões de acesso que fazem a CPU passar a maior parte do tempo esperando dados da RAM, enquanto os cores permanecem subutilizados. O perfilamento com ferramentas como perf, VTune ou EvenMix é essencial, mas mesmo assim existem edge cases difíceis de capturar. Por exemplo, o Intel Ice Lake e o AMD Zen 4 têm comportimentos de prefetch diferentes, e otimizações que funcionavam perfeitamente em uma geração podiam degradar o desempenho na seguinte. Se você está começando e quer aplicar isso na prática, não tente adivinhar onde estão os gargalos. Use um profiler. Measure primeiro, otimize depois. A intuição quase sempre falha porque o comportamento do cache é contra-intuitivo por design — o que parece inofensivo no código fonte pode ser catastrófico no hardware.