Search Unity

Unity 2020.2 릴리스에서는 베타 테스트가 가능한 최적화 몇 가지를 제공합니다. 본 블로그 포스팅에서는 속도 향상이 적용된 부분과 개선 과정을 소개하고자 합니다.

고성능 코드를 작성하는 것은 효율적인 소프트웨어 개발에 있어 필수적이며 유니티 개발 프로세스의 주요 부분을 차지합니다. 2년 전부터 유니티는 성능에 주력하기 위해 최적화 전담 팀을 구성하였고, 저는 현재 이 팀을 이끌고 있습니다. Unity 2020.2 릴리스에 대한 개요는 아래에서 살펴볼 수 있으며, 기타 개선 사항에 대한 목록은 Unity 2020.2 베타 릴리스 노트에서 확인하시기 바랍니다.

네스티드 프리팹 최적화

최적화 팀은 이 기능을 최초로 개발한 씬 개발 팀과 함께 네스티드 프리팹에 대한 다양한 최적화를 위해 긴밀히 협력했습니다. 최적화의 구체적인 내용은 다음과 같습니다. 

  • 프로퍼티의 동적 배열에 대한 수정 감소
  • 수정 목록의 정렬 방법 변경
  • 빠른 검색을 위해 해시 세트(hash set) 사용

유니티에서는 프리팹의 인스턴스를 로드할 때 원본 프리팹 에셋과 비교하여 인스턴스에서 다른 값을 가진 다양한 프로퍼티에 수정 사항을 적용합니다. 이를 PropertyModifications라고 합니다. PropertyModifications를 병합하는 경우, 동적 배열에 대한 업데이트와 삽입이 이루어지며 구조체의 규모가 매우 크기 때문에 비용이 빠르게 증가합니다. 반면 새로운 프로퍼티 배열에서 PropertyModifications를 삭제하지 않고 이미 업데이트된 프로퍼티를 추적하면 속도가 60 향상(테스트 프로젝트에서 3,300밀리초에서 54밀리초로 단축)됩니다.

PropertyModifications를 업데이트할 때 수정 목록이 바르게 정렬되지 않을 수 있기 때문에 새로운 정렬이 필요합니다. 이전에는 기존의 수정 사항을 새로운 배열로 정렬했으며, 테스트 프로젝트에서 이 과정은 11초가 걸렸습니다. 기존의 방법과 달리, 컨테이너에서 정렬을 수행하고 수정 사항을 적용한 결과 정렬에 걸리는 시간이 44밀리초(250 빨라짐)로 단축되었으며, 수정이 필요하지 않을 때는 11밀리초(800 빨라짐)로 단축되었습니다.

또한, 컨테이너를 해시 세트로 변경한 결과 수정 사항 목록에서 propertyPath를 찾는 속도가 50(300밀리초에서 6밀리초로 단축) 빨라졌습니다. 따라서 프로퍼티 차이를 생성할 때 전반적인 최적화가 이루어집니다.

스크립트된 임포터 최적화

  • RegisterScriptedImporters에서 선형 검색의 네스티드 루프 최적화

데이터베이스 확장성 테스트 결과, 등록된 임포터의 수가 증가함에 따라 에디터의 스크립트된 임포터 등록 함수의 성능이 감소하는 것으로 나타났습니다. 충돌 검색 속도를 높이기 위해 파일 확장자별로 딕셔너리에 임포터를 저장하여 함수를 최적화했습니다. 전반적인 최적화 결과는 100개에서 5,000개의 임포터를 처리할 때 12배에서 800배 이상 속도가 향상되는 수준인 것으로 나타났습니다. 전반적인 개선 사항은 아래 그래프에서 확인할 수 있습니다.

에디터 워크플로 최적화

  • 주요 에디터 작업에서 문자열 복사 및 할당 감소
  • 임시 메모리를 사용하여 씬에서 레퍼런스 검색 최적화

단일 프레임에만 존재하는 문자열을 대상으로 수많은 저속 문자열 메모리 할당을 임시 메모리 레이블로 대체했습니다. 실제로는 Unity에서 대부분의 문자열이 단일 함수 호출 내에서 로컬 변수로만 존재하기 때문에 속도가 빠른 메모리 할당자를 사용할 수 있습니다. 지금은 대부분의 문자열 유틸리티 함수가 임시 메모리를 사용합니다.

