CODEONWORT

큰 쓰레드 그룹을 이용한 GPU 점유율과 리소스 사용량 최적화 본문

Season 2

큰 쓰레드 그룹을 이용한 GPU 점유율과 리소스 사용량 최적화

codeonwort 2020. 11. 23. 23:25

gpuopen.com/learn/optimizing-gpu-occupancy-resource-usage-large-thread-groups/

 

Optimizing GPU occupancy and resource usage with large thread groups - GPUOpen

Sebastian Aaltonen, co-founder of Second Order Ltd, talks about how to optimize GPU occupancy and resource usage of compute shaders that use large thread groups.

gpuopen.com

TODO

  • Shader Model 6의 wave intrinsic과 연관있는지 조사
  • 내가 작성했던 컴퓨트 셰이더들 최적화
    • GCN 아키텍처를 가정한 글이여서 내 그래픽카드들의 아키텍처에 대한 화이트 페이퍼를 읽어봐야 한다.
    • RTX 2070 Super : Turing (TU104)
    • RX 5500 XT : RDNA

Second Order LTD 공동 설립자이자 유비소프트 시니어 렌더링 리드였던 Sebastian Aaltonen 기고글에 것을 환영합니다. Second Order 최근에 게임인 Claybook 발표했습니다. Claybook 아주 재밌어보일 아니라, 렌더러를 특이한 방식으로 사용하여 고유의 모양새를 빚어냈습니다. Claybook 확인해보세요.

 

Sebastian Claybook 작업하며 직면한 흥미로운 문제를 다룹니다. 쓰레드 그룹을 사용하는 컴퓨트 셰이더들의 GPU 점유율(occupancy) 리소스 사용을 어떻게 최적화할까요?

쓰레드 그룹의 점유율과 리소스 사용량

컴퓨트 셰이더를 때는 쓰레드 그룹 사이즈가 퍼포먼스에 주는 영향을 고려하는 것이 중요합니다. 제한된 레지스터 공간, 메모리 레이턴시, SIMD 점유율은 각각 셰이더 퍼포먼스에 다른 방식으로 영향을 줍니다. 글에서는 올바르게 적용하면 퍼포먼스를 극단적으로 올려주는 잠재적 퍼포먼스 이슈, 기법, 최적화를 논의합니다. 쓰레드 그룹이 경우의 문제에 초점을 맞추지만, 팁은 일반적인 상황에서도 유용합니다.

배경지식

DirectX 11 셰이더 모델 5 컴퓨트 셰이더 명세(2009) 따르면 쓰레드 그룹마다 가용한 최대 메모리는 32 KiB이고, 최대 워크그룹 크기는 1024 쓰레드입니다. 최대 레지스터 개수에 대해 명시된 것은 없고, 컴파일러는 레지스터가 모자랄 경우 메모리를 침범(spilling) 있습니다. 하지만 메모리 레이턴시 때문에 이러한 침범은 퍼포먼스에 심각한 영향을 끼치며, 프로덕션 코드에서는 피해야 합니다.

 

현대 AMD GPU 단일 컴퓨트 유닛(CU)에서 1024 쓰레드 그룹 2개를 동시에 실행할 있습니다. 하지만 점유율을 최대화하려면 셰이더들은 레지스터와 LDS 사용을 최소화하여 모든 쓰레드에 필요한 리소스가 CU 안에 들어갈 있게 해야 합니다.

AMD GCN 컴퓨트 유닛(CU)

다음은 GCN 컴퓨트 유닛의 아키텍처입니다.

 

GCN CU SIMD 4개를 포함하며 SIMD 32 비트 VGPR(Vector General-Purpose Register)들의 64 KiB 레지스터 파일을 가지므로 CU마다 VGPR 65536 있습니다. 모든 CU 32 비트 SGPR(Scalar General-Purpose Register)들을 포함하는 레지스터 파일도 가집니다. GCN3 이전에는 CU SGPR 512 포함했고, GCN3부터 개수는 800개로 늘어났습니다. 따라서 CU마다 SGPR 3200 12.5 KiB만큼 있습니다.

 

