== GLSL에서 분기를 유발합니까?


27

분기를 일으키는 원인과 GLSL에없는 것을 정확히 파악하려고합니다.

셰이더에서이 작업을 많이하고 있습니다.

float(a==b)

조건부 분기없이 if 문을 시뮬레이션하는 데 사용하지만 효과적입니까? 나는 지금 내 프로그램 어딘가에 if 문을 가지고 있지 않으며 루프도 없다.

편집 : 명확히하기 위해 코드에서 다음과 같은 작업을 수행합니다.

float isTint = float((renderflags & GK_TINT) > uint(0)); // 1 if true, 0 if false
    float isNotTint = 1-isTint;//swaps with the other value
    float isDarken = float((renderflags & GK_DARKEN) > uint(0));
    float isNotDarken = 1-isDarken;
    float isAverage = float((renderflags & GK_AVERAGE) > uint(0));
    float isNotAverage = 1-isAverage;
    //it is none of those if:
    //* More than one of them is true
    //* All of them are false
    float isNoneofThose = isTint * isDarken * isAverage + isNotTint * isAverage * isDarken + isTint * isNotAverage * isDarken + isTint * isAverage * isNotDarken + isNotTint * isNotAverage * isNotDarken;
    float isNotNoneofThose = 1-isNoneofThose;

    //Calc finalcolor;
    finalcolor = (primary_color + secondary_color) * isTint * isNotNoneofThose + (primary_color - secondary_color) * isDarken * isNotNoneofThose + vec3((primary_color.x + secondary_color.x)/2.0,(primary_color.y + secondary_color.y)/2.0,(primary_color.z + secondary_color.z)/2.0) * isAverage * isNotNoneofThose + primary_color * isNoneofThose;

편집 : 왜 분기를 원하지 않는지 알고 있습니다. 나는 분기가 무엇인지 안다. 분기에 대해 아이들에게 가르치는 것이 기쁘지만 부울 연산자 (및 비트 연산)에 대해 나 자신을 알고 싶습니다.

답변:


42

GLSL에서 분기를 일으키는 원인은 GPU 모델 및 OpenGL 드라이버 버전에 따라 다릅니다.

대부분의 GPU는 분기 비용이없는 "두 값 중 하나를 선택하십시오"형식의 작업 인 것 같습니다.

n = (a==b) ? x : y;

때로는 다음과 같은 것들도 있습니다 :

if(a==b) { 
   n = x;
   m = y;
} else {
   n = y;
   m = x;
}

분기 벌금없이 몇 가지 선택 값 작업으로 축소됩니다.

일부 GPU / 드라이버는 두 값 사이의 비교 연산자에 약간의 페널티가 있지만 0에 비해 비교 작업이 더 빠릅니다.

더 빠른 곳 :

gl_FragColor.xyz = ((tmp1 - tmp2) != vec3(0.0)) ? E : tmp1;

(tmp1 != tmp2)직접 비교 하는 것이 아니라 이것은 GPU와 드라이버에 따라 매우 다르므로 매우 구체적인 GPU를 타겟팅하지 않는 한 비교 연산을 사용하지 않는 것이 좋습니다. 다른 드라이버가 더 긴 형식에 문제가있을 수 있으므로 OpenGL 드라이버에 최적화 작업을 남겨 두십시오. 더 간단하고 읽기 쉬운 방법으로 더 빨라집니다.

"분기"가 항상 나쁜 것은 아닙니다. 예를 들어 OpenPandora에서 사용되는 SGX530 GPU에서이 scale2x 셰이더 (30ms)는 다음과 같습니다.

    lowp vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;
    lowp vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;
    lowp vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;
    lowp vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;
    lowp vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;
    if ((D - F) * (H - B) == vec3(0.0)) {
            gl_FragColor.xyz = E;
    } else {
            lowp vec2 p = fract(pos);
            lowp vec3 tmp1 = p.x < 0.5 ? D : F;
            lowp vec3 tmp2 = p.y < 0.5 ? H : B;
            gl_FragColor.xyz = ((tmp1 - tmp2) != vec3(0.0)) ? E : tmp1;
    }