이 작업의 일부는 이미 Unity 2020.1에 포함되어 있습니다. 아래 그래프를 통해 얼마나 많은 저속 문자열 할당이 삭제되었는지 확인할 수 있습니다. 그래프는 버전 2020.1.0a12와 버전 2020.2.0a20의 다양한 프로젝트에 포함된 저속 문자열 할당의 수를 나타내며, 여러 번의 반복 작업을 통해 개선된 것을 볼 수 있습니다. x축은 반복 수를 나타내며, y축은 문자열 할당의 수를 나타냅니다.

FindReferencesInScene에서도 에디터 워크플로 최적화가 진행되었습니다. 이전에는 프로젝트 뷰의 에셋을 오른쪽 클릭하고 Find References in Scene을 선택하면 대규모 씬에서 속도가 느려지기도 했습니다.

과도한 스마트 포인터 역참조를 방지하고 임시 메모리를 사용하여 일반 사용 사례의 속도가 대략 10% 빨라졌습니다.

씬에서 레퍼런스가 누락된 경우, 스마트 포인터를 역참조하면 파일 시스템에서 유효하지 않은 파일 이름을 매번 로드해야 했습니다. 유효하지 않은 파일 이름을 감지하고, 오류가 예상되는 파일을 열지 않게 하여 검색 시간을 최대 3배 단축했습니다.

잡 시스템

  • JobQueue 최적화로 대규모 병렬 잡의 스케줄링 속도 약 2배 증가

유니티에서는 다른 내부 팀과 협력하여 JobQueue의 최적화 작업을 진행하고 있습니다. 이 작업은 연초에 DOTS 샘플 프로젝트를 프로파일링하는 것으로 시작했으나, AtomicStack::Pop()에 예상외로 큰 비용이 들었습니다. 추가 조사 결과 JobQueue의 메모리 관리 시스템, 특히 AtomicStack을 메모리 관리 풀로 사용하는 JobInfo에 문제가 있었음이 밝혀졌습니다.

데이터 지향 기술 스택(DOTS)은 ForEach 잡을 포함하여 메모리 할당을 위한 요소별 Pop()과 메모리 할당 해제를 위한 요소별 Push()가 필요합니다. 이로 인해 AtomicStack의 헤드 항목에 경합이 발생하게 됩니다.

다른 팀은 특히 메모리 관리 사용 사례를 위한 새로운 개별 컨테이너를 구현하여 ForEach 잡의 요소별 Pop()을 사용하지 않고 청크 단위의 요소를 단일 작업으로 할당하도록 했습니다..

초기 로컬 성능 테스트 결과는 인상적이었으며, 잡 워커 스레드(job worker thread)의 수가 증가함에 따라 성능 확장이 최대 2배 향상되었습니다.

DOTS 팀의 한 개발자는 새로운 컨테이너가 성능 이점을 보여야 하는 사용 사례(예를 들어 JobQueue ForEach 잡)에 대해 언급했습니다.

다음 이미지는 Android에서의 예시를 나타냅니다. 초록색은 신규 코드를, 빨간색은 기존 코드를 나타냅니다.

Camera.main 최적화

  • 메인 카메라 노드를 전용 목록에 저장하여 불필요한 검색 제거

Camera.main은 비효율적인 검색 기능 때문에 사용에 불편함이 있었습니다. 이전에는 태그가 있는 게임 오브젝트가 먼저 모두 검색된 후 일치하는 태그가 있는 게임 오브젝트는 모두 임시 배열로 추출되었습니다. 이후 두 번째 목록이 검색되며 활성화된 카메라가 있는 오브젝트가 모두 반환되었습니다.

새로운 방식에서는 MainCamera 태그가 있는 오브젝트를 전용 목록에 저장하고, 잠재적인 일치 항목을 저장하기 위한 이차 배열을 사용하지 않습니다. 대신 목록이 직접 쿼리되고, 일치 항목이 발견되는 순간 바로 반환됩니다. MainCamera 태그가 있는 오브젝트만 고려하므로 검색 속도가 훨씬 빠릅니다.

