DirectX 10 셰이더의 if 문을 피하십시오?


14

if 문은 셰이더에서 피해야한다고 들었습니다. 문의 두 부분이 모두 실행되고 잘못된 것이 삭제되기 때문에 (성능에 해를 끼칩니다).

DirectX 10에서 여전히 문제가 있습니까? 누군가 나에게 말했다, 그것의 오른쪽 지점 만 실행됩니다.

설명을 위해 코드가 있습니다.

float y1 = 5; float y2 = 6; float b1 = 2; float b2 = 3;

if(x>0.5){
    x = 10 * y1 + b1;
}else{
    x = 10 * y2 + b2;
}

더 빠르게 만드는 다른 방법이 있습니까?

그렇다면 어떻게해야합니까?

두 가지 모두 비슷해 보이지만 유일한 차이는 "상수"의 값입니다 ( y1, y2, b1, b2픽셀 셰이더의 모든 픽셀에서 동일 함).


1
솔직히 말해서, 그것은 매우 조기에 최적화 된 것이므로 코드를 벤치마킹하고 100 % 쉐이더가 병목 현상이 될 때까지 변경하지 마십시오.
pwny

답변:


17

마이크로 최적화 셰이더에 대한 많은 규칙은 벡터 확장 기능이있는 기존 CPU와 동일합니다. 다음은 몇 가지 힌트입니다.

  • 내장 테스트 기능이 있습니다 ( test, lerp/ mix)
  • 두 개의 벡터를 추가하면 두 개의 부동 소수점을 추가하는 것과 동일한 비용이
  • 스위 즐링은 무료입니다

현대 하드웨어에서는 지점이 예전보다 저렴하다는 것이 사실이지만 가능하다면이를 피하는 것이 좋습니다. 스위 즐링 및 테스트 기능을 사용하면 테스트없이 셰이더를 다시 작성할 수 있습니다.

/* y1, y2, b1, b2 */
float4 constants = float4(5, 6, 2, 3);

float2 tmp = 10 * constants.xy + constants.zw;
x = lerp(tmp[1], tmp[0], step(x, 0.5));

사용 step하고하는 것은 lerp두 값 사이의 선택에 대한 매우 일반적인 관용구이다.


6

일반적으로 괜찮습니다. 셰이더는 정점 또는 픽셀 그룹으로 실행됩니다 (다른 공급 업체는 서로 다른 용어를 사용하므로이를 멀리합니다). 그룹의 모든 정점 또는 픽셀이 동일한 경로를 사용하는 경우 분기 비용은 무시할 수 있습니다.

또한 셰이더 컴파일러를 신뢰해야합니다. 작성한 HLSL 코드는 컴파일 할 바이트 코드 나 어셈블리를 직접 표현하는 것으로 보아서는 안되며 컴파일러는 해당 코드를 동등한 것으로 변환 할 수 있지만 분기는 피할 수 있습니다. 바람직한 전환). 반면에 컴파일러에서 분기 수행이 실제로 더 빠른 경로라고 판단하면 분기로 컴파일합니다. PIX 또는 유사한 도구로 생성 된 어셈블리를 보는 것이 여기에서 매우 유용 할 수 있습니다.

마지막으로 오래된 지혜는 여전히 여기에 있습니다. 프로파일을 작성하고 실제로 성능 문제인지 확인한 다음 이전이 아닌 문제를 해결하십시오. 뭔가를 가정 할 수 있습니다 성능 문제와 행동이 가정에 따라 단지 나중에 더 큰 문제의 큰 위험을 초래합니다.


4

Robert Rouhani가 게시 한 링크 / 문서에서 인용 :

