C++ de los fundamentos al dominio — Lección 22
Diseñar el coste de memoria con disposición de datos, cachés y pmr
Los artículos de este blog se han escrito con fines didácticos. Pueden contener errores; contrasta la información con el compilador y la biblioteca estándar de tu plataforma.
El rendimiento no depende solo del número de operaciones. La disposición de objetos, el orden de acceso y la frecuencia de asignación afectan a la caché y al throughput.
Los datos contiguos aprovechan la caché
struct Particle
{
float X, Y, Z;
float VelocityX, VelocityY, VelocityZ;
bool Active;
};
std::vector<Particle> particles;
Un Array of Structures es simple y local cuando cada paso usa casi todos los campos. Si solo actualiza posiciones, puede cargar campos no usados en la cache line.
struct ParticlePositions
{
std::vector<float> X, Y, Z;
};
Una Structure of Arrays mantiene campos iguales contiguos y puede ayudar a SIMD y al procesamiento masivo, a costa de complicar operaciones sobre una partícula completa.
Diseñar la asignación según la vida útil
std::array<std::byte, 4096> buffer;
std::pmr::monotonic_buffer_resource arena(buffer.data(), buffer.size());
std::pmr::vector<std::pmr::string> names(&arena);
names.emplace_back("Knight");
names.emplace_back("Mage");
Los contenedores PMR reciben su memory resource en ejecución. monotonic_buffer_resource asigna hacia delante y libera todo junto, apropiado para datos temporales con una vida compartida.
El resource debe vivir más que cada contenedor y elemento que lo usa. Destruir antes el arena deja referencias a memoria inválida.
reserve es una política de reasignación
std::vector<Entity> entities;
entities.reserve(expectedCount);
Con un tamaño esperado, reserve evita asignaciones y movimientos repetidos. Una estimación excesiva retiene memoria sin usar. La reasignación invalida direcciones e iteradores; las referencias duraderas pueden requerir índices o handles con generación.
No cambies el layout sin medir. Compara workloads representativos mediante wall time, CPU time, asignaciones, peak memory y cache misses, y vuelve a comprobar la corrección.
Un buen diseño de memoria no usa el allocator más ingenioso. Expresa vidas y patrones de acceso y elige el modelo de propiedad más simple que cumpla el objetivo medido.