Search Unity

Unity 19.1a10에 실험 버전으로 제공되는 새 기능인 점진적 가비지 컬렉션을 추가했습니다. 이 게시물을 통해 이 기능이 어떤 것이며 프로젝트에 어떻게 도움을 줄 수 있는지, 이 기능에 대한 유니티의 향후 계획은 무엇인지, 개선 프로세스에 참여하려면 어떻게 해야 하는지에 대해 알아보시기 바랍니다.

점진적 GC 사용하는 이유

C# 언어는 관리되는 메모리와 자동화된 가비지 컬렉션을 사용합니다. 다시 말해서, 자동으로 메모리 내의 오브젝트를 추적하고 더 이상 필요 없는 오브젝트가 차지하는 메모리를 해제하는 방법을 사용합니다(자세한 내용은 기술 자료 참조). 이 방법의 장점은 가비지 컬렉터가 자동으로 메모리 해제를 수행하므로 직접 메모리 해제 여부를 관리할 필요가 없다는 점입니다. 자동 방식은 작업자의 일을 수월하게 만들고 잠재적인 버그의 근원을 제거합니다. 단점은 가비지 컬렉터가 작업을 수행하는 데 시간이 다소 소요되며 이 작업에 시간을 낭비해서는 안 되는 시점에 이런 상황이 발생할 수도 있다는 점입니다.

Unity는 Boehm–Demers–Weiser 가비지 컬렉터를 사용합니다. 이는 stop-the-world 방식의 가비지 컬렉터로, 가비지 컬렉션을 수행해야 할 때마다 실행 중인 프로그램을 중지했다가 작업이 모두 완료된 후에야 실행을 재개합니다. 이 방식은 가비지 컬렉터가 처리해야 하는 메모리의 양과 프로그램을 실행 중인 플랫폼에 따라 임의의 순간에 프로그램 실행을 지연시킬 수 있습니다(1밀리초에서 몇 백 밀리초까지 걸릴 수 있음). 게임과 같은 실시간 애플리케이션의 경우 이러한 단점은 상당히 큰 문제가 될 수 있습니다. 프로그램 실행이 가비지 컬렉터에 의해 임의로 중지될 수 있다면 원활한 애니메이션에 필요한 일관된 프레임 속도를 유지할 수 없기 때문입니다. 이러한 중단(간섭)을 GC 스파이크라고도 합니다. 프로파일러의 일정한 프레임 시간 그래프 상에서 스파이크처럼 표시되는 것을 표현한 말입니다. 대체로 개발자는 게임을 실행하는 동안 “가비지” 메모리 생성을 방지하는 코드를 작성해서 가비지 컬렉터가 해야 할 일을 줄이는 방법으로 이 문제를 해결하려고 합니다. 하지만 이 방법이 항상 가능한 것은 아니며, 어려운 경우도 있습니다.

그렇다면 이제 점진적 가비지 컬렉션에 대해 알아보겠습니다. 점진적 GC 방식도 동일한 Boehm–Demers–Weiser GC를 사용하지만 점진적 모드로 실행하므로 GC 작업을 여러 개의 슬라이스로 분할합니다. 따라서 GC 작업을 위해 프로그램 실행을 한 번에 오랫동안 중단하지 않고 여러 번에 걸쳐 짧게 중단할 수 있습니다. 이렇게 하면 GC에 소요되는 시간의 총량이 줄어드는 것은 아니지만, 여러 개의 프레임에 걸쳐 워크로드를 분산함으로써 원활한 애니메이션의 맥을 끊는 GC 스파이크 문제를 크게 줄일 수 있습니다.

이 방식의 효과를 이해하려면 점진적 GC를 사용한 경우와 사용하지 않은 경우로 나누어 macOS 스탠드얼론 빌드로 실행되는 GC 성능 테스트 스크립트의 Unity 프로파일러 스크린샷을 확인하시기 바랍니다. 이 스크립트는 60fps로 실행됩니다. 프레임에서 연한 푸른색 부분은 “스크립트 작업”(스크립트의 System.Threading.Thread.Sleep 호출에 의한 시뮬레이션)이고, 노란색 부분은 Vsync(다음 프레임 시작 대기 중)이고, 진한 녹색 부분은 가비지 컬렉션입니다.