"이전 아키텍처에서는 조건 코드 (예측)가 실제 분기를 에뮬레이트하는 데 사용됩니다. 이러한 아키텍처로 컴파일 된 If-then 명령문은 모든 프래그먼트에 대해 취해진 분기 명령과 취하지 않은 분기 명령을 모두 평가해야합니다. 분기 조건이 평가되고 조건 코드가 설정됩니다. 분기의 각 부분에있는 명령어는 결과를 레지스터에 기록하기 전에 조건 코드의 값을 확인해야하므로 결과적으로 가져온 분기의 명령어 만 출력을 작성하므로 이러한 아키텍처에서는 모든 분기에 비용이 많이 듭니다. 분기, 분기 조건 평가 비용 + 분기는 이러한 아키텍처에서 드물게 사용해야합니다. NVIDIA GeForce FX 시리즈 GPU는 프래그먼트 프로세서에서 조건 코드 분기 에뮬레이션을 사용합니다. "

mh01이 제안한대로 ( "PIX 또는 유사한 도구에서 생성 된 어셈블리를 보는 것이 여기에서 매우 유용 할 수 있습니다."), 컴파일러 도구를 사용하여 출력을 검사해야합니다. 내 경험에 따르면 nVidia의 Cg 도구 (Cg는 오늘날 크로스 플랫폼 기능으로 인해 여전히 널리 사용됩니다)는 의 GPU 보석 조건 코드 (예측) 언급 된 동작을 완벽하게 보여주었습니다 단락에 . 따라서 트리거 값에 관계없이 두 분기 모두 조각별로 평가되었으며 마지막에만 올바른 레지스트리가 출력 레지스트리에 배치되었습니다. 그럼에도 불구하고 계산 시간이 낭비되었습니다. 그때 나는, 그 분기의 의지 도움말 성능을 생각 특히 때문에 모든해당 셰이더의 조각은 올바른 지점을 결정하기 위해 균일 한 값을 사용했습니다. 의도 한대로 발생하지 않았습니다. 따라서 여기서 중요한 경고 (예를 들어, 우버 셰이더는 피하십시오 – 아마도 가장 큰 분기 지옥의 원인).


2

아직 성능 문제가 없다면 괜찮습니다. 상수와 비교하기위한 비용은 여전히 매우 저렴합니다. 다음은 GPU 분기에 대한 유용한 정보입니다. http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.html

어쨌든, 다음은 if 문보다 훨씬 나쁘고 읽기 쉽고 유지하기가 쉽지 않은 코드 스 니펫은 다음과 같습니다.

int fx = floor(x);
int y = (fx * y2) + ((1- fx) * y1);
int b = (fx * b2) + ((1 -fx) * b1);

x = 10 * y + b;

x가 range로 제한된다는 가정을하고 있습니다 [0, 1]. x> = 2 또는 x <0 인 경우 작동하지 않습니다.

스니핑하는 것은 x를 하나 0또는 10으로 변환하고 다른 하나에 0을 곱하고 다른 하나에 1을 곱하는 것입니다.


원래 테스트는 if(x<0.5)의 값 이므로 또는 fx이어야합니다 . round(x)floor(x + 0.5)
sam hocevar

1

분기없이 조건을 수행 할 수있는 여러 명령이 있습니다.

vec4 when_eq(vec4 x, vec4 y) {
  return 1.0 - abs(sign(x - y));
}

vec4 when_neq(vec4 x, vec4 y) {
  return abs(sign(x - y));
}

vec4 when_gt(vec4 x, vec4 y) {
  return max(sign(x - y), 0.0);
}

vec4 when_lt(vec4 x, vec4 y) {
  return max(sign(y - x), 0.0);
}

vec4 when_ge(vec4 x, vec4 y) {
  return 1.0 - when_lt(x, y);
}

vec4 when_le(vec4 x, vec4 y) {
  return 1.0 - when_gt(x, y);
}

또한 일부 논리 연산자;

vec4 and(vec4 a, vec4 b) {
  return a * b;
}

vec4 or(vec4 a, vec4 b) {
  return min(a + b, 1.0);
}

vec4 xor(vec4 a, vec4 b) {
  return (a + b) % 2.0;
}

vec4 not(vec4 a) {
  return 1.0 - a;
}

출처 : http://theorangeduck.com/page/avoiding-shader-conditionals

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.