주의 : 번역이 개판이므로 이상하면 원문을 참조하세요.

주의 : 번역이 너무 껄끄러우면 원문을 그대로 사용하겠습니다.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

원문 : Performance Tweets series: Barriers, fences, synchronization. GpuOpen.



DX12 시리즈에 오신 걸 환영합니다. 가장 화끈한 주제 중 하나로 들어 가 보죠: 동기화, 즉 배리어와 펜스입니다!


Barriers


배리어는 이전에는 드라이버 단에 숨겨져 있지만 개발자에게 노출된 새로운 개념입니다. 만약 여러분이 이것을 동기화라고 생각한다면, 아주 크게 벗어는 것은 아닙니다. 왜냐하면 이것 또한 배리어의 일부이기 때문입니다. 동기화 부분은 CPU 측에 대해서는 잘 알려져 있습니다: 버퍼를 갱신하는 다중의 쓰기 스레드를 가지고 있고, 모든 쓰기 작업이 종료되도록 만들기 위해서 동기화를 하고, 다중의 읽기 쓰레드를 사용해 데이터를 처리할 수 있습니다. 하지만 그것이 전부는 아닙니다. GPU 배리어가 존재합니다( ResourceBarrier vkCmdPipelineBarrier ). 전체적으로 볼 때 3 가지 기능들이 있습니다:


  • Synchronization 은 새로운 작업을 시작하기 전에 이전의 의존성 작업이 완료되는 것을 보장합니다.
  • Visibility 는 특정 유닛에서 캐싱되어야 하는 데이터가 그것을 확인하고자 하는 다른 유닛에서 가시적임을 보장합니다.
  • Reformatting 은 유닛에 의해서 읽어들여진 데이터가 유닛이 이해할 수 있는 형식임을 보장합니다.


여러분은 리포매팅이 발생하는 경우에 대해서 궁금할 겁니다. 이는 압축을 하는 경우에 종종 요구됩니다. 예를 들어 전통적으로 렌더 타깃은 읽기와 쓰기를 위해 요구되는 대역폭을 줄이기 위해서 압축이 됩니다. GPU 의 어떤 유닛들은 모든 압축 모두를 제공하지는 않으며, 압축해제를 요구합니다. 보통 배리어는 캐시 플러시( cache flush )( 컬러 버퍼, 깊이 버퍼같은 개별 유닛에 붙어 있는 L1 캐시나 좀더 일원화된( unified ) L2 캐시 )로 변환됩니다. 그리고 나서 대기하고( 예를 들어, 결과를 사용하고자 하는 그래픽스 셰이더 이전에 컴퓨트 셰이더가 완료되기를 기다립니다 ), 압축을 해제합니다.


이제 배리어가 뭔지 알게 되었습니다. 그런데 그게 왜 중요할까요? 배리어는 보통 Direct3D 12 와 Vulkan 에서 버그의 온상이 됩니다. 흔들림( flickering ), 깨짐( blocky artifact ), 데이터 손상( corrupted data ), 깨진 지오메트리와 같은 많은 현상들이 잘못된 배리어의 책임일 수 있습니다. 일단, 서브리소스 당 트래킹( per-subresource tracking )을 포함하는 100 % 올바른 배리어가 있다고 가정해 봅시다. 즉 여러분이 텍스쳐 배열의 한 레이어에 쓰기 작업을 하면, 이 레이어는 올바른 상태로 전이( transition )될 필요가 있습니다 -- subresources are often overlooked. You also have to make sure to have all required transitions in place. Some might be easy to overlook like transitioning for presentation.


배리어를 만들었으면, 이제 최적화를 할 시간입니다. Barriers are expensive and every barrier counts. 그것을 요구하는 리소스들만 트랜지션 해야 합니다 -- 그 예로는 텍스쳐로서 읽어들여지는 렌더 타깃이 있습니다. 일반적으로는 쓰기 작업을 하는 리소스보다 더 많은 배리어를 만들어서는 안 됩니다. 다른 모든 리소스들은 프레임 당 트랜지션을 하지 말아야 합니다. 특히 리소스에 대해서 쓰기 작업이 한 번만 수행되고 여러 방식으로 읽어들여진다면, 다중의 읽기-읽기( read-read ) 상태 트랜지션들을 사용하기 보다는, 대부분의 읽기 상태에 대해 리소스를 한 번만 트랜지션하십시오.