CU 실행하는 작업의 가장 작은 단위는 웨이브라고 부르며, 웨이브는 쓰레드 64개를 포함합니다. CU 내의 4개의 SIMD마다 웨이브 10개까지 동시에 스케줄링할 있습니다. CU 웨이브 하나를 일시정지하고 메모리 연산을 기다리며 다른 웨이브를 실행할 수도 있습니다. 이러면 레이턴시를 감추고 CU 컴퓨트 리소스 사용을 극대화할 있습니다.

 

SIMD VGPR 파일의 크기는 중요한 제한을 만듭니다. SIMD VGPR들은 활성 웨이브들의 쓰레드들 간에 균등 배분됩니다. 셰이더가 가용한 것보다 많은 VGPR 요구하면, SIMD 최적 개수의 웨이브를 실행할 없습니다. 따라서 GPU 주어진 시간에 수행할 있는 병렬 작업의 척도인 점유율이 떨어지게 됩니다.

 

GCN CU 64 KiB Local Data Share(LDS) 가집니다. LDS 컴퓨트 셰이더 쓰레드 그룹들의 groupshared data 보관하는데 사용됩니다. Direct3D 단일 쓰레드 그룹이 사용할 있는 groupshared data 양을 32 KiB 제한합니다. 따라서 CU에서 LDS 최대로 활용하려면 적어도 그룹을 실행해야 합니다.

큰 쓰레드 그룹의 리소스 목표

글의 예제 셰이더는 쓰레드 그룹 크기가 1024 복잡한 GPGPU physics solver입니다. 셰이더는 그룹 크기와 groupshared 메모리를 최대로 사용합니다. 그룹 크기로부터 이득을 보는 이유는, groupshared 메모리를 여러 패스간 임시 저장소로 활용해서 물리 제약을 해결하기 때문입니다. 쓰레드 그룹 크기가 크면 임시 결과를 전역 메모리에 기록할 필요 없이 많은 island들을 처리할 있습니다.

 

1024 쓰레드의 그룹을 효율적으로 실행하기 위해 반드시 만족해야 하는 리소스 목표를 논의해보겠습니다.

  • 레지스터: GPU 채우려면 CU마다 1024 쓰레드의 그룹 2개를 할당받아야 합니다. CU 전체에서 가용한 65536 VGPR 쓰레드는 최대 32개의 VGPR 요구할 있습니다.
  • groupshared 메모리: GCN 64 KiB LDS 가집니다. groupshared 메모리 32 KiB 완전히 활용해도 CU마다 그룹 개를 넣을 있습니다.

셰이더가 한계를 초과하면 CU 동시에 그룹을 실행하는데 필요한 리소스가 충분치 않습니다. VGPR 32개라는 목표는 이루기 어렵습니다. 먼저 목표를 달성하지 못했을 일어나는 문제를 논의하고, 해당 문제를 해결하는 방법, 그리고 아예 회피하는 방법을 알아봅니다.

문제: CU 단일 쓰레드 그룹

애플리케이션이 최대 그룹 크기인 쓰레드 1024개를 사용하지만 셰이더가 VGPR 40개를 요구하는 상황을 생각해보죠. 경우 CU마다 쓰레드 그룹이 하나만 실행될 것입니다. 그룹, 쓰레드 2048개를 실행하려면 VGPR 81920 필요한데, CU 가용한 65536개보다 한참 많습니다.

 

쓰레드 1024개는 쓰레드 64개로 이루어진 웨이브 16개로 묶여 SIMD마다 균등 분배되고, 따라서 SIMD마다 웨이브 4개가 할당됩니다. 최적의 점유율과 레이턴시 은닉을 위해서는 웨이브 10개가 필요하므로 웨이브 4개의 점유율은 겨우 40% 입니다. 이러면 GPU 잠재적인 레이턴시 은닉이 크게 저하되어 SIMD 활용도가 떨어집니다.

 

