스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Metal 3를 통한 GPU 바이너리 타겟팅 및 최적화
오프라인 컴파일을 통해 프로젝트 빌드 시 전체 GPU 바이너리를 생성하여 앱 내 지연, 첫 실행 시간 및 새로운 수준의 로드 시간을 줄이는 방법을 알아보세요. 또한 ‘Optimize for size(크기에 맞추어 최적화)' 컴파일러 옵션을 사용하여 대규모 GPU 프로그램을 위해 총 컴파일 시간과 바이너리 크기를 개선하는 방법을 보여드립니다.
리소스
관련 비디오
WWDC23
WWDC22
WWDC21
WWDC20
-
다운로드
♪♪ Metal 3를 사용해 GPU 바이너리 최적화하기 안녕하세요, 여러분 반갑습니다 GPU소프트웨어팀 엔지니어링 매니저 Galo Avila입니다 이번 세션에서는 Eylon과 제가 Metal 3를 통해 여러분 앱의 GPU 바이너리를 향상시키는 법을 알려 드릴 겁니다 우선, 오프라인 컴파일링이 어떻게 앱의 끊김 현상을 줄이고 앱의 시작 시간이나 로딩 시간을 줄이는지 설명해 드리죠 그다음 Eylon이 크기 최적화 컴파일러 옵션을 사용해 코드 크기를 변경해 컴파일 시간을 조정하는 법을 설명할 겁니다 크기 최적화 오프라인 컴파일로 GPU 바이너리를 프로젝트에 적용할 수 있습니다 이 기술이 Metal 응용 프로그램에 가져올 이점을 이해하기 위해 GPU 바이너리를 생성하는 방법부터 살펴보고 시작하죠 Metal 앱에서는 GPU 바이너리가 빌드 타임과 런타임에 생성됩니다 소스에서 Metal 라이브러리를 인스턴스화한다고 가정해 보죠 이렇게 하면 앱의 런타임에 Apple's Intermediate Representation 즉, AIR가 생성됩니다 이는 CPU가 중심이 되는 작업으로 여러분의 소스를 미리 컴파일링 해 앱 빌드 타임을 Metal 라이브러리로 옮길 수 있고 파일을 인스턴스화할 수도 있습니다 Metal 라이브러리가 메모리에 있으면 상태 및 함수를 포함하는 Pipeline State Descriptor를 생성하는 게 간단한 일이 되죠 파이프라인 상태 객체 (PSO)를 생성하기 전까지는 GPU 바이너리 생성을 바로 수행하는 CPU 중심의 또 다른 작업을 할 수 있습니다
런타임의 GPU 바이너리 생성은 CPU 중심 작업일 수 있기 때문에 Metal이 PSO 생성 속도를 높이는 데 도움을 줍니다 여러분이 PSO를 인스턴스화할 때 Metal은 파일 시스템 캐시에 GPU 바이너리를 저장합니다 그리고 새로운 PSO가 생성될 때마다 새로 생성된 함수가 추가됩니다 그다음 이전에 생성된 바이너리를 참조해서 케시에서 로딩하는 거죠
Metal은 Binary Archives를 이용해 GPU 바이너리가 저장되는 시기와 위치를 확실히 제어하게 해 줍니다 PSO 기술자를 사용해서 여러분이 필요한 만큼 아카이브에 GPU 바이너리를 저장하면 됩니다 그러면 PSO 생성이 부담 없는 작업이 되죠 바이너리 아카이브로 더 융통성 있게 저장할 수 있지만 여전히 런타임에 생성돼야 합니다 대부분의 경우 사람들은 빌드 타임에 아카이브를 생성하길 원하는데 이제는 그게 가능해졌습니다 오프라인 바이너리 생성으로 새 아티팩트를 Metal 파이프라인 스크립트라는 프로젝트 빌드 타임에 Metal 소스나 Metal 라이브러리와 함께 지정합니다 파이프라인 스크립트는 여러분의 컴파일러 툴체인으로 API의 파이프라인 기술자 모음집과 같습니다 컴파일 과정의 결과물이 바이너리 아카이브입니다 더는 앱 런타임에 GPU 코드가 생성되지 않습니다 바이너리 아카이브만 로딩하면 PSO 생성을 가속화할 수 있죠
오프라인 컴파일은 앱의 런타임 CPU 오버헤드를 줄여 주는데 이로 인해 Metal이 로우 레벨 API가 되는 게 중요하죠 이를 적용함으로써 앱의 경험을 두 방법으로 향상시킬 수 있습니다 우선, 앱을 여는 시간과 새로 로딩하는 시간이 줄어 관여 및 상호 작용 능력이 잠재적으로 향상될 수 있습니다 런타임 컴파일로 인한 끊김 현상이나 프레임 속도 저하도 마침내 없앨 수 있게 됐습니다 프레임을 미리 예열하는 CPU 비용이나 메모리 없이 말이죠 다음으로 이런 이점에 대해 자세히 알아보겠습니다
이건 전형적인 앱 런타임에 생성된 바이너리입니다 이 예시에서는 앱이 로딩 화면 뒤에서 GPU 바이너리를 컴파일하는 데 런타임의 2/3를 소비합니다 상호 작용을 시작하기도 전에 말이죠 하지만 오프라인 컴파일을 통해 셰이더 생성을 빌드 타임으로 옮기면 순식간에 PSO가 생성되면서 여러분은 여러분의 앱과 더 빨리 소통할 수 있습니다 로딩 화면에서 기다리지 않아도 되죠
오프라인 컴파일은 끊김 현상을 줄이는 데 도움이 됩니다 전형적인 런타임 바이너리 생성의 경우에는 로딩 타임에 파이프라인을 너무 많이 만들어서 만드는 시간을 정해 놓은 걸 수도 있습니다 이 경우에는 컴파일로 인해 명령 인코딩이 일시적으로 중단돼 프레임이 손실될 수 있죠 오프라인 컴파일은 성가신 요소를 제거해 주는데 앱 빌드 타임에 셰이더를 더 많이 컴파일할 수 있기 때문이죠 다음으로는 새로운 개발자 워크플로우를 공유하겠습니다 오프라인 컴파일의 이점을 활용할 수 있는 방법이죠
다음 워크플로우에서는 새로운 툴체인 기능을 사용하여 GPU 바이너리를 오프라인에서 구축하는 법을 알아볼 겁니다 새로운 파이프라인 스크립트 입력 아티팩트 생성 방법과 GPU 바이너리 생성을 위한 툴체인 적용법을 알려 드릴게요 파이프라인 스크립트 아티팩트는 하나 이상의 API 파이프라인 상태 설명자에 대한 JSON 형태의 설명으로 다양한 방법으로 생성할 수 있습니다 예를 들어, JSON 편집 프로그램에 그걸 입력하거나 개발이나 시험 단계에서 입력한 바이너리 아카이브에서 수집하죠 이건 상태 및 함수에 상응하는 JSON 표현을 사용하여 렌더 파이프라인 기술자를 생성하는 Metal 코드 스니펫입니다 우선, API Metal 라이브러리 파일은 라이브러리 패스 프로퍼티로 지정하고 API가 렌더 파이프라인 프로퍼티란 렌더 기술자 함수를 렌더링합니다 끝으로 raster_sample_count이나 픽셀 형식과 같은 다른 파이프라인 상태도 스크립트 속성으로 캡처합니다 JSON 스키마에 대한 자세한 내용은 Metal 개발자 문서에서 확인하세요
JSON 스크립트 생성을 시작할 수도 있는데 Metal 런타임을 이용하면 도움이 될 수 있습니다 런타임에 바이너리 아카이브를 생성한 다음 개발 및 실험 과정 중에 직렬화만 하면 되죠 이제 Metal API를 사용해 진행하는 방법을 보여 드릴게요
런타임 수집 프로세스를 시작하고 상태 및 함수가 있는 파이프라인 기술자를 생성합니다 그걸 GPU 바이너리를 생성하는 아카이브에 추가하고 파일 시스템에 직렬화한 다음 앱의 번들에서 로딩합니다 Metal 3 런타임은 이 기술자를 GPU 바이너리와 함께 저장합니다 이제 추출하는 방법을 보여 드리죠 metal-source를 통해 JSON 파이프라인 스크립트를 기존의 아카이브에서 추출할 수 있습니다 이 방법은 런타임에서 빌드 타임으로 바이너리 생성을 옮길 때 유용하죠 플랫버퍼와 출력 디렉터리 옵션에 metal-source를 적용하면 됩니다 파이프라인 스크립트 파일이 그 결과로 생성되고 추가 바이너리 생성을 위해 편집할 수 있습니다 이제 툴체인을 적용하는 법을 보여 드릴게요 Xcode 프로젝트 구축 단계에서의 GPU 바이너리 생성은 쉽습니다 여러분의 소스와 파이프라인 스크립트, 출력 파일을 터미널에서 metal에 적용하면 되죠 출력 metal 라이브러리에는 GPU 바이너리가 포함되며 툴체인을 지원하는 기기에 배포할 수 있습니다 그리고 소스 대신에 Metal 라이브러리가 있다면 그것도 툴체인에 전달할 수 있죠 기존에 있던 Metal 라이브러리에서 바이너리를 생성하는 건 Metal 변환 도구처럼 쉽습니다 소스와 파이프라인 스크립트 출력 파일에서 하는 것처럼 metal-tt를 호출합니다 그 결과, Metal 라이브러리에는 툴체인을 지원하는 기기에 대한 GPU 바이너리가 포함됩니다 이제 오프라인에서 바이너리를 생성하는 법을 배웠으니 어떻게 로딩하는지 검토해 보도록 하죠 아카이브 기술자를 생성할 때 바이너리 URL을 제공하고 그걸로 아카이브를 인스턴스화하면 끝입니다! Metal 바이너리 아카이브 API에 대한 자세한 내용은 지난 세션을 참고해 주세요 마지막으로 오프라인에서 생성된 아티팩트에 대한 GPU 바이너리 호환성을 Metal이 어떻게 처리하는지 보죠 오프라인에서 생성된 바이너리 파일이 향후 OS 버전 및 제품과 제대로 호환되는지 확인해 봐야 합니다 OS 업데이트 또는 앱 설치 시 Metal이 적절하게 바이너리 아카이브를 업그레이드합니다 비동기적으로 백그라운드에서 진행되죠 런타임 끊김 현상을 없애 주고 시작 또는 로딩 시간을 줄여 주는 오프라인 컴파일의 장점을 빨리 활용해 주시면 좋겠습니다 다른 사람들이 이런 개선 사항을 체험하면서 전반적인 앱 경험을 향상시킬 수 있습니다 이제 Eylon에게 넘길게요 수고했어요, Galo 다음은 제가 새로운 컴파일 옵션인 크기 최적화에 대해 설명해 드릴게요 Metal 컴파일러는 런타임 성능을 위해 코드를 능동적으로 최적화합니다 일부 최적화는 GPU 프로그램의 크기를 확장시키는데 이는 예상치 못한 비용을 발생시킬 수 있습니다 예를 들어, 함수 인라이닝은 함수 호출의 오버헤드를 피하기 위한 최적화 작업입니다 호출된 함수는 호출 사이트에서 즉시 처리됩니다 예시로 든 이 커널에는 코드가 많아 보이지 않습니다 하지만 인라이닝 후에는 함수 'f'와 'g'의 사본을 포함할 수도 있고 'f'와 'g'에서 호출되는 함수가 있을 수도 있습니다 중요치 않은 라이브러리 함수나 헬퍼스 같은 거죠
또 다른 최적화 기능은 루프 언롤링으로 루프 본체의 추가 사본을 그때그때 처리해서 여러 번 반복하면서 유사성을 노출시키고 브랜칭 오버헤드를 방지합니다 컴파일러는 루프의 반복을 최소 두 번 풀 수 있거나 경계가 고정된 루프를 반복적으로 계속 풀 수 있죠 이런 최적화 기능으로 아주 큰 프로그램을 만드는 경우 컴파일러도 그걸 컴파일하는 데 많은 시간을 들여야 합니다 하지만 일부 상황에서는 그런 비용은 피하는 것이 좋죠 Xcode 14는 '크기 최적화'라는 새 Metal 최적화 모드를 도입했습니다 이 모드에서는 인라이닝과 루프 언롤링 같은 기능 같은 크기 확장 변환을 제한합니다 컴파일러가 성능 최적화를 적용하는 경우에 말이죠 이 기능이 의도하는 바는 기본값 최적화가 너무 비싼 경우 GPU 바이너리를 더 작게 유지하고 컴파일 시간을 더 단축시키는 겁니다 크기 최적화를 할 때는 그 결과로 런타임 성능이 저하될 수 있습니다 실제로 발생 여부는 프로그램에 따라 다를 테니 두 가지 최적화를 다 해 보고 비교해 봐야 합니다 크기를 최적화한다고 모든 셰이더의 크기와 컴파일 시간이 향상되진 않습니다 호출 경로와 루프가 깊고 큰 프로그램일수록 그 이점이 가장 큽니다 인라이닝과 언롤링은 일반적입니다 이 옵션은 기본 최적화 모드에서 예상치 않게 컴파일 시간이 길다면 시도해 볼 만한 가치가 있습니다 이 옵션은 프로젝트 빌드 타임이나 앱 런타임에 컴파일할 수 있습니다 크기 최적화로 차이를 만든 사례를 보여 드리죠 Cyles는 Blender 3D 디자인 환경을 위한 프로덕션 렌더러를 실행하는 오픈 소스 프로젝트로 최근 Metal을 지원하도록 업데이트됐습니다 Blender Development Fund에 Apple도 합류했습니다 저희가 배운 것 중 하나는 Blend의 패스 트레이싱 알고리즘이 헬퍼 함수와 루프가 있는 대형 컴퓨트 셰이더를 이용해 컴파일 시간을 최대 몇 분까지 늘릴 수 있다는 내용이었습니다 Metal 3의 크기 최적화 옵션에서 그런 셰이더들이 이익을 받을 수 있다는 게 밝혀졌죠
Apple Silicon GPU에서 이런 신을 렌더링할 때 크기 최적화를 통해서 컴파일 셰이더 파이프라인을 포함한 Blender의 설치 시간을 1.4배까지 향상시켰습니다 또한 렌더링 타임에서 성능 저하 없이 속도를 높일 수 있었죠 일부 렌더링에서는 최대 4%까지 느려졌고 일부는 속도가 전혀 줄지 않았습니다 그러니 런타임 성능을 낮추는 게 가능하다는 겁니다 하지만 경우에 따라 크기 최적화가 런타임 성능을 향상시킬 수도 있죠 예시를 보여 드릴게요 이건 Intel GPU에 맞게 크기 최적화를 실행했을 때와 같은 신에 대한 렌더링 시간 속도 변화를 나타냅니다 컴파일 속도가 빠를 뿐만 아니라 렌더링 속도도 최대 1.6배까지 빨라졌습니다 어떻게 된 걸까요? 작은 GPU 프로그램 덕분에 큰 프로그램에 따르는 런타임 페널티를 피할 수 있기 때문이죠 그러면 명령 캐시의 누락이 적거나 레지스터가 더 적게 필요할 수도 있는데 그건 메모리 유출이 적거나 동시에 많은 스레드를 발생시킨다는 뜻입니다 이런 결과가 모든 셰이더와 신에 나타나는 건 아니며 성능이 저하될 가능성도 있습니다 여러분 프로젝트에서도 컴파일 시간과 런타임 성능이 실제로 받는 영향을 평가해야 합니다 Metal 소스에서 컴파일할 때 크기 최적화 옵션을 세 가지 다른 환경에서 실행할 수 있습니다 Xcode 14 사용자 인터페이스에서는 크기 최적화를 빌드 설정으로 지정합니다 'Metal Compiler'의 'Build Options'에서 'Optimization Level' 설정을 찾습니다 레벨 'Default'은 Metal이 전에 했던 대로 성능을 최적화하고 레벨 'Size'는 크기에 맞게 최적화합니다
Metal 소스를 명령행에 따라 컴파일할 때 '-Os'를 사용해 크기 최적화를 지정합니다 첫 예시에선 단일 컴파일 링크 명령에 대한 옵션을 지정합니다 두 번째 예시는 두 개의 명령이 있고 그중 하나만 옵션을 지정해서 일부 셰이더에만 사용할 수 있게 합니다 이 옵션은 링크 명령이나 후속 명령으로 전달할 필요가 없습니다 이번 세션에서 제시했던 명령어를 사용해서 GPU 바이너리 생성과 관계없이 크기 최적화를 사용할 수 있죠
'newLibraryWithSource' 같은 Metal 프레임워크 API를 사용해서 앱 런타임에 Metal 소스를 컴파일할 때 'optimizationLevel' 프로퍼티로 'MTLCompileOptions' 객체의 크기 최적화를 설정합니다 최적화 레벨은 'Default' 또는 'Size'가 될 수 있겠죠 새로운 Metal 컴파일러 최적화 모드의 이점을 여러분의 프로젝트에 활용하시길 바랍니다 지금까지 제가 소개해 드린 오프라인 컴파일은 앱 빌트 타임에 GPU 바이너리를 생성하기 위한 새로운 워크플로우로 앱 내 끊김 현상이나 시작 및 로딩 시간을 줄여 주죠 그리고 저는 크기 최적화에 대해 말씀드렸는데요 새로운 Metal 최적화 모드로 소스를 컴파일할 때 프로그램 크기와 컴파일 시간을 줄여 주는 옵션이죠 저희는 이런 개선을 통해 여러분의 앱과 게임이 향상된 사용자 경험을 제공하는 데 도움이 되길 바랍니다 런타임 컴파일 비용을 낮춘 덕분에 새로운 워크플로우가 제공되고 설치와 로딩 시간이 단축되고 끊김 현상이 줄었죠
-
-
4:47 - Using a JSON editor: render pipeline descriptor
// An existing Obj-C render pipeline descriptor NSError *error = nil; id<MTLDevice> device = MTLCreateSystemDefaultDevice(); id<MTLLibrary> library = [device newLibraryWithFile:@"default.metallib" error:&error]; MTLRenderPipelineDescriptor *desc = [MTLRenderPipelineDescriptor new]; desc.vertexFunction = [library newFunctionWithName:@"vert_main"]; desc.fragmentFunction = [library newFunctionWithName:@"frag_main"]; desc.rasterSampleCount = 2; desc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; desc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float;
-
4:47 - Using a JSON editor: pipelines script
{ "//comment": "Its equivalent new JSON script", "libraries": { "paths": [ { "path": "default.metallib" } ] }, "pipelines": { "render_pipelines": [ { "vertex_function": "vert_main", "fragment_function": "frag_main", "raster_sample_count": 2, "color_attachments": [ { "pixel_format": "BGRA8Unorm" }, ], "depth_attachment_pixel_format": "Depth32Float" } ] } }
-
5:33 - Harvesting sample
// Create pipeline descriptor MTLRenderPipelineDescriptor *pipeline_desc = [MTLRenderPipelineDescriptor new]; pipeline_desc.vertexFunction = [library newFunctionWithName:@"vert_main"]; pipeline_desc.fragmentFunction = [library newFunctionWithName:@"frag_main"]; pipeline_desc.rasterSampleCount = 2; pipeline_desc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; pipeline_desc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float; // Add pipeline descriptor to new archive MTLBinaryArchiveDescriptor* archive_desc = [MTLBinaryArchiveDescriptor new]; id<MTLBinaryArchive> archive = [device newBinaryArchiveWithDescriptor:archive_desc error:&error]; bool success = [archive addRenderPipelineFunctionsWithDescriptor:pipeline_desc error:&error]; // Serialize archive to file system NSURL *url = [NSURL fileURLWithPath:@"harvested-binaryArchive.metallib"]; success = [archive serializeToURL:url error:&error];
-
6:01 - Extracting a JSON script
metal-source -flatbuffers=json harvested-binaryArchive.metallib -o /tmp/descriptors.mtlp-json
-
6:24 - Generate a GPU binary from source
metal shaders.metal -N descriptors.mtlp-json -o archive.metallib
-
6:48 - Generate a GPU binary from Metal library
metal-tt shaders.metallib descriptors.mtlp-json -o archive.metallib
-
7:07 - Load GPU binaries via the runtime API
MTLBinaryArchiveDescriptor *desc = [MTLBinaryArchiveDescriptor new]; desc.url = [NSURL fileURLWithPath:@"archive.metallib"]; NSError *error = nil; id<MTLDevice> device = MTLCreateSystemDefaultDevice(); id<MTLBinaryArchive> binaryArchive = [device newBinaryArchiveWithDescriptor:desc error:&error];
-
12:11 - Enable optimize for size in command lines
xcrun metal -Os large_shader.metal # or xcrun metal -c -Os large_shader.metal xcrun metal -c more_shaders.metal xcrun metal large_shader.air more_shaders.air
-
12:44 - Enable optimize for size with Metal framework
MTLCompileOptions* options = [MTLCompileOptions new]; options.optimizationLevel = MTLLibraryOptimizationLevelSize; NSString* source = @"..."; NSError* error = nil; id<MTLLibrary> lib = [device newLibraryWithSource:source options:options error:&error];
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.