Czy polecenia warunkowe spowalniają shadery?

Chcę wiedzieć, czy "if-statements" wewnątrz shaderów (wierzchołek / fragment / piksel...) naprawdę spowalniają działanie shader ' a. Na przykład:

Czy lepiej użyć tego:

vec3 output;
output = input*enable + input2*(1-enable);

Zamiast tego:

vec3 output;
if(enable == 1)
{
    output = input;
}
else
{
    output = input2;
}

Na innym forum była dyskusja o tym (2013): http://answers.unity3d.com/questions/442688/shader-if-else-performance.html Tutaj chłopaki mówią, że oświadczenia If są naprawdę złe dla wydajności shader.

Również tutaj mówią o tym, ile jest wewnątrz deklaracji if/else (2012): https://www.opengl.org/discussion_boards/showthread.php/177762-Performance-alternative-for-if-(-)

Może Sprzęt lub shadercompiler są teraz lepsze i jakoś naprawiają ten (może nie istniejący) problem z wydajnością.

EDIT:

Co jest w tym przypadku, powiedzmy, że enable jest zmienną jednorodną i jest zawsze ustawiona na 0:

if(enable == 1) //never happens
{
    output = vec4(0,0,0,0);
}
else  //always happens
{
    output = calcPhong(normal, lightDir);
}

Myślę, że tutaj mamy gałąź wewnątrz shadera, która spowalnia shadera. Zgadza się?

Czy bardziej sensowne jest tworzenie 2 różnych shaderów, takich jak jeden dla else, a drugi dla if?

Author: Nicol Bolas, 2016-06-15

2 answers

Co jest takiego w shaderach, że potencjalnie powodują problemy z wydajnością if? Ma to związek z tym, jak shadery są wykonywane i skąd GPU uzyskać ich ogromną wydajność obliczeniową.

Oddzielne wywołania shadera są zwykle wykonywane równolegle, wykonując te same instrukcje w tym samym czasie. Po prostu wykonują je na różnych zestawach wartości wejściowych; mają wspólne uniformy, ale mają różne wewnętrzne rejestry. Jeden termin dla grupy shaderów wszystkich wykonywanie tej samej sekwencji operacji to "wavefront".

Potencjalny problem z dowolną formą rozgałęzienia warunkowego polega na tym, że może to wszystko zepsuć. Powoduje to, że różne wywołania wewnątrz fali muszą wykonywać różne sekwencje kodu. Jest to bardzo kosztowny proces, w którym należy utworzyć nową falę, skopiować do niej dane itp. Chyba że... nie ma.

Na przykład, jeśli warunek jest taki, który jest przyjmowany przez co wywołanie w wavefront, wtedy nie jest potrzebna rozbieżność w uruchomieniu. Jako taki, koszt if jest tylko kosztem sprawdzenia warunku.

Załóżmy więc, że masz gałąź warunkową i załóżmy, że wszystkie wywołania w polu falowym przyjmą tę samą gałąź. Istnieją trzy możliwości natury wyrażenia w tym stanie:

  • statyczny czas kompilacji. Wyrażenie warunkowe jest całkowicie oparte na stałych czasu kompilacji. Jako takie, ty wiedzieć, patrząc na KOD, które gałęzie będą brane. Prawie każdy kompilator obsługuje to jako część podstawowej optymalizacji.
  • statycznie jednolite rozgałęzienia. Warunek jest oparty na wyrażeniach obejmujących rzeczy, które są znane w czasie kompilacji jako stałe (w szczególności stałe i wartości uniform). Ale wartość wyrażenia nie będzie znana podczas kompilacji. Więc kompilator może statycznie być pewien, że wavefronts nigdy nie zostanie złamany przez to if, ale kompilator nie może wiedzieć, która gałąź zostanie pobrana.
  • dynamiczne rozgałęzianie. Wyrażenie warunkowe zawiera terminy inne niż stałe i uniformy. W tym przypadku kompilator nie może a priori stwierdzić, czy pasmo falowe zostanie zerwane, czy nie. To, czy będzie to konieczne, zależy od runtime ewaluacji wyrażenia warunkowego.

Różne urządzenia mogą obsługiwać różne typy rozgałęzień bez rozbieżności.

Również, nawet jeśli warunek jest przyjmowany przez różne wavefronts, kompilator może zrestrukturyzować kod tak, aby nie wymagał rzeczywistego rozgałęzienia . Podałeś dobry przykład: output = input*enable + input2*(1-enable); jest funkcjonalnie równoważne instrukcji if. Kompilator może wykryć, że if jest używany do ustawiania zmiennej, a tym samym wykonać obie strony. Często robi się to w przypadku warunków dynamicznych, w których ciała gałęzi są małe.

