- 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:
- 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
Comentarios de Hacker News
Este artículo es una lectura entretenida
Reference[T]que ofrece la misma funcionalidadRecientemente, mientras hacía tuning de rendimiento en Go, terminé usando un diseño de arena muy parecido para exprimir el rendimiento al máximo
unsafeAlgunas mejoras sencillas
capde forma más agresiva antes de llamar alappendincorporadounsafe.Stringes útil para pasar strings desde un slice de bytes sin asignacionesNo viene al tema, pero me gusta el minimapa al costado
Resumen para quienes no quieren leer un artículo largo
unsafepara acelerar el trabajo del asignadorunsafe.Pointer, el GC no puede ver correctamente lo que se apunta dentro de la arena y podría liberarlo por errorchunks) que apunta a todos los bloques grandes de memoria que la arena obtuvo del sistema yreflect.StructOfpara crear un nuevo tipo que incluya campos de puntero adicionales hacia estos bloquesRelacionado: discusión sobre agregar "regiones de memoria" a la biblioteca estándar
Contenido interesante
Go prioriza no romper el ecosistema
Una nota meta rápida