c++ - proyecto - ¿Cómo y cuándo alinearse al tamaño de la línea de caché?
los objetivos organizacionales deben estar alineados con (1)
En la excelente cola limitada de mpmc de Dmitry Vyukov escrita en C ++ Ver: http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue
Él agrega algunas variables de relleno. Supongo que esto es para alinearlo con una línea de caché para el rendimiento.
Tengo algunas preguntas.
- ¿Por qué se hace de esta manera?
- ¿Es un método portátil que siempre funcionará?
- En qué casos sería mejor utilizar
__attribute__ ((aligned (64)))
lugar. ¿Por qué el relleno antes de un puntero de buffer ayuda con el rendimiento? ¿No es solo el puntero cargado en la memoria caché, por lo que realmente solo tiene el tamaño de un puntero?
static size_t const cacheline_size = 64; typedef char cacheline_pad_t [cacheline_size]; cacheline_pad_t pad0_; cell_t* const buffer_; size_t const buffer_mask_; cacheline_pad_t pad1_; std::atomic<size_t> enqueue_pos_; cacheline_pad_t pad2_; std::atomic<size_t> dequeue_pos_; cacheline_pad_t pad3_;
¿Funcionaría este concepto bajo gcc para el código c?
Se hace de esta manera para que los diferentes núcleos que modifican diferentes campos no tengan que rebotar la línea de caché que los contiene a ambos entre sus cachés. En general, para que un procesador tenga acceso a algunos datos en la memoria, toda la línea de caché que la contiene debe estar en la caché local de ese procesador. Si está modificando esos datos, esa entrada de caché generalmente debe ser la única copia en cualquier caché en el sistema (modo exclusivo en los protocolos de coherencia de caché de estilo MESI / MOESI). Cuando núcleos separados intentan modificar datos diferentes que pasan a vivir en la misma línea de caché, y por lo tanto pierden el tiempo moviendo toda esa línea hacia adelante y hacia atrás, eso se conoce como uso compartido falso .
En el ejemplo particular que das, un núcleo puede enqueuear una entrada (lectura (compartida) buffer_
y escritura (exclusiva) solo enqueue_pos_
) mientras que otra copia (shared buffer_
y exclusive dequeue_pos_
) sin core stalling en una línea de caché propiedad de la otra .
El relleno al principio significa que buffer_
y buffer_mask_
terminan en la misma línea de caché, en lugar de dividirse en dos líneas y, por lo tanto, requieren el doble de tráfico de memoria para acceder.
No estoy seguro si la técnica es completamente portátil. La suposición es que cada cacheline_pad_t
se alineará con un límite de línea de caché de 64 bytes (su tamaño) y, por lo tanto, lo que le sigue será en la siguiente línea de caché.Hasta donde sé, los estándares de lenguaje C y C ++ solo requieren esto de estructuras completas, de modo que puedan vivir en matrices muy bien, sin violar los requisitos de alineación de ninguno de sus miembros. (ver comentarios)
El enfoque del attribute
sería más específico del compilador, pero podría reducir el tamaño de esta estructura a la mitad, ya que el relleno se limitaría a redondear cada elemento a una línea de caché completa. Eso podría ser bastante beneficioso si uno tuviese muchos de estos.
El mismo concepto se aplica tanto en C como en C ++.