쓰레드 1024개의 그룹이 최대 32 KiB LDS 사용한다고 가정하겠습니다. 그룹만 실행 중일 때는 LDS 50% 활용되지 않는데, 번째 쓰레드 그룹을 위해 예약되었지만 레지스터 압박pressure 때문에 번째 그룹이 없기 때문입니다. 레지스터 파일 사용량은 쓰레드마다 VGPR 40개로 VGPR 40960 160 KiB 입니다. 그러므로 CU 레지스터 파일의 96 KiB (37.5%) 낭비되고 있는 것입니다.

 

이렇게 최대 크기의 쓰레드 그룹을 사용하면, VGPR 예산을 초과해서 그룹만 CU 들어갈 경우 GPU 리소스 활용도가 나빠집니다.

 

그룹 크기를 결정할 때는 GPU 리소스 라이프사이클을 고려하는게 중요합니다.

 

GPU 쓰레드 그룹을 위한 모든 리소스를 동시에 할당하고 해제합니다. 그룹 실행을 시작하기 전에 레지스터, LDS, 웨이브를 모두 할당해야 하고 그룹의 마지막 웨이브가 실행을 끝내면 모든 그룹 리소스를 해제해야 합니다. 따라서 쓰레드 그룹만 CU 들어가면 할당과 해제가 겹치지 않는데, 그룹을 시작하기 전에 이전 그룹이 끝나기를 기다려야 하기 때문입니다. 메모리 레이턴시는 예측할 없기 때문에 그룹 웨이브들은 서로 다른 시간에 종료됩니다. 이전 그룹의 모든 웨이브가 완료되기 전에는 다음 그룹의 웨이브들을 시작할 없기 때문에 점유율이 감소합니다.

 

쓰레드 그룹은 LDS 많이 사용하는 경향이 있습니다. LDS 접근은 배리어를 통해 동기화됩니다. (HLSL에서는 GroupMemoryBarrierWithGroupSync 사용) 배리어는 동일 그룹 모든 웨이브가 시점에 도달할 때까지 실행을 막습니다. 이상적으로는 CU 배리어를 기다리는 동안 다른 쓰레드 그룹을 실행할 있습니다.

 

불행히도 우리 상황에서는 쓰레드 그룹이 하나만 돌고 있습니다. CU에서 그룹만 돌고 있을 때는 동일한 일부 셰이더 명령 집합에 대해 배리어가 모든 웨이브를 제한합니다. 이런 명령어 혼합은 배리어 사이에서 monotonous하기 마련이며, 따라서 쓰레드 그룹의 모든 웨이브가 메모리를 동시에 로드하곤 합니다. 배리어는 셰이더의 이후 독립적인 부분으로 전진하는 것을 막기 때문에, CU 유용한 ALU 작업을 시켜 메모리 레이턴시를 감출 기회는 없습니다.

해법: CU 쓰레드 그룹

CU마다 쓰레드 그룹을 가지면 이런 문제들이 완화됩니다. 그룹은 서로 완료 시간이 다르고 서로 다른 시간에 다른 배리어에 부딪혀서, 명령어 혼합을 개선하고 점유율 감소 문제를 크게 완화합니다. SIMD 활용률도 높아져서 레이턴시 은닉의 기회가 많아집니다.

 

최근에 저는 1024 쓰레드 그룹 셰이더를 최적화했습니다. 기존에는 VGPR 48 써서 CU마다 그룹만 실행되고 있었는데, VGPR 사용을 32개로 줄이자 플랫폼에서 다른 최적화 없이 퍼포먼스가 50% 향상되었습니다.

 

CU마다 쓰레드 그룹 크기를 최대로 하여 그룹을 넣는 것이 최선입니다. 하지만 그룹을 써도 점유율 변동을 완전히 없앨 수는 없습니다. 쓰레드 그룹을 선택하기 전에 장단점을 분석하는게 중요합니다.

언제 큰 쓰레드 그룹을 사용해야 하는가