50,000개의 오브젝트를 포함하는 작위적 테스트 사례에서 검색 속도가 21,000배에서 51,000배까지 증가하는 것을 볼 수 있었습니다.

이번 개선 이후 Spotlight 팀 고객 프로젝트(아래 이미지 참조)에서 수백 밀리초가 0밀리초로 단축되었습니다.

RenderManager 카메라 사용 최적화

  • RenderManager에서 카메라 정렬의 영향 감소

이전에는 RenderManager 클래스에 카메라를 추가하거나 제거할 때마다 사용 중인 카메라를 뎁스별로 정렬하기 위해 연결된 목록이 업데이트되었습니다. 각 카메라의 뎁스를 확인하기 위해, 변경이 발생할 때마다 메모리 할당과 포인터 역참조를 해야 했고 이로 인해 카메라 여러 대를 사용할 때 속도가 느려졌습니다.

그러나 지금은 렌더링할 때만 정렬된 목록이 필요하기 때문에, 순서 지정이 필요한 상황에서만 목록이 정렬됩니다. 따라서 로딩 중에 카메라가 플랫 배열(더 적은 수의 할당)에 추가되거나 제거될 수 있으며 정렬된 목록이 처음 요청될 때, 즉 렌더링 중에만 정렬이 수행됩니다. 테스트 결과를 살펴보면 마지막 구간에 성능이 향상된 것을 볼 수 있으며, 그래프의 맨 오른쪽 주황색 막대가 새로운 코드를 나타냅니다.

텍스처 로딩 최적화

  • 대부분의 그래픽스 백엔드에서 스레드에 2D 텍스처와 큐브맵 생성
  • 콘솔에 최적화된 2D 텍스처와 단일 밉 큐브맵 로딩

텍스처 로딩 중 발생하는 장애를 줄이기 위해 2D 텍스처 생성을 그래픽스 스레드(graphics thread)에서 워커 스레드(worker thread)로 옮겼습니다. Unity 2019 릴리스에는 대부분의 그래픽스 백엔드에 해당 최적화가 완료되어 있습니다. Unity 2020.2에서는 8K 텍스처에서 발생하는 80밀리초 지연을 제거하여 DirectX 12에 대한 추가 사례를 수정했습니다.

Unity 2020.1에서는 텍스처 스위즐링을 오프라인으로 전환하고 GPU 메모리에 직접 로딩하여 콘솔에서의 Texture2D 로딩을 최적화했습니다. 텍스처 크기와 플랫폼에 따라 2D 텍스처 로드에 대한 성능이 최대 30% 향상되었습니다.

또한, Unity 2020.2에서는 콘솔에서의 큐브맵 로딩이 최적화되었습니다. 2K 큐브맵의 경우, 일부 콘솔에서 잡 스레드에 30밀리초의 시간 단축이 이루어졌으며 개별 텍스처의 전체 로딩 시간은 최대 15밀리초 감소했습니다.

프로파일 분석기 1.0.0

  • 2020.2에서 Profile Analyzer 1.0.x를 정식 패키지로 릴리스

유니티는 플랫폼별 프로파일링 툴과 Unity 자체 커스텀 프로파일러를 사용한 프로파일링과 분석을 통해 성능 최적화 방향을 설정해 왔습니다. 프로파일링 성과를 높이기 위해 2020.2버전에서는 프로파일 분석기 툴이 정식 제공될 예정입니다.

Profile Analyzer 1.0.0과 1.0.x 업데이트에는 편의성을 위한 다양한 버그 수정, 몇 가지 성능 최적화와 다음과 같은 기능이 추가로 포함됩니다.

  • 마커가 나타나는 스레드를 표시하는 열(선택 사항)
  • 프레임 시간 그래프 UI에 다중 선택 지원
  • 스레드 선택 UI에 정렬 기능

프로파일러 팀이 향후에 더 많은 기능을 개발할 예정입니다.

피드백을 공유해주세요