이 동등한 쉐이더 (80ms)보다 극적으로 빠릅니다.

    lowp vec3 E = texture2D(s_texture0, v_texCoord[0]).xyz;
    lowp vec3 D = texture2D(s_texture0, v_texCoord[1]).xyz;
    lowp vec3 F = texture2D(s_texture0, v_texCoord[2]).xyz;
    lowp vec3 H = texture2D(s_texture0, v_texCoord[3]).xyz;
    lowp vec3 B = texture2D(s_texture0, v_texCoord[4]).xyz;
    lowp vec2 p = fract(pos);

    lowp vec3 tmp1 = p.x < 0.5 ? D : F;
    lowp vec3 tmp2 = p.y < 0.5 ? H : B;
    lowp vec3 tmp3 = D == F || H == B ? E : tmp1;
    gl_FragColor.xyz = tmp1 == tmp2 ? tmp3 : E;

벤치 마크 할 때까지 특정 GLSL 컴파일러 또는 특정 GPU의 성능을 미리 알 수 없습니다.


지점을 추가하기 위해 (이 부분에 대해 실제 타이밍 번호와 셰이더 코드가 없어도) 현재 일반 테스트 하드웨어로 사용합니다.

  • 인텔 HD 그래픽 3000
  • 인텔 HD 405 그래픽
  • nVidia GTX 560M
  • 엔비디아 GTX 960
  • AMD 라데온 R7 260X
  • 엔비디아 GTX 1050

테스트 할 다양한 다른 일반적인 GPU 모델입니다.

Windows, Linux 독점 및 Linux 오픈 소스 OpenGL 및 OpenCL 드라이버로 각각 테스트합니다.

그리고 하나의 특정 GPU / 드라이버 콤보에 대해 GLSL 쉐이더 (위의 SGX530 예제에서와 같이) 또는 OpenCL 작업을 미세 최적화하려고 할 때마다 다른 GPU / 드라이버 중 하나 이상의 성능이 동일하게 저하됩니다.

따라서 높은 수준의 수학적 복잡성을 명확하게 줄이고 (예 : 5 개의 동일한 부분을 단일 왕복으로 변환하고 5 개의 곱셈으로 변환) 텍스처 룩업 / 대역폭을 줄이는 것 외에는 시간 낭비 일 가능성이 높습니다.

모든 GPU는 다른 GPU와 너무 다릅니다.

특정 GPU가있는 (a) 게임 콘솔에서 특별히 작업하는 경우에는 다른 이야기가 될 것입니다.

이것의 다른 (작은 게임 개발자에게는 중요하지 않지만 여전히 주목할만한) 측면은 컴퓨터 GPU 드라이버가 언젠가 자동으로 쉐이더를 ( 그 게임이 충분히 유명 해지면 ) 특정 GPU에 최적화 된 커스텀 재 작성된 것으로 대체 할 수 있다는 것입니다. 모든 것이 당신을 위해 일하는 것.

벤치 마크로 자주 사용되는 인기있는 게임에 대해이 작업을 수행합니다.

또는 플레이어에게 셰이더에 대한 액세스 권한을 부여하여 쉽게 직접 편집 할 수 있도록하는 경우 일부 사용자는 자신의 이익을 위해 몇 가지 추가 FPS를 압박 할 수 있습니다.

예를 들어 Oblivion을 위해 팬이 만든 셰이더 및 텍스처 팩이있어 거의 재생이 불가능한 하드웨어에서 프레임 속도를 크게 높일 수 있습니다.

마지막으로, 셰이더가 복잡해지면 게임이 거의 완료되고 다른 하드웨어에서 테스트를 시작하면 다양한 GPU에서 작동하도록 셰이더를 수정하는 것만으로 바쁠 것입니다. 그 정도로 최적화 할 시간이 있습니다


"또는 플레이어에게 쉐이더에 대한 액세스 권한을 부여하여 쉽게 편집 할 수있게한다면 ..."위에서 언급 한 이후, wallhack 쉐이더 등에 대한 접근 방법은 무엇입니까? 명예 시스템, 검증, 보고서 ...? 최대 / 최소 / 확장 가능한 현실감, 익스플로잇 등에 대한 입장은 플레이어와 모더가 함께 검토, 협업 등을 장려해야하기 때문에 로비는 동일한 셰이더 / 자산으로 제한되는 아이디어가 마음에 듭니다. 이것이 게리의 모드가 일했던 방식임을 기억하기 위해, 나는 루프에서 벗어났습니다.
John P

1
@JohnP 보안 현명하게 클라이언트가 손상되지 않았다고 가정하면 작동하지 않습니다. 물론 사람들이 셰이더를 편집하는 것을 원하지 않으면 노출 할 점이 없지만 보안에 큰 도움이되지는 않습니다. wallhacks와 같은 것을 탐지하는 전략은 클라이언트 쪽을 엉망으로 만드는 것을 낮은 첫 번째 장벽으로 취급해야하며 플레이어가 탐지 할 수없는 불이익을 초래하지 않으면이 답변에서와 같이 가벼운 모딩을 허용하면 더 큰 이점이있을 수 있습니다 .
큐빅

