- GPU 셰이더에서 삼항 연산자나 단순
if로 값을 고르는 코드는 보통 조건부 분기가 아니라 조건부 이동(select)으로 처리됨 step()과 산술 마스킹으로 바꿔도 사라질 분기가 없으므로, 이른바 브랜치 제거 최적화라는 전제 자체가 맞지 않음- AMD와 Microsoft 컴파일러 출력에는 비교와 조건부 마스크/이동 명령이 나타나며, 점프나 브랜치 명령은 보이지 않음
step()버전은0.0/1.0마스크를 만든 뒤 곱셈과 덧셈으로 결과를 합성해, 직접 조건부 이동하는 코드보다 불필요한 연산이 늘어남- 조건에 따라 큰 계산 블록을 건너뛰는 GPU 분기는 여전히 유용하지만, 단순 값 선택은 생성된 머신 코드를 확인하는 쪽이 더 안전함
단순 값 선택은 GPU 분기가 아님
- 예시 함수
snap45()는 입력 벡터에서x = abs(v.x)를 계산한 뒤, 두 개의 삼항 연산자로 세 가지vec2결과 중 하나를 반환함 - 같은 논리는 일반
if문으로 작성해도 유지됨 - 문제가 되는 “최적화”는 삼항 연산자를
step()과 가중치 합성으로 바꾸는 방식임w0,w1,w2를step()으로 만듦res0,res1,res2를 각각 계산함w0*res0 + w1*res1 + w2*res2로 최종 결과를 합성함
- 이 변환은 원래 코드가 조건부 브랜치를 만든다는 오해에서 출발함
- 단순 레지스터 값 선택은 명령 포인터를 바꾸지 않으며, 예측 실패나 파이프라인 플러시, 명령 캐시 무효화도 일으키지 않음
- GPU의 실제 분기는 조건에 따라 큰 계산 블록을 건너뛸 때 빠르고 유용할 수 있음
- 다만 예시처럼 단순 값이나 계산 결과를 고르는 경우에는 생성된 머신 코드에서 브랜치가 생기지 않는다고 볼 수 있음
컴파일러 출력이 보여주는 차이
- 원래 GLSL 삼항 연산자 코드는 AMD 컴파일러에서 비교와 조건부 마스크 명령으로 변환됨
- 비교:
v_cmp_gt_f32,v_cmp_ngt_f32 - 조건부 마스크:
v_cndmask_b32
- 비교:
- Microsoft 컴파일러 출력도 같은 구조를 보임
- 비교:
lt - 조건부 이동:
movc
- 비교:
- 두 컴파일러 출력 모두 jump/branch 명령이 없음
step() 방식이 더 비싸지는 이유
step()기반 방식은 먼저 조건부 이동으로0.0또는1.0마스크를 만든 뒤, 여러 후보 결과를 곱셈과 덧셈으로 마스킹함- 원래 코드는 필요한 값을 직접 조건부 이동하므로, 마스크 생성과 산술 합성이 추가되는
step()방식보다 낭비가 적음 - 다양한 하드웨어에서
step()기반 버전은 원래 버전보다 훨씬 느리게 측정될 수 있음 - 예시 코드의 일부
abs()GLSL 호출은 별도 GPU 명령이 아니라 명령 modifier로 들어가며, 이런 경우abs()호출은 무료에 가깝게 볼 수 있음 float a = mix(b, c, step(y, x));를float a = x < y ? b : c;의 최적화로 권장하는 것은 잘못된 접근임