점진적 GC를 사용하지 않을 때(위), 프로젝트에 몇 초마다 약 30ms의 스파이크가 발생하여 원활한 60fps 프레임 속도를 방해합니다.

점진적 GC를 사용할 때(위), GC 작업이 여러 프레임에 걸쳐 분할되어 각 프레임에 대해 짧은 시간만 사용하므로 동일한 프로젝트가 일관된 60fps 프레임 속도를 유지합니다.

위 스크린샷 역시 점진적 GC를 사용하여 실행 중인 동일한 프로젝트를 보여줍니다. 단, 이 경우는 프레임당 “스크립팅 작업”의 수를 줄여서 실행했습니다. 여기서도 GC 작업이 여러 프레임에 걸쳐 분할되었습니다. 차이점은, 이 경우 GC가 프레임당 시간을 더 많이 사용했으며 완료하는 데 필요한 총 프레임 수가 더 적다는 점입니다. 이는 Vsync 또는 Application.targetFrameRate 사용 시 남은 가용 프레임 시간에 따라 GC에 할당되는 시간을 조정하기 때문입니다. 이렇게 할당 시간을 조정하면 대기하느라 소요될 시간에 GC를 실행할 수 있습니다. 다시 말해서, 다른 요소의 희생 없이 GC를 수행합니다.

점진적 GC 활성화하는 방법

점진적 GC는 현재 Mac, Windows 및 Linux 스탠드얼론 플레이어와 iOS, Android 및 Windows UWP 플레이어에서 실행하는 Unity 2019.1 알파에서 지원됩니다. 지원되는 플랫폼이 앞으로 더 추가될 것입니다. 점진적 GC를 사용하려면 새로운 .NET 4.x에 상당하는 스크립팅 런타임 버전이 필요합니다.

지원되는 구성에서 점진적 GC는 Player 설정 창의 “Other settings”를 통해 실험 단계 옵션으로 제공됩니다. Use incremental GC (Experimental) 체크박스를 선택하면 이 기능을 사용하도록 플레이어가 빌드됩니다.

2019.1에 새롭게 추가된 Scripting.GarbageCollector API를 사용하여 점진적 GC 동작을 보다 정밀하게 제어할 수 있습니다.

프로젝트에서 이를 테스트해 보고 결과를 포럼에서 공유해 주시기 바랍니다. 여러분의 피드백을 환영합니다!

기대하는 결과

점진적 GC를 활성화하면 가비지 컬렉터가 가비지 컬렉션을 여러 개의 작업으로 분할하며, 이는 또 다시 여러 프레임에 분산될 수 있습니다. 이렇게 하면 GC 스파이크가 문제가 되는 대부분의 상황을 완화하는 데 도움이 될 것으로 기대합니다. 하지만 Unity 콘텐츠가 매우 다양하고 그 동작 방식이 크게 다를 수 있으므로 점진적 GC가 유익하지 못한 경우도 있을 수 있습니다.

구체적으로, 점진적 GC가 작업을 분할할 때 분할되는 부분이 마킹 단계(모든 관리되는 오브젝트를 스캔하여 이들 오브젝트가 레퍼런스하는 다른 오브젝트를 찾고 아직 사용 중인 오브젝트를 추적)인 경우를 가정해 보겠습니다. 이는 오브젝트 간 레퍼런스의 대부분이 분할된 작업 간에 변경되지 않는다고 가정합니다. 만약 변경된다면, 변경된 오브젝트를 다음 반복에서 다시 스캔해야 합니다. 이렇게 되면 수행할 작업이 계속해서 추가되므로 점진적 컬렉션이 완료되지 않는 상황이 발생할 수 있으며, 이 경우 GC는 비점진적인 전체 컬렉션을 수행하게 됩니다. 모든 레퍼런스가 항상 변화하는 인위적인 테스트 사례를 만들기는 쉬우나, 이 경우에는 비점진적 GC가 점진적 GC보다 성능면에서 더 낫습니다.

