1 puntos por GN⁺ 2025-04-24 | 1 comentarios | Compartir por WhatsApp
  • El lenguaje Go tiene muy poco comportamiento indefinido y una semántica simple de GC (recolección de basura)
  • En Go es posible la gestión manual de memoria, y puede hacerse en cooperación con el GC
  • Un Arena es una estructura de datos para asignar eficientemente memoria con el mismo tiempo de vida, y se explica cómo implementarla en Go
  • Se explica cómo el GC administra la memoria mediante el algoritmo de Mark and Sweep
  • Usar Arena puede mejorar el rendimiento de asignación de memoria, y esto es posible mediante varias optimizaciones
  • Se busca mejorar el rendimiento y minimizar la carga del GC mediante la eliminación de write barriers, reutilización de memoria, chunk pooling, etc.
  • Se presentan patrones seguros y rápidos para el manejo real de memoria a gran escala mediante funciones como implementación de realloc, reutilización de Arena e inicialización (Reset)

Panorama general de la asignación manual de memoria basada en Arena en Go

  • Go es un lenguaje seguro gracias a su comportamiento claro del GC y a la casi ausencia de Undefined Behavior
  • Usando el paquete unsafe, es posible el control directo de memoria ajustado a la implementación interna del GC
  • Este artículo explica cómo crear un asignador de memoria basado en estructuras Arena que pueda cooperar con el GC en Go

Definición de Arena y por qué se necesita

  • Arena es una estructura para asignar eficientemente objetos con el mismo tiempo de vida
  • Mientras que un append normal expande arreglos de forma exponencial, Arena agrega nuevos bloques y entrega punteros
  • La interfaz estándar es la siguiente:
    • Alloc(size, align uintptr) unsafe.Pointer

Punteros y cómo funciona el GC

  • El GC funciona rastreando (mark) y recuperando (sweep) memoria
  • Para un GC preciso, usa metadatos llamados pointer bits que indican la ubicación de los punteros
  • Si los punteros se manejan mal dentro de Arena, el GC podría no rastrearlos y provocar errores de Use-After-Free

Cómo diseñar un Arena

  • La estructura Arena tiene campos como:
    • next, left, cap, chunks
  • Todas las asignaciones se manejan con alineación de 8 bytes, y si no alcanza, se crea un nuevo chunk con nextPow2
  • El chunk se asigna con un tipo struct { A [N]uintptr; P *Arena } en lugar de []uintptr, para que el GC pueda rastrear el Arena

Cómo garantizar la seguridad de punteros en Arena

  • Cuando se usan punteros asignados solo dentro de Arena, el GC mantiene vivo todo el Arena
  • Haciendo que un puntero haga referencia al Arena, se garantiza que todo el Arena sobreviva al GC
  • El método de asignación del Arena realiza lo siguiente:
    • guarda el puntero al Arena al final del chunk en allocChunk()

Resultados de benchmarks de rendimiento

  • Frente a new, la asignación con Arena muestra en promedio una mejora de rendimiento de 2 a 4 veces o más
  • Incluso en situaciones con alta carga del GC, el enfoque con Arena muestra un rendimiento superior de hasta más de 2 veces
  • Optimizaciones como eliminar write barriers y usar uintptr logran hasta 20% de mejora en asignaciones pequeñas

Estrategias de reutilización de chunks y eliminación del heap

  • Es posible reutilizar chunks usando sync.Pool
  • Mediante runtime.SetFinalizer(), cuando el Arena desaparece los chunks se devuelven al pool
  • El rendimiento mejora mucho en asignaciones pequeñas, pero en asignaciones grandes puede ser más lento que new

Inicialización y reutilización del Arena

  • El método Reset() puede devolver el Arena a su estado inicial
  • Aunque es riesgoso, permite reutilizar la misma estructura sin reasignar memoria
  • Incluso al reutilizar, también se reutilizan los chunks, mejorando mucho el rendimiento

Implementación de la funcionalidad Realloc

  • Se implementa la función realloc dentro del Arena para permitir expansión dinámica sobre la asignación más reciente
  • Cuando no es posible, se asigna nueva memoria y luego se copian los datos

Conclusión y código completo disponible

  • A partir de una comprensión profunda del mecanismo de GC de Go y de su implementación interna, se completa un gestor de memoria basado en Arena
  • Es una estructura que combina seguridad y rendimiento, y si se usa correctamente resulta muy útil para manejar estructuras de datos a gran escala
  • El código completo incluye la estructura Arena y New, Alloc, Reset, allocChunk, finalize, etc.

