Você ouve o tempo todo que acesso à memória é O(1). É uma mentira útil. Entre o registrador e o disco tem 6 ordens de grandeza de latência, e o cache decide quase tudo. Roda os padrões de acesso aqui embaixo e olha o hit rate despencar quando você perde a localidade.
Cada linha aqui é uma cache line (carrega de uma vez). O cache só segura 3 linhas por vez (LRU). Escolha o padrão e rode: olha quem fica verde (hit) e quem fica vermelho (miss).
na RAM (fora do cache)linha no cachehitmiss
A CPU nunca lê 1 byte da RAM. Ela puxa uma cache line inteira (64 bytes) de uma vez. Se você lê em sequência, os vizinhos já vieram junto e saem de graça (localidade espacial). Se você pula pela memória, cada acesso é um miss e paga ~100x mais. É por isso que array contíguo ganha de lista encadeada na vida real, mesmo com o mesmo Big-O.
Nível
Latência
~Tamanho
Registrador
<1 ns
~ bytes
L1
~1 ns
~64 KB
L2
~4 ns
~1 MB
L3
~15 ns
~32 MB
RAM
~100 ns
~GBs
SSD
~100 µs
~TBs
Disco
~10 ms
~TBs
Loop cache-friendly vs não (Go)
// rápido: percorre na ordem da memória (row-major)
sum := 0for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
sum += m[i][j] // contíguo: cache hit
}
}
// lento: pula uma linha inteira a cada passofor j := 0; j < n; j++ {
for i := 0; i < n; i++ {
sum += m[i][j] // stride = n: cache miss toda hora
}
}
Mesmo algoritmo, mesmo O(n²). Só trocando a ordem dos loops a versão de cima costuma rodar várias vezes mais rápido. O relógio mede cache; o Big-O não.
🧠 Desafio — Memória & Cache
Roda os padrões de acesso aqui de cima antes de responder. As duas últimas são de reflexão: escreve a sua e só então revela o modelo.
🔧 Prática — ache o bug
Os dois loops são O(n²), mas um é ~10x mais lento. Clica na linha que mata o cache.