É popular renderizar conteúdo procedural dentro da GPU, por exemplo, na demoscene (desenhar um único quad para preencher a tela e permitir que a GPU calcule os pixels).
A marcha dos raios é popular:
Isso significa que a GPU está executando um número desconhecido de iterações de loop por pixel (embora você possa ter um limite superior como maxIterations
).
Como ter um loop de comprimento variável afeta o desempenho do shader?
Imagine o psuedocode simples de marchar com raios:
t = 0.f;
while(t < maxDist) {
p = rayStart + rayDir * t;
d = DistanceFunc(p);
t += d;
if(d < epsilon) {
... emit p
return;
}
}
Como são afetadas as várias famílias principais de GPU (Nvidia, ATI, PowerVR, Mali, Intel etc.)? Shaders de vértice, mas particularmente shaders de fragmentos?
Como isso pode ser otimizado?
Respostas:
Houve uma boa conversa no GDC 2012 sobre a marcha de raios a distância na GPU (e outros tópicos): http://directtovideo.wordpress.com/2012/03/15/get-my-slides-from-gdc2012/
Quanto ao desempenho, as placas gráficas mais recentes (classe DX11) executam shaders em unidades SIMD que executam 32 "threads" (NVIDIA) ou 64 (AMD) na etapa de bloqueio. Esses grupos são conhecidos como deformações ou frentes de onda. Para pixel shaders, cada thread é igual a um pixel, então eu esperaria que a unidade SIMD esteja processando algo como um bloco de pixels 8x4 (NVIDIA) ou 8x8 (AMD) juntos. O controle de ramificação e fluxo é feito por frente de onda, para que todos os segmentos em uma frente de onda tenham que repetir tantas vezes quanto o pixel individual mais profundo dentro dessa frente de onda. As máscaras de pista do SIMD desativam a execução dos pixels que já foram finalizados, mas ainda precisam silenciosamente acompanhar o controle de fluxo geral da frente de onda. Isso significa, é claro, que o sistema é muito mais eficiente quando a ramificação é coerente,
Na minha experiência, a sobrecarga de ramificação ainda é bastante alta, mesmo que todos os threads na frente de onda se ramifiquem da mesma maneira. Vi ganhos de desempenho em alguns casos, desenrolando o loop para amortizar parte da sobrecarga da filial. No entanto, depende de quanto trabalho você está fazendo em cada iteração de loop, é claro. Se o corpo do loop tiver bastante "material", o desenrolar não será uma vitória.
fonte
Sugiro a leitura de código em execução no Teraflop: Como funcionam os núcleos de sombreador de GPU (pdf) de SIGGRAPH 2008: Beyond Programmable Shading . Ele fala sobre ramificação dinâmica.
fonte
No que diz respeito à ramificação dinâmica, uma observação adicional (pode ser óbvia, mas ainda vale a pena notar para algumas pessoas): ela pode afetar seriamente o desempenho de loops desenrolados (você obviamente não pode desenrolar um loop se houver um número não constante de iterações) .
fonte
int s = 0;
agora para (int k = 1; k <= n; k ++) {s + = k;} é o mesmo que s = n * (n + 1) / 2
então isso não é verdade em geral: D
fonte