1 comentarios

 
GN⁺ 2025-04-24
Comentarios de Hacker News
  • Este artículo es una lectura entretenida

    • Si te gustó este artículo o quieres controlar mejor la asignación de memoria en Go, te recomiendo revisar el paquete que escribí
    • Me gustaría recibir comentarios o que otras personas lo usen
    • Este paquete asigna su propia memoria por separado del runtime, evitando por completo el GC
    • No permite tipos puntero en la asignación, pero los sustituye con un tipo Reference[T] que ofrece la misma funcionalidad
    • La liberación de memoria se hace manualmente, así que no se puede depender del recolector de basura
    • En Go, este tipo de asignadores personalizados normalmente apuntan a arenas que soportan grupos de asignaciones que se crean y destruyen juntas
    • Sin embargo, el paquete offheap busca construir estructuras de datos grandes y de larga duración con costo cero de recolección de basura
    • Cosas como cachés grandes en memoria o bases de datos
  • Recientemente, mientras hacía tuning de rendimiento en Go, terminé usando un diseño de arena muy parecido para exprimir el rendimiento al máximo

    • Usé slices de bytes como buffer y chunks en lugar de punteros unsafe
    • Lo intenté, pero no fue más rápido y sí mucho más complejo
    • Tengo que volver a comprobarlo antes de estar 100% seguro
  • Algunas mejoras sencillas

    • Si empiezas con un slice pequeño y luego se agrega mucho payload de golpe, escribe tu propio append que aumente cap de forma más agresiva antes de llamar al append incorporado
    • unsafe.String es útil para pasar strings desde un slice de bytes sin asignaciones
    • Hay que leer con cuidado las advertencias y entender lo que se está haciendo
  • No viene al tema, pero me gusta el minimapa al costado

    • Es útil para moverse por artículos técnicos largos o volver a consultar algo que ya habías leído
    • Me pregunto cómo podría agregarlo a mi sitio
    • Está realmente bueno
  • Resumen para quienes no quieren leer un artículo largo

    • El autor original construyó un asignador de arenas en Go usando unsafe para acelerar el trabajo del asignador
    • Es especialmente útil al asignar muchas cosas que se crean y destruyen juntas
    • El problema principal es que el GC de Go necesita conocer el layout de los datos, especialmente dónde están los punteros, para funcionar correctamente
    • Si asignas bytes crudos con unsafe.Pointer, el GC no puede ver correctamente lo que se apunta dentro de la arena y podría liberarlo por error
    • Pero, para hacer que funcione siempre que los punteros apunten a otras cosas dentro de la misma arena, mantiene viva toda la arena si alguna parte de ella sigue referenciada
    • (1) Mantiene un slice (chunks) que apunta a todos los bloques grandes de memoria que la arena obtuvo del sistema y
    • (2) usa reflect.StructOf para crear un nuevo tipo que incluya campos de puntero adicionales hacia estos bloques
    • Así, si el GC encuentra un puntero hacia los chunks, también encuentra los back pointers, marca la arena como viva y conserva el slice de chunks
    • Luego presenta técnicas de optimización interesantes para eliminar varias comprobaciones internas y barreras de escritura
  • Relacionado: discusión sobre agregar "regiones de memoria" a la biblioteca estándar

    • Propuesta anterior de arenas
  • Contenido interesante

    • Me da curiosidad cómo suelen probar o hacer benchmarks de seguridad de memoria e interacción con el GC quienes construyen asignadores off-heap o estilo arena en Go
  • Go prioriza no romper el ecosistema

    • Eso permite asumir que la ley de Hyrum protegerá ciertos comportamientos observables específicos del runtime
    • Si esa afirmación es correcta, Go como lenguaje está en un callejón sin salida evolutivo
    • En ese caso, no estoy seguro de que Go me parezca interesante
  • Una nota meta rápida

    • Este artículo es realmente muy largo y no tengo tiempo para leer los detalles de contexto
    • Por ejemplo, la sección "Mark and Sweep" ocupa más de 4 páginas en la pantalla de mi laptop
    • Esa sección empieza después de más de 5 páginas desde el inicio del artículo
    • Me pregunto si es resultado de que una IA ayudó a redactar las secciones y por eso quedó demasiado exhaustivo
    • Generar contenido es fácil, pero no se tomaron decisiones editoriales para conservar solo lo importante
    • Solo quiero conocer la parte sobre asignadores de arenas, no necesito un tutorial sobre recolección de basura