Li muito sobre os benefícios da organização de dados em 'Structs of Arrays' (SoA), em vez do típico 'Array of Structs' (AoS), para obter melhor rendimento ao usar as instruções SIMD . Enquanto o 'porquê' faz total sentido para mim, não tenho certeza de quanto fazer isso ao trabalhar com coisas como vetores.
Os próprios vetores podem ser considerados como uma estrutura de uma matriz de dados (tamanho fixo), para que você possa converter uma matriz desses em uma estrutura de matrizes X, Y e Z. Com isso, você pode trabalhar em 4 vetores de uma vez, em oposição a um de cada vez.
Agora, pelo motivo específico de postar isso no GameDev:
Isso faz sentido para trabalhar com vetores na SPU? Mais especificamente, faz sentido DMA várias matrizes apenas para um único vetor? Ou seria melhor manter o DMA na matriz de vetores e desenrolá-los nos diferentes componentes para trabalhar?
Eu pude ver o benefício de cortar o desenrolamento (se você fez 'AoS'), mas parece que você pode ficar rapidamente sem canais de DMA se você seguir essa rota e estiver trabalhando com vários conjuntos de vetores ao mesmo tempo.
(Nota: ainda não tem experiência profissional com o Cell, mas já está brincando no OtherOS há algum tempo)
fonte
As SPUs são realmente um caso especial interessante quando se trata de vetorizar código. As instruções são divididas em famílias "aritmética" e "carga / armazenamento", e as duas famílias são executadas em tubulações separadas. A SPU pode emitir um de cada tipo por ciclo.
Obviamente, o código matemático está fortemente vinculado às instruções matemáticas - portanto, normalmente, os loops matemáticos no SPU terão muitos e muitos ciclos abertos no pipe de carregamento / armazenamento. Como as embaralhamentos ocorrem no tubo de carregamento / armazenamento, geralmente você tem instruções de carregamento / armazenamento livres suficientes para passar o formato xyzxyzxyzxyz para o formato xxxxyyyyzzzz sem nenhuma sobrecarga.
Essa técnica está em uso no Naughty Dog, pelo menos - consulte as apresentações de montagem da SPU ( parte 1 e parte 2 ) para obter detalhes.
Infelizmente, o compilador geralmente não é inteligente o suficiente para fazer isso automaticamente - se você optar por seguir esse caminho, precisará escrever o assembly você mesmo ou desenrolar seus loops usando intrínsecos e verificar o assembler para garantir que é o que deseja. Portanto, se você deseja escrever um código geral de plataforma cruzada que funcione bem no SPU, convém usar SoA ou AoSoA (como o jpaver sugere).
fonte
Como em qualquer otimização, perfil! A legibilidade vem em primeiro lugar, e só deve ser sacrificada quando o perfil identifica um gargalo específico e você esgotou todas as opções para ajustar o algoritmo de alto nível (a maneira mais rápida de fazer o trabalho é não ter que fazer o trabalho!) seguindo qualquer otimização de baixo nível para confirmar que você realmente tornou as coisas mais rápidas do que o oposto, especialmente com dutos tão peculiares quanto os da célula.
Quais técnicas você usa então dependerão dos detalhes do gargalo. Em geral, ao trabalhar com tipos de vetores, um componente de vetor que você ignora em um resultado representa o trabalho desperdiçado. Alternar SoA / AoS não faz sentido, a menos que permita um trabalho mais útil preenchendo esses componentes não utilizados (por exemplo, um produto pontual na PPU do PS3 vs quatro produtos pontuais paralelamente na mesma quantidade de tempo). Para responder à sua pergunta, gastar tempo misturando componentes apenas para executar uma operação em um único vetor parece uma pessimização para mim!
O outro lado das SPUs é que a maior parte do custo de pequenas transferências de DMA está configurada; qualquer coisa menor que 128 bytes levará o mesmo número de ciclos para transferir e qualquer coisa menor que cerca de um kilobyte apenas alguns ciclos a mais. Portanto, não se preocupe em fornecer mais dados do que o necessário; reduzir o número de transferências sequenciais de DMA acionadas e executar o trabalho enquanto as transferências de DMA estão acontecendo - e, portanto, desdobrar prólogos e epílogos de loop para formar pipelines de software - é a chave para o bom desempenho da SPU e é mais fácil lidar com casos extremos, buscando dados extras / descartando resultados parcialmente computados do que pulando em bastidores para tentar organizar a quantidade exata de dados necessários para serem lidos e processados.
fonte
Não, isso não faria muito sentido em geral, pois a maioria dos opcodes vetoriais opera em um vetor como um todo e não em componentes separados. Assim, você já pode multiplicar um vetor em 1 instrução, enquanto que, ao dividir os componentes separados, você gasta 4 instruções nele. Portanto, como você basicamente realiza muitas operações em parte de uma estrutura, é melhor colocá-las em uma matriz, mas dificilmente você faz as coisas apenas em um componente de um vetor ou muito diferente em cada componente, quebrando-as fora não funcionaria.
Obviamente, se você encontrar uma situação em que precisa fazer algo apenas para os componentes (digamos) x dos vetores, pode funcionar, no entanto, a penalidade de refazer tudo de volta quando você precisar do vetor real não seria barata, então você poderia pergunto se você não deveria usar vetores para começar, mas apenas uma matriz de elementos flutuantes que permitem que os códigos de vetor opcionais façam seus cálculos específicos.
fonte