문제를 푸는 가장 쉬운 방법은 문제를 아예 피해가는 것입니다. 제가 언급한 많은 문제는 작은 쓰레드 그룹을 써서 해결할 있습니다. 여러분의 셰이더에 LDS 필요 없다면 쓰레드 그룹을 사용할 이유가 전혀 없습니다.

 

LDS 필요 없으면 그룹 크기를 64 ~ 256 쓰레드 사이에서 선택해야 합니다. AMD 그룹 크기 256 기본값으로 추천하는데, AMD 작업 분배 알고리즘에 최적이기 때문입니다. 그렇지만 단일 웨이브 64 쓰레드의 그룹들도 나름의 쓸모가 있습니다. GPU 웨이브가 끝나는 즉시 리소스를 해제할 있으며, 웨이브 전체가 lock step으로 진행된다는 것이 보장되기 때문에 AMD 셰이더 컴파일러는 모든 메모리 배리어를 없앨 있습니다. 저희 게임인 Claybook 렌더링하기 위해 추적 알고리즘처럼 루프가 크게 변동하는 작업은 단일 웨이브 워크 그룹으로부터 가장 이득을 보는 경우입니다.

 

하지만 LDS 다른 셰이더 스테이지에는 없는 컴퓨트 셰이더만의 유용한 기능이며, 올바르게 사용하면 퍼포먼스를 크게 개선할 있습니다. 쓰레드마다 별도의 load 수행하는 대신 공용 데이터를 LDS 한번 불러오면 중복된 메모리 접근이 크게 감소합니다. LDS 효율적으로 사용하면 L1 캐시 미스와 thrashing 줄어들 있고, 메모리 레이턴시와 파이프라인 스톨도 마찬가지입니다.

 

1024 쓰레드 그룹에서 겪는 문제들은 그룹 크기를 줄이면 상당히 완화됩니다. 그룹 크기를 512 쓰레드로 줄이기만 해도 충분히 좋아집니다. CU에는 최대 5개의 그룹이 들어갑니다. 하지만 좋은 점유율을 달성하려면 여전히 VGPR 32개라는 빠듯한 제한을 맞춰야 합니다.

이웃 처리

많은 포스트 프로세싱 필터들(TAA, 블러, 팽창dilation, 복원reconstruction ) 요소의 가장 가까운 이웃들을 알아야 합니다. 이런 필터들은 LDS 써서 중복된 메모리 접근을 없애면 퍼포먼스가 최대 30%까지 크게 향상됩니다.

 

2D 입력이 들어오고 쓰레드가 픽셀 하나를 셰이딩한다고 가정하면, 쓰레드마다 초기값 외에 인접한 여덟 픽셀에 접근해야 합니다. 인접한 각각의 픽셀도 해당 쓰레드의 초기값이 필요합니다. 그리고 이웃 쓰레드마다 가운데 값이 필요합니다. 이러면 중복된 load 많아집니다. 일반적으로 픽셀을 필요로 하는 쓰레드는 9개입니다. LDS 쓰지 않으면 픽셀을 쓰레드마다 한번씩 아홉 로드해야 합니다.

 

필요한 데이터를 먼저 LDS 로드하고 이후의 모든 메모리 로드를 LDS 로드로 교체하면 전역 디바이스 메모리에 대한 접근과 캐시 thrashing 가능성을 크게 줄입니다.

 

LDS 그룹 내에서 공유할 있는 데이터가 많을 가장 효과적입니다. 이웃이 많을 수록, 따라서 그룹 크기가 수록 많은 데이터를 의미있게 공유할 있고 많은 중복 로드가 제거됩니다.

 

1-픽셀 neighborhood 직사각형 2D 쓰레드 그룹을 생각해봅시다. 그룹은 그룹 영역 모든 픽셀에 더해 경계조건을 위해 픽셀 넓은 영역을 로드해야 합니다. 변이 X 정사각 영역은 내부 픽셀이 X^2, 경계 픽셀이 4X+4개입니다. 내부 페이로드는 제곱으로 증가하지만 경계 오버헤드 읽지만 쓰지는 않는 픽셀은 선형 증가합니다.

 

