Uma pausa de 40 ms no garbage collector do Go causada por swap
Um caso de pausas stop-the-world de 40 ms em Go, atribuídas ao garbage collector tocando metadados que haviam sido movidos para swap, provoca um escrutínio mais amplo de como o GC interage com a pressão de memória do sistema operacional. Comentadores debatem se sistemas de baixa latência devem evitar totalmente linguagens com GC, desativar swap ou usar técnicas como `mlock`, coletores cooperativos ou cientes de swap, e algoritmos alternativos como GC on-the-fly ou reference counting. A discussão destaca um trade-off entre throughput e latência, e sugere que tanto o design do runtime quanto as APIs em nível de SO moldam significativamente o comportamento real das pausas.
Projeto de GC do Go e alternativas “on-the-fly”
- O GC do Go já usa um GC concorrente de mark-sweep com tempos de pausa típicos muito curtos.
- Alguns perguntam por que o Go não adota algoritmos clássicos de GC “on-the-fly” (por exemplo, do tipo DLG) que visam pausas zero de stop-the-world (STW).
- Outros observam que a equipe do Go provavelmente conhece esses algoritmos; trade-offs entre latência e throughput, além da complexidade de varredura de stack/corrotinas, podem explicar as escolhas atuais.
- O algoritmo alternativo on-the-fly do Fil-C é mencionado como potencialmente relevante para o modelo de corrotinas do Go.
Swap, page faults e picos de latência
- O problema central: quando metadados do GC ou páginas do heap são removidos para swap, seções STW podem acumular muitas page faults, transformando pressão de memória em grandes picos de latência.
- Alguns argumentam que, se você se importa com latência, deve desativar o swap; outros contrapõem que isso apenas desloca as paradas para páginas de código e pode provocar OOM em vez disso.
- São dadas explicações de como o kernel expulsa páginas anônimas versus páginas apoiadas por arquivo; desativar o swap não impede paging, apenas muda o que é expulso.
- Mitigações sugeridas:
mlock/mlockallpara regiões críticas ou metadados, uso cuidadoso de cgroups e swappiness, ou aceitarOOM-killcomo algo preferível a tempestades de swap.
Reference counting vs GC por tracing
- Vários participantes afirmam que reference counting (RC) oferece desempenho mais previsível; outros discordam, citando:
- Desativações em cascata podem durar arbitrariamente muito.
- Alto overhead, especialmente com RC atômico em programas multithread.
- Problemas com ciclos e NUMA/travamento.
- Alguns preferem esquemas híbridos ou RC diferido; outros enfatizam que décadas de experiência da indústria mostram que o RC puro tem trade-offs significativos.
Cooperação entre SO e GC e designs cientes de swap
- Ideias levantadas:
- GC ciente de swap que faz prefetch ou trava páginas usadas em fases STW.
- Uso de APIs existentes como
mlock,madvise,mincoreou toque de página para gerenciar residência. - Cooperação mais profunda entre SO e GC (por exemplo, bookmark collectors, gerenciamento cooperativo de memória, GCs baseados em regiões coordenando com
madvise).
- Alguns veem isso como uma oportunidade perdida de pesquisa/prática; outros duvidam que uma integração tão profunda apareça amplamente tão cedo.
Escolha de linguagem e requisitos de latência
- Vários comentários tratam isso como mais um dado: use Go quando a latência ocasional do GC for aceitável; escolha Rust/C++ (RAII/controle manual) para necessidades estritas de baixa latência.
- O caso do Discord é citado: mesmo com pouco lixo, um grande grafo de objetos mais GC periódico pode causar latência de cauda inaceitável, motivando uma reescrita em uma linguagem sem GC.