이번 업데이트는 성능을 향상하기 위한 유니티의 개발 계획의 일부이며, 2021년에도 더 많은 성능 개선을 제공하기 위해 노력하고 있습니다. 기능에 대한 피드백과 향후 개발 방향에 대한 의견이 있다면 공유해주세요.

Unity 2020.2 베타 버전 시작하기

2020년도에 계획했던 성능, 안정성 및 워크플로 개선을 Unity 2020.2에서도 이어나가고자 합니다. 베타 프로그램에 참여한 후 2020.2 베타 포럼에 업데이트에 관한 의견을 남겨주시기 바랍니다.

 

25 replies on “Unity 2020.2의 새로운 성능 개선 사항”

This is all great work! However, why when running on my 32/core 64 thread threadripper with 64gb of ram and nvme with read/write 7000mb/s / 5000mb/s I still wait just as long for anything to load. Texture imports don’t seem to benefit from more cores, there’s really no benefits from having more cores.

Texture imports do benefit from cores, but not the whole process. Texture compression is multi-threaded and should scale to all cores, as well as some parts of other texture processing (sRGB conversions, etc.).

We are optimizing texture import time as we speak; some optimizations are already in current 2021.1 alpha builds. More coming along the way.

That said, some parts of texture importing won’t ever scale to multiple cores — e.g. if source texture file is a .PNG file, then decoding of the PNG can’t be multi-threaded (it just can’t; that’s how PNG works). To get more core scaling there (and for other similar situations), Unity would have to import multiple assets in parallel — today it does not, but there’s also work underway to make that happen eventually.

Can we use Camera.main without any performance issue or we should use traditional way of declaring Camera variable and assigning it via inspector ?

Declaring a Camera variable and assigning it via the Inspector will still be marginally faster than accessing Camera.main, because all function calls into the Unity engine code come with a small amount of extra cost, vs. using a cached variable in your script.

Great work! But could you also fix/optimize the static/dynamic batch generator? Currently in one of my projects the batch generator breaks, because the object is affected by multiple forward lights even though I have only one light (spotlight) active in my scene… I get like 100 extra drawcalls because of this and I think I need to merge the meshes using code, because the meshes are small and just let the GPU to decide which of them are visible and which are not…

Great news! I hope the assets importing time will be minimized in Unity 2020.2, it takes about an hour whenever I open a project on a new machine.

Asset import optimizations (mostly focusing on textures & meshes right now) are happening as we speak. Meanwhile, using Accelerator (née Cache Server) might help to reduce import work across multiple machines.

Please, try to update this changes to 2020.1. We can’t wait for the official realease of 2020.2 and it’s currently unworkable as of now.

Why not use open hashing for an easier search of objects with a certain tag like Laurent suggested? Let’s say you have n different tags (buckets) and m objects for each tag. You could store a list (call it BucketList) of n pointers to linked lists (call them List_i) where each one of those linked lists corresponds to a different tag and when you want to find an object with a certain tag, you calculate the hash of that tag which gives you a position in the list BucketList, and that tells you which List_i contains objects with that specific tag. Using a good hash function (like this one which operates on strings http://www.azillionmonkeys.com/qed/hash.html) makes this lookup fast and the memory needed is O(number of tagged objects).

Incredible work guys! Really appreciate short overview articles like these.
Really good news ahead for 2020.2!
The Unity devs community is rooting for you!!!

I never use tags because they’re so slow.
Now if you generalize the camera.main optimization to all tags and make query against a hash of the tag name as fast then tag becomes very AI friendly.

The optimization that I want the most is lazy importing of assets. So that I don’t have to wait a few hours the first time I open a project on a new machine. There was talk of it last fall. Please make that happen and don’t drop the ball on it.

Yes I’m also looking forward to not having to compress all the textures at build time, and only compress that ones actually used the build

Reading between the lines I can see there’s a lot of really questionable historical decisions in the Unity codebase! Optimisations are the best thing for us in VR land, so thank you for keeping the code simpler and faster :-)

In Unity 2020 why blendshape contained model files have a small size than 2019? (Same model file) Is there anything new about blendshapes? I cannot found in release notes. The thing I know is Unity manages blendshapes with a special shader which is avoid re-uploading mesh to GPU. In 2020 is this still available? Thanks!

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다