1픽셀 경계의 8x8 그룹은 내부 픽셀이 64, 경계 픽셀이 36개로 로드가 100번입니다. 오버헤드는 56%입니다.

 

이제 16x16 쓰레드 그룹을 보면, 페이로드는 256 픽셀이고 오버헤드는 68 경계 픽셀입니다. 페이로드 크기는 4배지만 오버헥드는 68 픽셀 뿐으로 27% 차지합니다. 그룹의 차원을 배로 늘려 오버헤드를 크게 단축했습니다. 최대 쓰레드 그룹 크기인 1024 쓰레드에서 32x32 정사각형의 오버헤드는 132 경계 픽셀로 로드의 13% 만을 차지합니다.

 

3D 그룹은 상황이 좋은데, 그룹 볼륨이 경계 영역보다 훨씬 빠르게 증가하기 때문입니다. 조그마한 4x4x4 그룹에서 페이로드는 요소 64개를 포함하지만 6x6x6 큐브인 표면 경계는 216 요소를 가져서 오버헤드가 70% 입니다. 하지만 8x8x8 그룹에서는 내부 픽셀이 512, 경계 영역이 488 픽셀로 오버헤드가 48% 입니다. 쓰레드 그룹 크기가 작으면 이웃 오버헤드가 크지만 쓰레드 그룹이 커질 수록 개선됩니다. 분명히 쓰레드 그룹은 나름의 쓸모가 있습니다.

LDS 이용한 멀티 패스 처리

많은 알고리즘이 멀티 패스입니다. 간단하게 구현하자면 중간 결과를 글로벌 메모리에 저장하면 되지만 메모리 대역폭을 크게 소모합니다.

 

때로는 문제의 독립적인 부분 혹은 "" 작아서, 중간 결과를 LDS 보관하는 식으로 문제를 여러 단계로 쪼갤 있습니다. 개별 컴퓨트 셰이더는 모든 필요한 단계를 수행하고 단계 사이에서 중간 결과를 LDS 기록합니다. 최종 결과만이 메모리에 기록됩니다.

 

physics solver들은 이런 접근법을 훌륭하게 적용합니다. Gauss-Seidel 같은 반복법은 모든 제약을 안정화하기 위해 여러 스텝이 필요합니다. 문제는 섬으로 쪼갤 있는데, 연결된 단일 바디를 이루는 모든 파티클을 동일 쓰레드 그룹에 할당하고 독립적으로 해결하는 것입니다. 연이은 패스들에서는 이전 패스에서 계산된 중간 데이터를 활용해 바디간 상호작용을 처리합니다.

VGPR 사용량 최적화하기

쓰레드 그룹을 사용하는 셰이더들은 복잡하기 마련입니다. 32 VGPR이라는 목표를 달성하기는 힘듭니다. 다음은 제가 지난 년간 배운 팁입니다.

스칼라 데이터

GCN 장치는 웨이브 쓰레드마다 다른 상태를 보관하는 벡터 (SIMD) 유닛과 웨이브 모든 쓰레드에 공통 단일 상태인 스칼라 유닛을 가집니다. SIMD 웨이브마다 자신만의 SGPR 파일을 가지는 스칼라 쓰레드 하나가 추가로 실행됩니다. 스칼라 레지스터들은 웨이브 전체를 위한 단일 값을 포함합니다. 따라서 SGPR 칩에서 저장 비용이 64 낮습니다.

 

GCN 셰이더 컴파일러는 스칼라 로드 명령을 자동으로 방출합니다. 로드 주소가 웨이브 불변임을 컴파일 시간에 있다면( 주소가 웨이브 64 쓰레드 모두에 동일하다면) 컴파일러는 같은 데이터를 웨이브마다 독립적으로 로드하지 않고 스칼라 로드를 만들어냅니다. 가장 흔한 웨이브 불변 데이터는 콘스탄트 버퍼와 리터럴 값입니다. 웨이브 불변 데이터에 기반한 정수 산술 결과 역시 웨이브 불변인데, 스칼라 유닛이 완전한 정수 명령 집합을 가지기 때문입니다. 이런 스칼라 명령들은 벡터 SIMD 명령들과 같이 발행되며 실행 시간 면에서 공짜나 마찬가지입니다.

 