8
@JohnP 플레이어가 벽을 통해 보지 못하게하려면 서버가 벽 뒤에있는 것에 대한 정보를 보내지 않도록하십시오.
Polygnome

1
그게 다야-나는 어떤 이유로 든 그것을 좋아하는 플레이어 간의 벽 해킹에 반대하지 않습니다. 그러나 플레이어로서 나는 여러 가지 AAA 타이틀을 버렸습니다. 왜냐하면 그들은 돈 / XP / 등 동안 미적 모더의 예를 만들었 기 때문입니다. 해커들은 쫓겨나 지 않았으며 (결제 할만큼 실망한 사람들로부터 돈을 벌었습니다), 보고서 및 항소 시스템을 부족하게하고 자동화했으며, 게임이 살아 남기 위해 간호했던 서버의 수에 따라 게임이 진행되고 죽도록했습니다. 나는 개발자와 선수 모두보다 더 분산 된 접근법이 있기를 바랐습니다.
John P

아니요, 아무 데나 인라인하지 않습니다. 난 그냥 float (boolean statement) * (something)
Geklmintendon't at Awesome

7

@Stephane Hockenhull의 답변은 거의 모든 하드웨어에 의존 할 것입니다.

그러나 하드웨어에 의존 할 수있는 방법 과 분기가 왜 문제가되는지, 분기 발생할 때 GPU가이면에서 수행하는 작업의 예를 몇 가지 보여 드리겠습니다 .

저의 초점은 주로 Nvidia에 중점을두고 있으며 저수준 CUDA 프로그래밍 경험이 있으며 PTX ( SPIR-V 와 같은 CUDA 커널IR 이지만 Nvidia 용 IR )가 생성되는 것을보고 특정 변경을 수행하는 벤치 마크를 확인하십시오.

GPU 아키텍처에서 분기하는 것이 왜 그렇게 중요한가?

처음에 분기하는 것이 왜 나쁜가요? GPU가 왜 처음부터 분기하지 않으려 고합니까? GPU는 일반적으로 스레드가 동일한 명령어 포인터를 공유하는 방식을 사용하기 때문 입니다. GPU는 SIMD 아키텍처를 따릅니다일반적으로 그 세분성은 변경 될 수 있지만 (즉, Nvidia의 경우 32 스레드, AMD의 경우 64 스레드 등) 일정 수준의 스레드 그룹이 동일한 명령 포인터를 공유합니다. 즉, 동일한 스레드에서 동일한 문제를 해결하려면 해당 스레드가 동일한 코드 줄을 봐야합니다. 그들이 어떻게 동일한 코드 줄을 사용하고 다른 일을 할 수 있는지 물어볼 수 있습니까? 레지스터에서 다른 값을 사용하지만 해당 레지스터는 여전히 전체 그룹에서 동일한 코드 줄로 사용됩니다. 사건이 멈 추면 어떻게 되나요? (IE 브랜치?) 프로그램이 실제로 주변에 방법이 없다면 그룹을 분할합니다 (Nvidia 32 스레드 스레드 번들은 Warp 이며 AMD 및 병렬 컴퓨팅 학계에서는 Wavefront 라고합니다.)를 두 개 이상의 다른 그룹으로

결국 두 줄의 다른 코드 줄만 있으면 작업 스레드가 두 그룹으로 나뉩니다 (여기서 하나를 워프라고 부릅니다). 워프 크기가 ​​32 인 Nvidia 아키텍처를 가정 해 봅시다.이 스레드의 절반이 분기 되면 32 개의 활성 스레드가 차지하는 2 개의 워프가 생겨 계산에서 퍼트 엔드까지 절반의 효율을 얻습니다. 많은 아키텍처에서 GPU는 동일한 명령어 포스트 브랜치에 도달하면 스레드를 단일 워프로 수렴 하여이 문제를 해결하려고 시도 하거나 컴파일러가 GPU에 스레드를 수렴하거나 시도하도록 동기화 지점을 명시 적으로 지정합니다.

예를 들면 다음과 같습니다.

if(a)
    x += z * w;
    q >>= p;
else if(c)
    y -= 3;
r += t;

