He leído mucho sobre los beneficios de organizar los datos en 'Structs of Arrays' (SoA) en lugar del típico 'Array of Structs' (AoS) para obtener un mejor rendimiento al usar instrucciones SIMD . Si bien el 'por qué' tiene mucho sentido para mí, no estoy seguro de cuánto hacer esto cuando trabajo con cosas como vectores.
Los propios vectores pueden considerarse como una estructura de una matriz de datos (de tamaño fijo), por lo que podría convertir una matriz de estos en una estructura de matrices X, Y y Z. A través de esto, puede trabajar en 4 vectores a la vez en lugar de uno a la vez.
Ahora, por la razón específica, estoy publicando esto en GameDev:
¿Tiene sentido trabajar con vectores en la SPU? Más específicamente, ¿tiene sentido para DMA múltiples matrices solo para un solo vector? ¿O sería mejor quedarse con DMA colocando la matriz de vectores y desenrollarlos en los diferentes componentes para trabajar?
Pude ver el beneficio de cortar el desenrollado (si lo hizo 'AoS'), pero parece que podría quedarse rápidamente sin canales DMA si tomara esta ruta y trabajara con múltiples conjuntos de vectores a la vez.
(Nota: todavía no hay experiencia profesional con Cell, pero he estado jugando en OtherOS por un tiempo)
fuente
Las SPU son en realidad un caso especial interesante cuando se trata de vectorizar código. Las instrucciones se dividen en familias "aritméticas" y "cargar / almacenar", y las dos familias se ejecutan en tuberías separadas. La SPU puede emitir uno de cada tipo por ciclo.
Obviamente, el código matemático está fuertemente vinculado a las instrucciones matemáticas, por lo que, por lo general, los bucles matemáticos en SPU tendrán muchos ciclos abiertos en la tubería de carga / almacenamiento. Dado que las mezclas se producen en la tubería de carga / almacenamiento, a menudo tiene suficientes instrucciones de carga / almacenamiento gratuitas para mezclar el formulario xyzxyzxyzxyz en el formulario xxxxyyyyzzzz sin ningún tipo de sobrecarga.
Esta técnica se usa en Naughty Dog al menos; consulte sus presentaciones de ensamblaje de SPU ( parte 1 y parte 2 ) para obtener más detalles.
Desafortunadamente, el compilador a menudo no es lo suficientemente inteligente como para hacer esto automáticamente; si decide seguir esta ruta, deberá escribir el ensamblaje usted mismo o desenrollar sus bucles utilizando intrínsecos y verificar el ensamblador para asegurarse de que sea lo que desea. Por lo tanto, si está buscando escribir código multiplataforma general que funcione bien en SPU, es posible que desee utilizar SoA o AoSoA (como sugiere jpaver).
fuente
Como con cualquier optimización, perfil! La legibilidad es lo primero, y solo debe sacrificarse cuando el perfil identifica un cuello de botella en particular y ha agotado todas sus opciones para ajustar el algoritmo de alto nivel (¡la forma más rápida de hacer el trabajo es no tener que hacer el trabajo!) Siempre debe volver a crear un perfil siguiendo cualquier optimización de bajo nivel para confirmar que realmente ha hecho las cosas más rápido en lugar de lo contrario, especialmente con tuberías tan extravagantes como las de Cell.
Las técnicas que utilice dependerán de los detalles del cuello de botella. En general, cuando se trabaja con tipos de vectores, un componente de vector que ignora en un resultado representa el desperdicio de trabajo. Cambiar SoA / AoS no tiene sentido a menos que le permita hacer un trabajo más útil al llenar dichos componentes no utilizados (por ejemplo, un producto de punto en la PPU de PS3 frente a cuatro productos de punto en paralelo en la misma cantidad de tiempo). Para responder a su pregunta, ¡pasar tiempo barajando componentes solo para realizar una operación en un solo vector me parece una pesadilla!
La otra cara de las SPU es que la mayor parte del costo de las pequeñas transferencias DMA está en configuración; cualquier cosa menor a 128 bytes tomará la misma cantidad de ciclos para transferir, y cualquier cosa menor a aproximadamente un kilobyte solo unos pocos ciclos más. Por lo tanto, no se preocupe si DMA envía más datos de los estrictamente necesarios; reducir la cantidad de transferencias secuenciales de DMA activadas y realizar el trabajo mientras se realizan transferencias de DMA, y por lo tanto desplegar prólogos y epílogos de bucle para formar tuberías de software, es clave para un buen rendimiento de SPU, y es más fácil lidiar con los casos de esquina obteniendo datos adicionales / descartar resultados parcialmente calculados que saltar a través de aros para tratar de organizar la cantidad exacta de datos que es necesario leer y procesar.
fuente
No, eso no tendría mucho sentido en general, ya que la mayoría de los códigos de operación de vectores operan en un vector en su conjunto y no en componentes separados. Por lo tanto, ya puede multiplicar un vector en 1 instrucción, mientras que al dividir los componentes separados gastaría 4 instrucciones en él. Entonces, dado que básicamente realiza muchas operaciones en general en parte de una estructura, es mejor empaquetarlas en una matriz, pero casi nunca hace cosas solo en un componente de un vector, o es muy diferente en cada componente, por lo que los divide fuera no funcionaría.
Por supuesto, si encuentra una situación en la que tiene que hacer algo solo con los componentes (digamos) x de los vectores, podría funcionar, sin embargo, la penalización de devolver todo cuando necesita el vector real no sería barato, por lo que podría me pregunto si no debería usar vectores para comenzar, sino solo una serie de flotantes que permiten que los códigos de operación de vectores hagan sus cálculos específicos.
fuente