컴퓨트 셰이더의 내장 입력값 SV_GroupID 또한 웨이브 불변입니다. 이는 중요한 점인데, 그룹에 한정된 데이터를 스칼라 레지스터로 옮겨서 VGPR 압박을 줄일 있기 때문입니다.

 

스칼라 로드 명령은 타입 있는 버퍼나 텍스처를 지원하지 않습니다. 컴파일러가 데이터를 VGPR 대신 SGPR 로드하게 만들려면 데이터를 ByteAddressBuffer 또는 StructuredBuffer에서 로드해야 합니다. 그룹에서 공통된 데이터를 저장하기 위해 타입 있는 버퍼나 텍스처를 사용하면 안됩니다. 2D/3D 자료구조에서 스칼라 로드를 수행하고 싶다면 주소를 직접 계산해야 합니다. 다행히 주소 계산도 효율적으로 동시 발생되는데, 스칼라 유닛이 완전한 정수 명령 집합을 가지기 때문입니다.

 

물론 SGPR 부족해질 수도 있지만 그럴 가능성은 희박합니다. 보통은 텍스처와 샘플러를 과도하게 쓰면 SGPR 예산을 초과하게 됩니다. 텍스처 디스크립터 하나는 SGPR 8개를 소모하는 반면 샘플러는 SGPR 4개를 소모합니다. DirectX 11 샘플러를 여러 텍스처에 사용하는 것을 허용합니다. 보통은 샘플러 하나만으로 충분합니다. 버퍼 디스크립터는 SGPR 4개를 사용합니다. 버퍼 로드와 텍스처 로드 명령은 샘플러를 사용하지 않기 때문에 필터링이 필요 없으면 사용해야 합니다.

 

예시: 쓰레드 그룹이 행렬 또는 프로젝션 행렬 같은 웨이브 불변 행렬을 이용해 위치값을 변환합니다. 여러분은 Buffer<float4> 로부터 4개의 타입 있는 로드 명령을 통해 4x4 행렬을 로드합니다. 데이터는 16 VGPR 저장됩니다. 이러면 이미 VGPR 예산의 반절을 쓰게 됩니다. 대신 ByteAddressBuffer로부터 4번의 Load4 해야 합니다. 컴파일러는 스칼라 로드를 생성하고, 행렬을 VGPR 대신 SGPR 저장합니다. VGPR 하나도 쓰지 않습니다.

불필요 데이터

동차 좌표는 3D 그래픽스에서 흔히 쓰입니다. 대부분의 경우 여러분은 W 성분이 0이나 1임을 알고 있습니다. 이런 경우 W 성분을 로드하거나 사용하지 마세요. 순전히 쓰레드마다 VGPR 1 낭비이며 ALU 명령을 많이 생성합니다.

 

비슷한 문맥에서 완전한 4x4 행렬은 투영에만 필요합니다. 모든 어파인 변환에는 기껏해야 4x3 행렬이면 충분하며 마지막 열은 (0, 0, 0, 1)입니다. 4x3 행렬은 4x4 행렬보다 VGPR, SGPR 4개씩 절약합니다.

비트 패킹

비트 패킹은 메모리를 절약하는 유용한 수단입니다. VGPR 여러분의 가장 귀중한 메모리입니다. VGPR 매우 빠르지만 턱없이 부족합니다. GCN 빠른 단일 사이클 비트 필드 추출과 삽입 연산을 제공합니다. 연산들을 통해 32비트 VGPR 안에 여러 데이터 조각을 저장할 있습니다.

 

예를 들어 2D 정수 좌표는 16b+16b 패킹될 있습니다. HLSL 16비트 부동소수점을 32비트 VGPR으로 패킹하거나 추출하는 명령들(f16tof32 f32tof16) 가집니다. 이것들은 GCN에서 full-rate입니다.