또한 점진적 GC를 사용할 때 Unity에서는 레퍼런스가 변경될 때마다 이를 GC에 알려 GC가 오브젝트를 다시 스캔해야 하는지 여부를 판단하도록 하는 쓰기 배리어(write barrier)라고 하는 추가 코드를 생성해야 합니다. 이 경우 레퍼런스 변경 시 오버헤드가 약간 더해지며, 이는 일부 관리되는 코드의 성능에 가시적인 영향을 미칠 수 있습니다.

그렇지만 대부분의 Unity 프로젝트의 경우 GC 스파이크 문제를 겪고 있다면 점진적 GC가 유용할 수 있습니다.

실험 단계

점진적 GC는 Unity 2019.1에 실험적 프리뷰 기능으로 포함되었습니다. 프리뷰 버전으로 추가하게 된 이유는 다음과 같습니다.

  • 아직 모든 플랫폼에서 지원되지는 않습니다.
  • 위의 “기대하는 결과” 섹션에서 언급했듯이, 대부분의 Unity 콘텐츠에서 점진적 GC가 성능 면에서 유익하거나 혹은 적어도 피해는 되지 않기를 기대합니다. 다행히도 지금까지 테스트했던 다양한 프로젝트에서는 기대했던 결과를 얻었습니다. 하지만 Unity 콘텐츠가 매우 다양하기 때문에, 이러한 결과가 더 광범위한 Unity 에코시스템 전체에서도 유효한지 확인해야 하며, 이와 관련하여 사용자의 피드백이 필요합니다.
  • 관리되는 메모리의 레퍼런스가 변경될 때마다 이를 GC에 알리기 위해 Unity 코드 및 스크립팅 VM(mono, il2cpp)에서 쓰기 배리어를 추가하도록 하면, 쓰기 배리어를 추가하지 못할 경우 필요한 오브젝트가 가비지 컬렉션 대상으로 처리되어 버그가 발생할 수 있습니다. 지금까지 수동 및 자동 모드로 모두 광범위하게 테스트해 본 결과, 이러한 문제가 발견되지는 않았기 때문에 이 기능이 안정적이라고 판단하여 출시하게 되었습니다. 그러나 앞서 언급한 것처럼 Unity 콘텐츠가 매우 다양하고 이런 버그가 실제로 트리거하기 어려워서 아직 발견되지 않았을 수도 있으므로, 문제 발생 가능성을 완전히 배제할 수는 없습니다.

 

전반적으로는, 이 기능이 예상대로 작동하며 알려진 문제가 없다고 생각합니다. 하지만 Unity 에코시스템의 복잡성으로 인해, 실험 단계를 넘어섰다는 확신을 갖기까지는 좀 더 시간이 필요하며, 이 과정에서 여러분의 피드백이 필요합니다.

향후 계획

점진적 GC는 현재 Unity 2019.1 알파 버전에서 사용 가능하며, 2019년 중에 몇 가지 기능 변경이 있을 예정입니다.

  • 다른 모든 플레이어 플랫폼에 대한 지원 추가
  • 실험 단계 탈피(사용자의 피드백 필요)
  • 점진적 GC 모드에서 에디터 자체를 실행하도록 지원 추가
  • 점진적 GC를 기본 옵션으로 설정(사용자의 피드백 필요)

상당히 좋다고 알려진 다른 GC 사용하지 않은 이유

점진적 GC에 대해 대화할 때 Boehm GC 대신 예를 들어 Xamarin의 Sgen GC와 같은 다른 GC 솔루션을 고려하지 않은 이유에 대해 자주 질문을 받습니다. 사실, 자체 GC를 작성하는 것을 비롯하여 다른 옵션을 고려해 왔으며 앞으로도 그럴 것입니다. 하지만 Boehm을 계속 사용하고 이를 대신 점진적 모드로 전환하는 방식을 쓰는 주요 이유는 상당한 개선과 더불어 가장 안전한 방법으로 여겨지기 때문입니다. 위의 “실험 단계” 섹션에서 설명했듯이, 쓰기 배리어를 추가하지 않거나 쓰기 배리어를 잘못된 위치에 배치하는 경우 야기되는 버그가 위험 요소의 한 가지 예입니다. 그러나 쓰기 배리어를 추가하도록 하는 요구 사항은 현재 대부분의 최신 GC 솔루션에서도 적용하고 있습니다. Boehm GC를 계속 사용함으로써 우리는 다른 새 GC로 전환할 때의 위험과 쓰기 배리어를 정확하게 추가해야 하는 데서 발생하는 위험을 다소 분리할 수 있습니다.

