Una pausa de 40 ms del recolector de basura de Go causada por swap

Un caso de pausas stop-the-world de 40 ms en Go, atribuido al recolector de basura al tocar metadatos que habían sido enviados a swap, lleva a un escrutinio más amplio de cómo el GC interactúa con la presión de memoria del sistema operativo. Los comentaristas debaten si los sistemas de baja latencia deberían evitar por completo los lenguajes con GC, desactivar swap o usar técnicas como `mlock`, recolectores cooperativos o conscientes de swap, y algoritmos alternativos como el GC on-the-fly o el conteo de referencias. El intercambio pone de relieve un compromiso entre rendimiento y latencia, y sugiere que tanto el diseño del runtime como las APIs a nivel de sistema operativo influyen de manera significativa en el comportamiento real de las pausas.

Diseño del GC de Go y alternativas “on-the-fly”

  • Go ya usa un GC concurrente de marcado y barrido con tiempos de pausa típicos muy cortos.
  • Algunos preguntan por qué Go no adopta algoritmos clásicos de GC “on-the-fly” (por ejemplo, de estilo DLG) que apuntan a pausas stop-the-world (STW) de cero.
  • Otros señalan que el equipo de Go probablemente conoce estos algoritmos; los compromisos entre latencia y rendimiento, y la complejidad del escaneo de pilas/corutinas, pueden explicar las elecciones actuales.
  • Se menciona el algoritmo alternativo on-the-fly de Fil-C como potencialmente relevante para el modelo de corutinas de Go.

Swap, fallos de página y picos de latencia

  • El problema central: cuando los metadatos del GC o las páginas del heap se sacan a swap, las secciones STW pueden acumular muchos fallos de página, convirtiendo la presión de memoria en grandes picos de latencia.
  • Algunos argumentan que, si te importa la latencia, deberías desactivar swap; otros responden que eso solo desplaza las detenciones a las páginas de código y puede provocar OOM en su lugar.
  • Se explican formas en que el kernel expulsa páginas anónimas frente a páginas respaldadas por archivos; desactivar swap no detiene la paginación, solo cambia qué se expulsa.
  • Mitigaciones sugeridas: mlock/mlockall para regiones o metadatos críticos, uso cuidadoso de cgroups y swappiness, o aceptar que el OOM-kill es preferible a las tormentas de swap.

Reference counting frente a GC por trazado

  • Varios comentaristas afirman que el conteo de referencias (RC) ofrece un rendimiento más predecible; otros lo discuten, citando:
    • Las desasignaciones en cascada pueden ser arbitrariamente largas.
    • Alto coste, especialmente con RC atómico en programas multihilo.
    • Problemas con ciclos y NUMA/bloqueos.
  • Algunos prefieren esquemas híbridos o RC diferido; otros subrayan que la industria lleva décadas mostrando que el RC puro tiene compromisos significativos.

Cooperación OS–GC y diseños conscientes de swap

  • Ideas planteadas:
    • GC consciente de swap que precargue o bloquee páginas usadas en fases STW.
    • Uso de APIs existentes como mlock, madvise, mincore o tocar páginas para gestionar la residencia.
    • Cooperación más profunda entre SO y GC (por ejemplo, recolectores con marcadores, gestión cooperativa de memoria, GCs basados en regiones coordinándose con madvise).
  • Algunos ven esto como una oportunidad de investigación/práctica desaprovechada; otros dudan que una integración tan profunda aparezca ampliamente pronto.

Elección del lenguaje y requisitos de latencia

  • Varios comentarios lo tratan como otro dato más: usa Go cuando una latencia ocasional del GC es aceptable; elige Rust/C++ (RAII/control manual) para necesidades estrictas de baja latencia.
  • Se menciona el caso de Discord: incluso con poca basura, un gran grafo de objetos más un GC periódico puede causar una latencia de cola inaceptable, lo que motivó una reescritura en un lenguaje sin GC.