Impacto dos loops de comprimento variável nos shaders da GPU

9

É 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:

insira a descrição da imagem aqui

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?

Vai
fonte
Infelizmente, esta pergunta é muito difícil de ser respondida corretamente aqui. Embora uma resposta já tenha dado pontos a uma fonte que vale a pena ler (envolve ramificação dinâmica). +1 para o "tópico" ..
teodron
11
@teodron não seja derrotista! Eu esperava que alguém dissesse que, nos cartões da NVidia, os pixels da tela em blocos de 8x8 serão todos iterados o mais profundo possível, e que os blocos de 8x8 pixels possam ser feitos em qualquer ordem ou algo assim; isso não é verdade, é exatamente o tipo de sabedoria que espero que as pessoas possam compartilhar. Links no Larrabee, hmm, são bem indiretos.
Will
Parece que ele não está discutindo Larrabee, mas o cara de Stanford deu a mesma palestra dois anos depois, em 2010 ( você pode vê-lo aqui ). Pelas figuras dele, considerando um loop while, não entendi se os pixels que "terminam" seus cálculos mais cedo compensam qualquer desempenho. No CUDA, os threads aguardam em uma barreira. Por analogia, o que acontece com os threads do shader?
Teodron #
@teodron sim, eu entendi o CUDA e me inscrevi nas GPUs; Tenho certeza de que eles estão em sintonia, mas eu gostaria que alguém com conhecimento entrasse; de qualquer maneira, algo relacionado está aqui williamedwardscoder.tumblr.com/post/26628848007/rod-marching
Will

Respostas:

8

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.

Nathan Reed
fonte
0

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) .

Gavan Woolery
fonte
-4

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

gimp
fonte
11
Você pode estar sendo muito criticado porque ninguém sabe ao certo o que você está tentando transmitir aqui ou o que isso tem a ver com a pergunta.
doppelgreener