Enquanto tentava melhorar o desempenho da minha classe de detecção de colisões, descobri que ~ 80% do tempo gasto na gpu passava em condições if / else apenas tentando descobrir os limites dos buckets pelos quais deveria passar.
Mais precisamente:
cada thread obtém um ID, por esse ID ele busca seu triângulo na memória (3 inteiros cada) e pelos 3 busca seus vértices (3 flutua cada).
Em seguida, transforma os vértices em pontos inteiros da grade (atualmente 8x8x8) e os transforma nos limites do triângulo nessa grade
Para transformar os 3 pontos em limites, ele encontra o mínimo / máximo de cada dimensão entre cada um dos pontos
Como a linguagem de programação que estou usando está com falta de um minmax intrínseco, eu mesmo criei uma, assim:
procedure MinMax(a, b, c):
local min, max
if a > b:
max = a
min = b
else:
max = b
min = a
if c > max:
max = c
else:
if c < min:
min = c
return (min, max)
Portanto, em média, deve haver comparações de 2,5 * 3 * 3 = 22,5, o que acaba consumindo muito mais tempo do que os testes reais de interseção da borda do triângulo (cerca de 100 * 11-50 instruções).
De fato, descobri que pré-calcular os buckets necessários na CPU (thread único, sem vetorização), empilhá-los em uma exibição de GPU junto com a definição do bucket e fazer com que a GPU faça ~ 4 leituras extras por thread foi 6 vezes mais rápida do que tentar para descobrir os limites no local. (observe que eles são recalculados antes de cada execução, pois estou lidando com malhas dinâmicas)
Então, por que a comparação é tão terrivelmente lenta em uma gpu?
fonte
Respostas:
GPUs são arquiteturas SIMD. Nas arquiteturas SIMD, todas as instruções precisam ser executadas para cada elemento que você processa. (Há uma exceção a essa regra, mas ela raramente ajuda).
Portanto, em sua
MinMax
rotina, não apenas todas as chamadas precisam buscar as três instruções de ramificação (mesmo que em média sejam avaliadas apenas 2,5), mas todas as instruções de atribuição também executam um ciclo (mesmo que, na verdade, não sejam "executadas" )Esse problema às vezes é chamado de divergência de thread . Se sua máquina tiver algo como 32 faixas de execução SIMD, ainda terá apenas uma única unidade de busca. (Aqui, o termo "encadeamento" significa basicamente "faixa de execução do SIMD".) Portanto, internamente, cada faixa de execução do SIMD possui um bit "Estou ativado / desativado", e as ramificações na verdade apenas manipulam esse bit. (A exceção é que, no ponto em que todas as faixas do SIMD ficam desabilitadas, a unidade de busca geralmente passa diretamente para a cláusula "else".)
Portanto, no seu código, todas as faixas de execução do SIMD estão executando:
Pode ser que em algumas GPUs essa conversão de condicionais em predicação seja mais lenta se a GPU estiver fazendo isso sozinha. Conforme apontado por @ PaulA.Clayton, se sua linguagem e arquitetura de programação tiver uma operação de movimentação condicional predicada (especialmente uma das formas
if (c) x = y else x = z
), você poderá fazer melhor. (Mas provavelmente não muito melhor).Além disso, colocar o
c < min
condicional dentroelse
dec > max
é desnecessário. Certamente não está poupando nada e (já que a GPU precisa convertê-la automaticamente em predicação) pode realmente ser prejudicial ao aninhar-se em dois condicionais diferentes.fonte