antonio leandro

estruturas e algoritmos

ARC: A Self-Tuning, Low Overhead Replacement Cache

paper · Nimrod Megiddo, Dharmendra S. Modha · · ~59 min de leitura do original

a tese

dá para ter recência e frequência sem escolher entre as duas: duas listas lru, um catálogo fantasma do tamanho do cache e uma regra de aprendizado que move a fronteira entre elas a cada miss

o que fica

  1. O ARC guarda metadados do dobro de páginas que cabe no cache: os hits nas listas fantasma, de páginas já expulsas, são o sinal que diz para que lado a fronteira entre recência e frequência deve andar.
  2. Toda a adaptação cabe em um escalar p, o tamanho-alvo da lista de recência — configurar o cache virou ajustar um número que o próprio algoritmo mexe a cada miss.
  3. Resistência a scan sai de graça do desenho: página nunca vista entra na lista de recência e só chega à de frequência se for pedida de novo antes de ser expulsa, então uma leitura sequencial longa atravessa o cache sem lavá-lo.
  4. Não existe parâmetro fixo bom: no trace P12, o melhor CIP do LRU-2 com 1.024 páginas rende 4,07% de hit ratio e o pior 0,87%, mas com 524.288 páginas a ordem se inverte (63,15% contra 60,82%).
  5. Adaptar sem parar cobra um preço em workload estável: no P8 o ARC fica em 27,51% contra 28,92% da política de fração fixa com o melhor p escolhido offline, porque ele nunca trava no valor certo e fica oscilando em volta dele.
  6. Hit ratio alto não paga overhead logarítmico: na tabela de tempos do paper, ARC e LRU ficam na casa dos 12 a 17 segundos e o LRFU chega a 554 segundos no mesmo trace.

o problema

Cache tem duas religiões. LRU aposta em recência e é ótimo quando o workload segue o modelo de stack depth distribution; é barato, é O(1) e responde rápido a mudança. Mas não enxerga frequência: uma varredura sequencial de uma tabela inteira empurra para fora todas as páginas quentes, e nenhuma delas será usada de novo. LFU aposta em frequência e é ótimo sob o modelo de referência independente, mas exige fila de prioridade, custa logarítmico no tamanho do cache e acumula páginas velhas com contador alto que ninguém mais pede.

Nos anos anteriores a 2003, a área tentou costurar as duas coisas: FBR, LRU-2, 2Q, LIRS, LRFU. Todos funcionam. Todos vêm com knob. E o paper mostra que o knob é o problema: no trace P12, o parâmetro CIP do LRU-2 com cache de 1.024 páginas dá 4,07% de hit ratio no melhor ajuste e 0,87% no pior; com 524.288 páginas, o ajuste que era o pior vira o melhor (63,15% contra 60,82%). Um valor bom para um cache é ruim para outro tamanho do mesmo cache, no mesmo trace. Os autores do 2Q admitiram que a fórmula deles para o parâmetro exige estimar a miss rate de antemão e “é de pouca utilidade prática”. Some a isso o custo: no trace de uma workstation, LRU e ARC ficam entre 12 e 17 segundos de book-keeping, LRU-2 dobra isso e LRFU chega a 554 segundos.

a ideia

Em vez de escolher entre recência e frequência, mantenha duas listas LRU — uma de páginas vistas uma vez recentemente, outra de páginas vistas pelo menos duas vezes — e trate a fronteira entre elas como a única decisão. O cache tem c páginas; o catálogo lembra 2c. As páginas que saíram do cache continuam no catálogo como fantasmas, sem dado, só metadado.

Fantasma serve para uma coisa: reclamar. Se chega uma requisição para uma página que está no fantasma da lista de recência, isso é prova de que a parte de recência estava curta demais, e o alvo cresce. Se o hit é no fantasma da lista de frequência, o alvo encolhe. O algoritmo investe na lista que está performando melhor, e o tamanho do passo depende de quão desequilibradas as duas listas de fantasma estão: quanto menor a lista que acertou, maior o ajuste. O efeito acumulado é um passeio aleatório do parâmetro, guiado pelo próprio workload.

como funciona

# T1/T2 no cache (c páginas); B1/B2 são fantasmas (só metadado, mais c)
# p = tamanho-alvo de T1, começa em 0

on request(x):
  if x in T1 or x in T2:              # hit
      move x -> MRU(T2)

  elif x in B1:                       # miss: recência estava curta
      p = min(c, p + max(1, |B2|/|B1|))
      replace(x); move x -> MRU(T2)

  elif x in B2:                       # miss: frequência estava curta
      p = max(0, p - max(1, |B1|/|B2|))
      replace(x); move x -> MRU(T2)

  else:                               # página nunca vista
      poda L1/L2 para caber em 2c; replace(x)
      insere x -> MRU(T1)

replace(x):
  if T1 não vazio and (|T1| > p or (x in B2 and |T1| == p)):
      LRU(T1) -> MRU(B1)              # sai do cache, fica o fantasma
  else:
      LRU(T2) -> MRU(B2)

Duas listas encadeadas, um inteiro, nenhuma fila de prioridade, nenhum contador de frequência para reescalar periodicamente. Custo constante por requisição, no pior caso — não na média.

o que isso custou

O catálogo dobra. Para cachear c páginas você mantém metadado de 2c, e isso é espaço que o LRU não gasta.

O modelo é estreito de propósito: demand paging, páginas de tamanho uniforme, sem prefetch, e a única métrica é hit ratio. Não há noção de miss caro e miss barato, nem de objeto de tamanho variável — nas medições de overhead os autores assumem explicitamente que um miss não custa nada além de um hit, para isolar o book-keeping.

Adaptar sem parar tem preço. Em workload estável existe um p quase ótimo, e o ARC nunca fica parado nele: oscila em volta. No trace P8 isso custa 27,51% contra 28,92% da política de fração fixa com o melhor p offline. O ganho aparece quando o workload muda de regime: no P4, onde o alvo percorre toda a faixa entre 0 e o tamanho do cache, o ARC online faz 11,24% e a melhor fração fixa offline faz 9,11%.

A universalidade é empírica, não provada — os autores tomam o termo emprestado da compressão de dados e o sustentam com traces, não com teorema. E a distância para o ótimo offline continua enorme: no OLTP com 1.000 páginas, ARC faz 38,93% e o MIN de Belady, que enxerga o futuro, faz 53,61%. A variante com histórico extra, ARC(x), perde a garantia de tempo constante no pior caso por causa da poda; os autores medem só a versão sem ela. A regra de desempate quando a lista está exatamente no alvo eles próprios chamam de arbitrária.

onde isso aparece hoje

A dupla lista-fantasma mais knob que se ajusta sozinho virou vocabulário: hoje se fala em ghost list sem citar a fonte. O cache de leitura do ZFS se chama ARC por causa deste paper.

A linhagem seguiu por dois caminhos opostos. Um insiste que a adaptação pode ser mais esperta e aprendida de dados, como em Cold-RL, que troca o escalar por uma política treinada com reinforcement learning offline. O outro desconfia da complexidade toda: SIEVE volta a uma lista só e argumenta que, para cache web, simplicidade rende mais que adaptatividade. A hipótese de página de tamanho uniforme, que aqui é premissa declarada, é justamente o que Segcache precisa demolir para lidar com objetos pequenos e heterogêneos. E o eixo espaço-versus-desempenho que o catálogo dobrado exemplifica é o que a conjectura RUM depois formalizou como escolha inescapável de projeto.

lido na íntegra por pipeline de llm, revisado por antonio leandro antes de publicar ·