배리어를 배칭하는 것도 좋은 성능을 내는 데 있어 중요합니다. 배리어는 GPU 플러싱( flushing )을 요구하므로 배리어 호출 당 더 많은 리소스들을 트랜지션 하십시오. 그러면 배리어를 필요로 하는 모든 리소스들에 대해 단 한 번의 플러싱만이 수행될 것입니다. 이는 성능 향상에 많은 도움이 됩니다 -- 그러므로 가능한 한 배리어 호출들을 배칭하십시오.  이를 구현하는 한 가지 방법은 상태를 식별하는 것입니다. 모든 리소스들은 커맨드 리스트에 존재해야 하며, 커맨드 리스트 시작이나 이전 커맨드 리스트의 끝에서 그것들을 모두 함께 트랜지션하는 것입니다. 물론 실제 및 트랜지션 작업이 실행되기 바로 전에 리소스 상태를 검사하는 안 좋은 패턴은 피해야 할 것입니다 -- 이는 필요한 것 보다 더 많은 트랜지션 호출이 발생하도록 만들 것입니다.


Fences


배리어와 관련이 있는 것으로 펜스가 있습니다( CreateFence 와 vkCreateFence ). 이것들은 CPU 와 GPU 뿐만 아니라 GPU 상의 큐들을 동기화하기 위해서 사용됩니다. 펜스는 매우 무거운 동기화 개체입니다. 왜냐하면 그것은 적어도 GPU 가 모든 캐시들을 플러싱할 것을 요구하며, 잠재적으로 일부 추가적인 동기화를 요구합니다. 그런 비용들 때문에, 펜스는 드물게 사용되어야 합니다. 특히, 프레임 당 리소스들을 그룹화하고, 미세한( fine-grained ) 리소스 당 트래킹을 사용하는 대신에, 단일 펜스를 사용해 함께 트래킹하도록 시도하십시오. 예를 들어, 한 프레임 내에서 사용되는 모든 프레임 버퍼들은 커맨드 버퍼 당 펜스를 사용하기 보다는 하나의 펜스를 사용해서 보호되어야 합니다.


펜스는 컴퓨트 큐, 카피 큐, 그래픽스 큐들을 프레임 단위에서 동기화하기 위해서 드물게 사용될 수도 있습니다. 이상적으로는, 모든 잡들이 종료되는 조짐이 보이는 끝 부분에서 단일 펜스를 사용해서 비동기 컴퓨트 잡들의 큰 배치를 제출하려고 시도하시기 바랍니다. 카피에 대해서도 동일합니다. 가장 가능성있는 성능을 획득하기 위해서 모든 카피의 끝에서 단일 펜스를 사용해서 시그널링을 하십시오.


늘 그렇듯이, 질문이 있으면 편한하게 댓글을 남기거나 Twitter 에 알려 주세요 : @NThibieroz & @NIV_Anteru.


Tweets


02 : 배리어 개수는 쓰기 작업을 하는 리소스 개수와 대충 비슷해야 합니다.

12 : 각 펜스는 ExecuteCommandList 와 비슷한 비용이 듭니다( CPU 와 CPU 비용 ).

22 : 읽기-읽기 배리어를 사용하지 마십시오. 올바른 상태의 리소스를 먼저 획득하십시오.

31 : 항상 리소스를 그것을 작성하는 마지막 큐에서 트랜지션하십시오.


Matthäus Chajdas is a developer technology engineer at AMD. Links to third party sites, and references to third party trademarks, are provided for convenience and illustrative purposes only. Unless explicitly stated, AMD is not responsible for the contents of such links, and no third party endorsement of AMD or any of its products is implied.

+ Recent posts