이 영역에서 지속적으로 발전이 이루어질 것입니다. 점진적 Boehm의 도입과 더불어 여러분의 요구 사항이 어떻게 반영될지도 지켜봐 주시기 바랍니다. 점진적 Boehm이 도입된 후에도 많은 사용자가 GC 스파이크나 다른 문제로 어려움을 겪는다면 다른 옵션을 고려하겠습니다.

다시 말씀드리지만, 여러분의 피드백이 중요합니다. 알파 버전을 확인해 보고 의견을 포럼에서 공유해 주시기 바랍니다.

 

20 코멘트

코멘트 구독

코멘트를 달 수 없습니다.

  1. Guys, this is a blessing!

  2. Non-progressive GC was of course a huge idea.
    Do not observe any rights that may help you develop the text and other things. Fighting!

  3. Unity’s GC is non-compacting, correct? Would a possible improvement be the ability to force-compact when there’s plenty of time to do so (like loading), reducing memory fragmentation issues over long playtimes?

  4. So, what happens if I try to create large objects that overflow the remaining memory (assuming there’s enough garbage to offer the required memory)?
    You can’t just make my constructor call incremental as well.
    Will it fallback to instant GC or is memory regularly collected to prevent this from happening?

    1. Jonas Echterhoff

      12월 3, 2018 9:09 오전

      It should fall back to doing a full GC if needed.

  5. I’ve had many issues with GC. I cringed every time I saw someone play my game and it would pause for a second once every minute.

  6. Heck yes. This will be a great addition. Seems like an obvious choice to make this the standard as long as GC is manually triggrable through scripts for the moments when you need it to clear completely.

  7. Finally!!! Excellent news! This will make our life so much easier.

  8. I wonder how big that overhead is… Do you perhaps have some measurements of references overhead?

    1. Jonas Echterhoff

      11월 27, 2018 11:05 오전

      Well, the overhead always depends on what you are doing (and the platform). If you only look at the specific operation of assigning a reference in managed code that does become several times slower. But the question is how much of the total time in your game is spent assigning references in managed code? Outside of micro-benchmarks, I did not find noticeable performance degradations in Unity projects. But that does not mean that there won’t be any, as each project is unique.

  9. Good write up.
    This is an exciting feature

  10. Robert Cummings

    11월 26, 2018 4:39 오후

    Sounds awesome. One issue with allocations I have is with multi scene loading. Async streaming a scene in is never pretty and I hope this will smooth out the final jerks I can’t really do much about without source access.

    1. Jonas Echterhoff

      11월 26, 2018 7:29 오후

      I’m not sure it will help with that. Scene loading performs a lot of work in native code, GC should not necessarily be the problem there. But you should be able to tell what time is being spent on using the profiler.

  11. Glad to hear the garbage collector is finally getting this update, however I really hope Unity can add some profiling tools to debug situations which cause the incremental GC to fail.

    1. Jonas Echterhoff

      11월 26, 2018 7:28 오후

      What do you mean by “cause the incremental GC to fail”? It is not expected to “fail”. Or do you mean cases where incremental GC will still not result in the performance you need or fall back to a full GC causing spikes? In such cases, you will need to reduce the amount of garbage created, as before. Incremental GC can help better distribute the time for GC, but it does not fundamentally change the work which needs to be done. So creating garbage is still not “free”. But the tools to help you reduce garbage exit – the Unity profiler can help you track allocations.

  12. YES! You finnally do this! Stable frame time very important for engine!

  13. WOWOW finally no more spikes and smooth fps… !!! Looks promising

    1. How to minimize GC allocation?

      1. InNoHurryToCode

        12월 10, 2018 9:00 오후

        By creating less instances of classes, gameobjects, etc. Everything that is created via the new keyword is memory on the heap, thus managed by the CG. One way is to use an object pool, another one is to create everything at the beginning of the game to reduce cg spikes.

        Google, ask around on the forums and many more people will be able to help you.