-> full-rate 최대 throughput 달성한다는 뜻인가?

 

여러분의 데이터가 이미 메모리에 비트 패킹되어있다면 바로 uint 레지스터나 LDS으로 로드하고 사용하기 전에는 언패킹하지 마세요.

불리언

GCN 컴파일러는 bool 변수들을 64비트 SGPR 저장하며, 비트마다 웨이브 lane 차지합니다. VGPR 비용은 0입니다. bool 흉내내기 위해 int float 사용하지 마세요. 최적화가 작동하지 않습니다.

 

SGPR 담을 있는 것보다 많은 bool 필요하면 bool 32개를 단일 VGPR 패킹하는 것을 고려해보세요. GCN 비트 필드를 빠르게 조작하기 위해 단일 사이클 비트 필드 추출/삽입을 지원합니다. 또한 reduction 비트필드 탐색을 위해 countbits(), firstbithigh()/firstbitlow() 사용할 있습니다. 이진 prefix-sum countbits() 통해 이전 비트들을 마스킹하고 셈으로써 효율적으로 구현할 있습니다.

 

bool 항상 양수인 float 부호 비트에도 저장할 있습니다. abs() saturate() GCN에서 공짜 함수입니다. 이것들은 이것들을 사용하는 명령들과 함께 실행되는 단순한 입출력 모디파이어입니다. 따라서 부호 비트에 저장된 bool 접근하는 것은 공짜입니다. 부호를 얻기 위해 HLSL sign() intrinsic 사용하지 마세요. 이러면 컴파일러 출력이 최적이 아닙니다. 부호 비트의 값을 보려면 값이 음수가 아닌지 테스트하는 것이 항상 빠른 방법입니다.

브랜치와 루프

컴파일러는 데이터 로드와 데이터를 사용하는 코드를 최대한 떨어뜨려서 메모리 레이턴시를 사이의 명령들을 통해 감추려 노력합니다. 불행히도 데이터는 로드와 실사용 사이에서는 VGPR 머물러야 합니다.

 

동적 루프를 통해 VGPR 생명 주기를 줄일 있습니다. 루프 카운터에 의존하는 로드 명령은 루프 밖으로 옮길 없습니다. VGPR 생명 주기는 루프 바디 내부로 한정됩니다.

 

여러분의 HLSL에서 [loop] attribute 써서 실제로 루프가 되도록 강제하세요. 불행히도 [loop] attribute 완벽 보장이 아닙니다. 셰이더 컴파일러는 필요한 이터레이션을 컴파일 시간에 있다면 여전히 루프를 unroll 있습니다.

16비트 레지스터

GCN3 16비트 레지스터 지원을 도입했습니다. Vega 지원을 확장해 16비트 연산을 2 빠르게 수행합니다. 정수와 부동소수점 모두 지원됩니다. 16비트 레지스터는 단일 VGPR 패킹됩니다. 이는 32비트 정밀도가 전부 필요 없을 VGPR 공간을 절약하는 쉬운 방법입니다. 16비트 정수는 2D/3D 주소 계산에 딱입니다. (리소스 로드/스토어와 LDS 배열들) 16비트 부동소수점은 포스트 프로세싱 필터, 특히 LDR 또는 톤맵 이후 데이터를 다룰 유용합니다.

LDS

같은 그룹 여러 쓰레드가 같은 데이터를 로딩하고 있으면 데이터를 대신 LDS 로딩하는 고려해보세요. 로드 명령어 개수와 VGPR 개수를 크게 절약할 있습니다.

 

LDS 당장 필요 없는 레지스터들을 임시 저장하는데 수도 있습니다. 예를 들면 셰이더가 어떤 데이터를 시작 부분에서 로드해서 쓰고 마지막에 다시 데이터를 사용하는데, VGPR 셰이더 중간에서 정점을 찍을 경우, 데이터를 잠시 LDS 저장하고 필요할 꺼낼 있습니다. 이러면 VGPR 최대 사용량을 줄일 있습니다.

 

0 Comments
댓글쓰기 폼