[Computer Graphics] Shader 코딩에서 if 조건문이 느린 이유

세동네·2022년 12월 14일
1
post-thumbnail

· 분기 발산

컴퓨터의 GPU는 CPU처럼 개별로 돌지 않고 여러 쓰레드(코어)가 묶음이 되어 하나의 명령을 수행한다. 일반적으로 32개 혹은 64개인데, 묶음으로 연산할 때 분기 발산이라는 문제를 마주할 수 있다. 해당 문제는 if/else 문을 셰이더 코딩에 포함했을 때 맞닥뜨릴 수 있는 문제이다.

예를 들어 다음과 같은 예시 코드가 있다.

if (condition)
{
    pixelColor = SkinShading();
}
else
{
    pixelColor = BackgroundShading();
}

condition에 따라 해당 픽셀이 캐릭터 오브젝트의 피부인지 배경인지 검사한다. 이때 한 픽셀에 둘이 공존하는 경우는 없다. GPU에선 여러 개의 쓰레드 묶음이 동시에 위와 같은 명령을 처리하는데, 분기 발산 상황에서 두 분기에 대한 명령을 모두 처리하고 필요한 값만 취한다.

즉, if를 만나 필요한 부분만 코드를 실행하는 것이 아니라, 일단 코드를 실행하고 결과를 반환한 후에 조건을 검사하는 것이다. 따라서 SkinShading()BackgroundShading()이라는 함수 계산을 모두 끝내고 condition을 만족하는 결과만 pixelColor에 저장하고 만족하지 않은 값의 계산 결과는 버린다.

필요하지 않은 코드까지 실행하는 상황이 발생하게 되는 것이다. 최신 하드웨어에서도 이러한 분기 발산은 확인되고 있다.

이러한 문제가 발생하는 이유는 conditiontruefalse 두 값을 모두 가지기 때문이다.

· 다이나믹 브랜칭(Dynamic branching)

앞서 말한 것처럼 분기 발산은 동시에 실행하는 쓰레드에서 조건 분기에 사용하는 condition이 여러 값을 가지기 때문이다. condition의 모든 값이 true거나 모두 false라면 GPU에서 특정 브랜치의 명령만 수행해도 된다는 것을 런타임에 알 수 있어 필요하지 않은 브랜치의 작업은 수행하지 않는다. 이러한 최적화 기법을 다이나믹 브랜칭이라고 하며, 이렇게 동일한 분기로 처리될 때 응집성(coherence)이 좋다고 말한다.

· 분기 발산에 도움이 되는 방법들

1. 다이나믹 브랜칭을 방해하는 코드를 짜지 않는다.

그나마 위의 코드는 응집성이 좋다면 다이나믹 브랜칭이 가능하지만,

Color skinColor = SkinShading();
Color backgroundColor = BackgroundShading();

if (condition)
{
    pixelColor = skinColor;
}
else
{
    pixelColor = backgroundColor;
}

처럼 코드를 작성하였다면 다이나믹 브랜칭조차도 불가능한 아주 답도 없는 상황이 된다. 이러한 코드 작성을 피해야 한다.

2. 공용 코드를 찾고 통일시킨다

만약 두 셰이딩 함수에서 공통된 계산이 있다면, 함수 안에서 하지 않고 밖에서 처리하도록 빼주면 비교적 연산량을 줄여줄 수 있다.

3. 셰이더 분리(셰이더 퍼뮤테이션)

언리얼에선 Shader permutation, 유니티에선 Shader variants라고도 하는데, 셰이더 코드 안에서 #define으로 하나의 코드를 여러 코드로 분리하는 것으로 미약한 성능 향상을 기대할 수 있다.


셰이더 프로그래밍에서 if 조건문에 의한 분기가 비싸다는 것은 위와 같은 이유이다. 그 이유를 알고 시스템을 이해할 필요가 있다.


· 참고

쉐이더에서 IF 문이 느린 이유

0개의 댓글