Prawie każdy sprzęt może obsłużyć var = bool ? val1 : val2 bez konieczności rozchodzenia się. Było to możliwe w 2002.

Ponieważ jest to bardzo zależne od sprzętu, it... zależy od sprzętu. Istnieją jednak pewne epoki sprzętu, na które można spojrzeć:

Desktop, Pre-D3D10

Tam, to taki dziki zachód. Kompilator NVIDII dla takiego sprzętu był znany z wykrywania takich warunków i w rzeczywistości rekompilowania shadera za każdym razem, gdy zmieniałeś mundury, które miały wpływ na takie warunki.

Ogólnie rzecz biorąc, w tej erze około 80% " nigdy nie używaj if statements " pochodzi z. Ale nawet tutaj, to niekoniecznie prawda.

Można oczekiwać optymalizacji rozgałęzień statycznych. Możesz mieć nadzieję, że statycznie jednolite rozgałęzienia nie spowodują dodatkowego spowolnienia (choć fakt, że NVIDIA uważała, że rekompilacja będzie szybsza niż jej wykonanie, sprawia, że jest to mało prawdopodobne przynajmniej dla ich sprzętu). Ale dynamiczne rozgałęzienie będzie cię coś kosztować, nawet jeśli wszystkie wywołania będą miały tę samą gałąź.

Kompilatory tej epoki do ich najlepsze do optymalizacji shaderów tak, że proste warunki mogą być wykonane w prosty sposób. Na przykład, twoja output = input*enable + input2*(1-enable); jest czymś, co porządny kompilator mógłby wygenerować z Twojej równoważnej instrukcji if.

Desktop, Post-D3D10

Sprzęt tej epoki jest na ogół w stanie obsługiwać statycznie jednolite deklaracje gałęzi z niewielkim spowolnieniem. W przypadku dynamicznego rozgałęziania możesz napotkać spowolnienie.

Pulpit, d3d11 +

Sprzęt tej epoki jest w zasadzie gwarantowane, aby być w stanie obsłużyć dynamicznie jednolite Warunki z niewielkimi problemami wydajności. Rzeczywiście, nie musi być nawet dynamicznie jednorodny; tak długo, jak wszystkie wywołania w tym samym paśmie falowym podążają tą samą ścieżką, nie zauważysz żadnej znaczącej utraty wydajności.

Zauważ, że niektóre urządzenia z poprzedniej epoki prawdopodobnie mogłyby to zrobić. Ale to jest ten, w którym jest prawie pewne, że to prawda.

Mobile, ES 2.0

Welcome back to Dziki Zachód. Chociaż w przeciwieństwie do pulpitu Pre-D3D10, wynika to głównie z ogromnej wariancji sprzętu ES 2.0. Jest tak ogromna ilość rzeczy, które mogą obsłużyć ES 2.0, a wszystkie działają bardzo inaczej od siebie.

Rozgałęzienia statyczne prawdopodobnie zostaną zoptymalizowane. Ale to, czy uzyskasz dobrą wydajność dzięki statycznie jednolitemu rozgałęzieniu, jest bardzo zależne od sprzętu.

Mobile, ES 3.0 +

Tutaj sprzęt jest raczej dojrzalszy i bardziej wydajny niż ES 2.0. Jako takie, ty można oczekiwać, że statycznie jednolite gałęzie będą działać w miarę dobrze. A niektóre urządzenia mogą prawdopodobnie obsługiwać dynamiczne gałęzie tak, jak robi to nowoczesny sprzęt komputerowy.

 148
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Jest wysoce zależny od sprzętu i stanu.

Jeśli twój warunek jest jednolity: nie kłopocz się, pozwól kompilatorowi się tym zająć. Jeśli twój warunek jest czymś dynamicznym (jak wartość obliczona z atrybutu lub pobrana z tekstury lub czegoś takiego), to jest to bardziej skomplikowane.

W tym drugim przypadku będziesz musiał przetestować i porównać, ponieważ będzie to zależało od złożoności kodu w każdej gałęzi i od tego, jak "spójna" jest decyzja oddziału jest.

Na przykład, jeśli jedna z gałęzi jest brana w 99% przypadków i odrzuca fragment, to najprawdopodobniej chcesz zachować warunek. Ale OTOH w Twoim prostym przykładzie powyżej jeśli enable jest pewnym warunkiem dynamicznym, wybór arytmetyczny może być lepszy.

Jeśli nie masz jasnego przypadku, jak powyżej, lub jeśli nie optymalizujesz dla jednej ustalonej znanej architektury, prawdopodobnie lepiej będzie, gdy kompilator się o tym dowie.

 11
Author: 246tNt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-15 06:18:34