스레드는 분기 할 수있는 강력한 가능성을 가지므로 (다른 명령어 경로) 이러한 경우 r += t;명령어 포인터가 다시 동일한 위치에 수렴이 발생할 수 있습니다 . 분기가 두 개 이상인 분기에서도 발생할 수 있으므로 워프 활용도가 훨씬 낮아집니다. 분기 4 개는 32 개의 스레드가 4 개의 워프 (25 % 처리량 활용)로 분할됨을 의미합니다. 그러나 25 %가 전체 프로그램에서 처리량을 유지하지 않기 때문에 컨버전스는 이러한 문제 중 일부를 숨길 수 있습니다.

덜 복잡한 GPU에서는 다른 문제가 발생할 수 있습니다. 분기하는 대신 모든 분기를 계산 한 다음 끝에 출력을 선택합니다. 이는 분산과 동일하게 보일 수 있지만 (1 / n 처리량 사용률이 있음) 복제 방법에는 몇 가지 주요 문제가 있습니다.

하나는 전력 사용량이며, 분기가 발생할 때 훨씬 더 많은 전력을 사용하고 있습니다. 이는 모바일 GPU에 좋지 않습니다. 두 번째는 같은 워프의 스레드가 다른 경로를 취하여 다른 명령 포인터 (파스칼과 공유 됨)를 가질 때 엔비디아 gpus에서만 발산이 발생한다는 것입니다. 따라서 Nvidia GPU가 32의 배수로 발생하거나 수십 번의 단일 워프에서만 발생하는 경우 여전히 분기가 가능하고 처리량 문제가 발생하지 않습니다. 분기가 발생할 가능성이 높으면 더 적은 수의 스레드가 발산 할 가능성이 높으며 분기 문제가 발생하지 않습니다.

또 다른 작은 문제는 GPU를 CPU와 비교할 때 종종 예측 메커니즘과 다른 강력한 분기 메커니즘이 없기 때문에 메커니즘이 차지하는 하드웨어 의 양이 많기 때문에 현대 GPU에서 종종 채워지지 않는 것을 볼 수 있습니다 .

실제 GPU 아키텍처 차이 예

이제 Stephanes를 예로 들어 두 가지 이론적 아키텍처의 분기없는 솔루션에 대한 어셈블리의 모습을 살펴 보겠습니다.

n = (a==b) ? x : y;

Stephane이 말했듯이, 디바이스 컴파일러가 브랜치를 만나면 브랜치 페널티가없는 요소를 "선택"하는 명령을 사용할 수 있습니다. 이것은 일부 장치에서 다음과 같이 컴파일 될 것임을 의미합니다.

cmpeq rega, regb
// implicit setting of comparison bit used in next part
choose regn, regx, regy

선택 명령이없는 다른 사람들에게는 다음과 같이 컴파일 될 수 있습니다.

n = ((a==b))* x + (!(a==b))* y

다음과 같이 보일 수 있습니다.

cmpeq rega regb
// implicit setting of comparison bit used in next part
mul regn regcmp regx
xor regcmp regcmp 1
mul regresult regcmp regy
mul regn regn regresult

이것은 분기가없고 동등하지만 더 많은 지시를받습니다. Stephanes 예제는 각 시스템에서 컴파일 될 가능성이 높으므로 첫 번째 아키텍처의 컴파일러가 대신 두 번째 형식으로 컴파일하기로 결정할 수 있기 때문에 직접 분기를 제거하기 위해 수학을 수동으로 계산하려고 시도하는 것은 의미가 없습니다. 더 빠른 형태.


5

@Stephane Hockenhull의 답변에 명시된 모든 내용에 동의합니다. 마지막 지점에서 확장하려면

벤치 마크 할 때까지 특정 GLSL 컴파일러 또는 특정 GPU의 성능을 미리 알 수 없습니다.

물론 그렇습니다. 또한, 나는 이런 종류의 질문이 자주 나오는 것을 봅니다. 그러나 실제로는 조각 쉐이더가 성능 문제의 원인이되는 것을 거의 보지 못했습니다. GPU에서 너무 많은 상태 읽기, 너무 많은 버퍼 교환, 단일 드로우 콜에서 너무 많은 작업 등과 같은 다른 요인으로 인해 문제가 발생하는 것이 훨씬 일반적입니다.

다시 말해, 셰이더 마이크로 최적화에 대해 걱정하기 전에 전체 앱을 프로파일 링하고 셰이더가 속도 저하의 원인인지 확인하십시오.

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