Search Unity

Unity에는 Messaging system이라고 불리우는 시스템을 가지고 있습니다. 이 시스템은 게임이 동작하는 동안 특정시점에 유저가 정의한 함수가 동작될 수 있게 해주죠. 이런일이 가능한 건 바로 매직함수들(Update, Start…) 때문입니다. 이건 굉장히 이해하기 쉬운 컨셉입니다. 특히 새로운 유저에겐 더욱 그렇지요. 아래처럼 Update 함수를 만들면, 이 함수는 알아서 매 프레임마다 호출 될 것입니다.

하지만 숙련된 개발자는 위의 코드에서 몇몇 의문이 들 수 있습니다.

  1. 이 함수는 도대체 언제 호출되지?
  2. 만약, 여러개의 스크립트가 있고 그 스크립트들 모두에 Update()를 정의 해놨으면,  어떤 순서로 스크립트들이 호출되지?
  3. 이런 코드 스타일은 인텔리센스하게 동작되지 않아.

Update 문은 어떻게 호출되나?

No! 유니티는 유저의 매직 함수를 한번 호출하기 위해서, 매번 System.Reflection을 이용해 그 함수를 찾지 않습니다.

대신, 주어진 MonoBehaviour 스크립트가 처음으로 이용될 때, 스크립트 내 매직함수들이 있다면 이 스크립트는 각 매직함수와 연관된 List에 저장됩니다. 예를 들어 어떤 스크립트에 Update 함수가 정의되어 있다면 이 스크립트는 Update와 관련된 List에 저장되고, 이건 매프레임마다 Update가 호출되어야 할 시점에 이용되지요.

게임이 실행되는 동안 유니티는 이러한 리스트를 순회하면서 실행시킵니다. — 매우 쉽죠? 이러한 이유때문에 Update 함수가 public이든 private이든 상관이 없습니다.

어떤 순서로 Update 함수는 호출될까?

호출 순서는 Script Execution Order Settings (menu: Edit > Project Settings > Script Execution Order) 을 이용해 결정할 수 있습니다. 하지만 만개의 스크립트가 있다면, 그걸 일일이 정의하는 건 쉬운일은 아닐것입니다. 어떤 스크립트가 다른 모든 스크립트들보다 먼저 실행되야 하는 경우에는 이 방식이 유용합니다. 물론 미래에는 C#의 속성 기능을 이용한다던가 하는 식의 좀 더 손쉬운 방법이 생기길 원합니다.

이건 인텔리센스하지 않아.

우리 보통 C# 스크립트를 작성하기 위해 어떤 종류든 IDE를 이용합니다. 하지만 IDE들은 이런식의 매직함수를 좋아하지 않죠. 이 매직함수는 언제 호출되는지도 알 수 없고, 네이게이션하기도 힘드니깐요.

몇몇 개발자들은 이를 위해 MonoBehaviour를 확장한 추상화된 클래스를 만듭니다.  BaseMonoBehaviour 라든가 또는 이와 유사한 형태로 기존적인 뼈대만 가지는 클래스를 만들지요. 그리고 게임에서 이용되는 스크립트들이 이 클래스를 상속받게 합니다. BaseMonoBehaviour 클래스는 보통 아래처럼 여러개의 virtual함수를 가지고 있습니다.

이러한 구조는 좀더 논리적으로 MonoBehaviours를 이용하게 하지만 약간의 흠이 있습니다.

유저가 만든 스크립트들은 유니티 내부적으로 이용하는 Update 리스트에 존재하게 되는데, 위와 같이 구현하게 되면, 정작 유저 스크립트내의 Update문에서는 아무것도 하는게 없지만, 유니티 내부에서는 매 프레임마다 이 함수를 호출하게 됩니다.

어떤 사람들은 ‘함수내에서 아무것도 안하는게 문제가 될 수 있어?’ 할 수 있습니다. 하지만 Update문이 호출되는 것은 Native C++에서 managed C#이 호출되는 경우이기 때문에 이건 꽤 비용이 발생할 수 있습니다. 자 그럼 어떤 비용이 발생하는지 확인해 보죠.

10000번의 Update 호출

저는 이 포스트를 위해 Github에 한개의 작은 프로젝를 만들었습니다. 프로젝트에는 2개의 씬이 있습니다. 씬전환은 간단하게 디바이스를 탭하거나 에디터에서 아무키나 누르면 됩니다.

(1) 첫번째 씬에서는 아래의 코드를 가지는 10000개의 MonoBehaviour가 생성됩니다.

(2) 두번째 씬에서는 또다른 10000개의 MonoBehaviour들이 생성됩니다. 하지만 이번에는 스크립트가 Update문을 가지는게 아니라 UpdateMe라는 함수를 가집니다. 그리고 이 함수는 아래처럼 매니징 스크립트의 Update함수에서 호출됩니다.

이 테스트 프로젝트는 2개의 iOS 장비에서 실행되었고, 릴리즈, 그리고 Non-Development 옵션으로 Mono와 IL2CPP 모두로 빌드되었습니다. 시간은 아래와 같은 방법으로 측정되었습니다.

  1. 첫번째 Update 콜에서 스탑워치는 시작됩니다.(이건Execution Order로 조절했습니다.)
  2. 스탑워치는 LateUpdate문에서 정지됩니다.
  3. 몇 분동안 테스트를 하고 평균을 냈습니다.

Unity version: 5.2.2f1
iOS version: 9.0

Mono

와우!엄청 걸리네요! 테스트에 뭔가 잘못된것 같습니다!

Fast but no Exceptions 옵션을 켜는것을 잊었네요.  하지만 우린 이제 이옵션이 어떤 영향을 주는지 알게 됐네요. (사실 IL2CPP에서는 아무도 이걸 신경쓰지 않지만…)

Mono (fast but no exceptions)

OK, 훨씬 낫네요. 이제 IL2CPP를 한번 볼까요.

IL2CPP

여기서 두가지를 알 수 있는데요:

  1. 이 옵션은 여전히 IL2CPP에 영향을 미칩니다.
  2. IL2CPP는 여전히 개선할 부분이 있네요. 사실 제가 이 글을 쓰고 있는 동안에도 우리 IL2CPP팀은 성능을 개선하기 위해 열심히 일하고 있습니다. (내부에 있는 최신 소스코드를 이용하면 성능이 35%나 좋아져요.^^)

유니티에서 무슨일을 하고 있는 앞으로 몇달동안 설명드리겠습니다. 하지만 지금은 왜 우리의 매니징 스크립트가 5배나 빠른지 알아보시죠.

Interface calls, virtual calls and array access

만약 this great series of posts about IL2CPP internals글을 읽지 않으셨다면, 이 글을 읽은 다음에 바로 그 글을 읽으셔만 해요.

아래의 결과를 보면, 매 프레임마다 10000개의 요소에 접근하려 하면 리스트보다 배열이 더 낫다는걸 볼 수 있습니다. 왜냐하면 배열을 이용한 경우가 C#에서 생성된 C++ 코드들이 좀 더 심플하기 때문입니다.

다음 테스트에서 나는 List<ManagedUpdateBehavior>를 ManagedUpdateBehavior[]로 바꿨습니다.

음 훨씬 낫네요!

Update: 나는 Mono빌드에서 배열로 테스트했을 때 0.23ms결과를 얻었습니다.

Instruments to the rescue!

우리는 C++에서 C#함수를 호출하는게 빠르지 않다는 걸 봤습니다. 하지만 실제로 유니티 내부에서 이를 위해 무엇을 하고 있는지 알아야 할 필요가 있었습니다. 이를 알기에 가장 쉬운 방법은 Apple Instruments의 Time Profiler를 이용하는 것이었습니다.

Note: 이건 Mono vs. IL2CPP 테스트가 아닙니다. — 밑에 나오는 모든 결과는 Mono(for iOS)에서 좀 더 빠르게 측정됩니다.

저는 아이폰6에 Time Profiler를 이용해 테스트했습니다. 몇분간 데이터을 기록했고 1분간 값을 수집했습니다. 그리고 모든 것은 아래 라인부터 시작되는 것을 알게 됐습니다.
void BaseBehaviourManager::CommonUpdate<BehaviourManager>()

Instruments를 보는 방법을 설명드리면, 오른쪽엔 함수가 실행 순서에 따라 정렬되어 있고 가장 왼쪽에는 각 함수별 CPU 점유율(ms, %)을 보실수 있습니다. 그 옆에는 해당 함수 자체만의 실행 시간이 써져있습니다. CPU 점유율이 유니티를 위해 전부 사용되지 않는것을 알 수 있지만, 이번 실험의 목적은 이것이 아니기 때문에 일단은 각 함수들이 걸린 시간을 집중적으로 분석해 보겠습니다.

나는 이해를 위해 어설프지만 포토샵을 이용해서 색칠을 해보았습니다.

UpdateBehavior.Update()

가운데를 보면 우리의 Update문을 볼 수 있습니다. IL2CPP에서는 이것이UpdateBehavior_Update_m18 으로 표현됩니다. 하지만 이 함수를 호출하기에 앞서 유니티는 무엇인가 더 많은 작업들을 하고 있습니다.

모든 Behaviours의 순회

유니티는 Update를 호출하기 위해 모든 Behaviours 순회합니다. 유니티는 Update관련 순회를 하면서 Update관련 리스트의 값이 안정적이기 원합니다. 그리고 그것을 보장하기 위해 SafeIterator를 이용합니다. 모든 Behaviour를 순회하는데 걸리는 시간이 1517ms가 걸렸습니다. (총 시간 9978ms 중)

안정성 검사

다음으로 유니티는 Update가 호출될 스크립트가 유효한지 검사합니다. Update가 호출되기 전에 이미 초기화는 잘 되었는지, 또 이전에 Start는 호출되었었는지, 그리고 이미 destory된 스크립트는 아닌지 등을 검사합니다. 이러한 검사를 하는데 2188ms가 소요됩니다.(총 시간 9978ms 중)

함수를 invoke하기 위한 준비

유니티는 ScriptingArguments와 함께 ScriptingInvocationNoArgs의 인스턴스를 생성합니다. (Native 쪽에서 managed 쪽 함수를 호출하기 위해 필요합니다.) 그리고 이것을 IL2CPP에서 invoke하라고 하죠.(scripting_method_invoke function) 이 과정은 총 2061ms가 소요됩니다.(총 시간 9978ms 중)

함수 호출

scripting_method_invoke function 함수는 전달된 매개변수가 유효한지 확인(900ms)하고 IL2CPP virtual machine의Runtime::Invoke를 호출(1520ms)합니다.Runtime::Invoke함수가  처음으로 하는일은 매개변수로 전달 받은 함수가 존재하는지 확인(1018ms)하고, 그리고 생성된method signature 를 위한 RuntimeInvoker를 실행(283ms)합니다. 단 42ms Update구문을 실행시키기 위해 우리는 이러한 작업들을 하게 됩니다.

그리고 아래는 이걸 정리했습니다.

Managed Updates?

자 이번엔 매니징 스크립트를 이용한 것을 한번 측정해 볼까요? 아래 이미지에서 볼 수 있듯이 이번 테스트도 호출순서는 위의 테스트와 거의 유사합니다. 다만 몇몇함수들은 걸린 시간이 1ms도 안되서 정확한 시간을 측정하진 못했죠. 대부분의 실행시간이UpdateMe 함수에 집중되어 있는것을 볼 수 있습니다. (IL2CPP에서는 이게ManagedUpdateBehavior_UpdateMe_m14함수이고 IL2CPP는 추가적으로 NullCheck를 또합니다.)

다음 이미지는 위와 동일한 방식으로 색칠을 해본것입니다.

자, 이제 여러분은 어떤 생각이 드시나요?  작은 함수 하나를 호출하는게 신경쓰이시나요?

이 테스트를 관한 몇 말씀..

솔직히 말씀드리면, 이 테스트가 매우 공정하다곤 생각하진 않습니다. 유니티는 당신의 Update문을 호출하기 위해 여러가지 작업들을 합니다. 이 GameObject는 Active되어 있는지, 이번 Update 루프가 진행되는 동안 오브젝트가 삭제되지는 않았는지, Update구문 동안에 생성된 오브젝트들은 어떻게 처리해야 하는지… 하지만 저의 매니져 스크립트에서는 이러한 것들을 하지 않죠.

실제로 사용될 매니져 스크립트라면 당연히 이것보다 훨씬 복잡한 동작을 처리하것입니다. 그리고 아마도 저의 테스트보다 훨씬 결과가 더 느리게 나오겠죠. 하지만 우리는 개발자입니다.  우리는 우리의 매니져 스크립트가 무엇을 해야할 지 알고, 또 뭐가 실행되면 않되는지 알고 있습니다. 하지만 유니티는 이런것을 알 수 없어요.

자 그럼 이제 어떻해야 할까요?

물론 이건 당신의 프로젝트에 달렸어요. 하지만 이제는 매 프레임마다 호출되고, 또 한 씬에 매우 많은 게임오브젝트가 있는것도 매우 흔한일이죠. 사실 제가 알려드린 작업은 단순 코드만 변경되는 작업이고 눈에 띄는 작업도 아니죠. 하지만 당신이 엄청나게 많은 게임오브젝트를 이용하고 있다면, 한번쯤은 인식해 보는게 좋다고 생각합니다. 이미 지금은 개발이 너무 많이 진행되어서 이러한 수정을 하기엔 너무 늦었을 수 도 있습니다.

다만, 이제 당신은 데이터를 가졌고, 당신의 다음 프로젝트를 시작할 때는 이것을 한번 생각해 보세요.

76 코멘트

코멘트 구독

코멘트를 달 수 없습니다.

  1. Hey i don’t understand this managed update concept.

    If i have 50 gameobjects with a script component that has update() function and inside the update it accesses gameobject position for example.

    In this case the gameobject position is unique to each gameobject.

    I need to have 50 update() calls, 1 for each gameobject.

    How do I implement an approach where i have only 1 update() in some manager and can access gameobject position of all objects??? i don’t understand how i would do this properly? Would i have to keep all 50 gameobjects in an array and access their positions in a loop and then pass data back into the gameobject?

    Is this better than having 50 update() funtions?

    1. Valentin Simonov

      2월 11, 2016 3:14 오후

      I think you are mixing two tasks together:
      1. A manager script which has an array of your script components on gameobjects which calls UpdateMe() function on each component every frame (as it was done in the example from the post).
      2. A manager script which has an array of gameobjects which does some logic (involving transforms) on all gameobjects in turn.
      The difference is that in (1) each script component does the work and accesses its own transform, while in (2) the manager does something with each transform it gets from array. In both cases every transform belongs to individual gameobject and is unique.

  2. Why not implement an in-house callback manager, that’s what a game engine is supposed to do!

  3. As an old-style developer I highly prefer to have my own handled managers, and only have one Update() on the top-level manager, that I then cascade down to the other classes as needed, only when needed. This blog post proves that this is a sane choice to make, and not implement Update() in each and every class, and have guard-clauses to return for every case when we don’t want it to actually run for that instance etc.

    1. This ^^^

      I’ve been building games this way for something around 20 years. It’s the correct approach for any game, not just unity games. This whole ‘attach every script to an object and use its Update()’ method really strikes me as the wrong way to do things.

  4. Just please make sure to provide plenty of tutorials and update the old ones accordingly to demonstrate.
    I promise you there are plenty of newer “developers” like myself that haven’t made it that far into UNITY yet that we wont have to re-train ourselves to this new approach.
    I don’t have a horse in this race yet so it doesn’t effect my interests yet.

    However I am all for improving Unity and making it better.

    Re-emphasizing – update old tutorials (re do them, please take the time to do this without simply adding annotations as Unity progresses (it only helps old tutorials so much before it isn’t anything like the original version) / this wont scare off potential new developers when something breaks from an old tutorial.

    I may be putting too much stock into what changes / approach the scripting team is doing though.

  5. So, after this article… Is Unity finally going to change the default Script template that ships to remove the empty Start() and Update() functions? Please?

    (I know that the templates can be changed, but srsly… why?)

    1. Still useful for newcomers ;)

      1. I agree, it really helped me when I started with Unity because there are lots of things/functions you still need to know. It doesn’t take that much time to remove them and also, how often do you create a script? Every hour? I doubt it.

        1. Aren’t empty methods typically optimized out by any compiler though?

  6. Has anyone tried this on PC? Is it normal that I’m getting 12ish ms on an i7 4770k, for the Update scene?

  7. Thanks for your article, Valentin! =)
    Going to bookmark it for colleagues who likes to leave empty Start() and Update() methods from the new C# script template even if they don’t need them.

  8. Mahan Moghaddam

    1월 2, 2016 7:10 오후

    Unity is a very good tool, I don’t know why some people complain. Compared to other tools created in the past decade, Unity is one of the best ones. Go ahead and run Unreal 3, it’s like you’re working with something from the 90’s. Unity has issues, but not that big, plus it’s a good practice for engine developers to follow Unity’s architecture, it worked fine for me.

  9. Interesting that unity posts things like this on their blog.

    The post basically says “the framework is old, bad, and slow, the new framework is even slower, here’s a half finished hack you can use instead”.

  10. First of all — great article. I’m always thrilled to learn something new, especially when its something that has a potentially huge impact.

    Second, has this “Managed Update” concept been some sort of Open Secret? This is the first I’ve read about it. All the articles I’ve read about Unity Performance Optimizations did not mention this, and I’ve read quite a few… or did I just miss it? I’ve just tried to search for more articles on the topic but there appears to be very little. I guess better late to the party than never.

    And last, (this is the part where I put on my fireproof pants), does this hold true if we’re developing using Javascript/Unityscript? My guess would be “yes” since for the final device build, I’m using IL2CPP. Just confirming.

    Cheers,
    Manny

    1. Valentin Simonov

      12월 30, 2015 12:53 오후

      Correct. C# and UnityScript are compiled to the same bytecode.
      Also it is worth mentioning that we’d like everyone to use C# instead of UnityScript.

      1. Stop writing documentation examples in Uscript and you’ll see newcomers using c# by default.

      2. Hi Valentin,

        While it look like you will drop support for UnityScript, do you have a plan in the future to support newer version of javascript instead? (es2015 and more) Or you just want a single language used to build a game with Unity?

        Kind Regards.

  11. I try to always use unity ‘tools’ instead of my customized ones … so, I don’t need to face problems because of my “scapes” LOL. Actually, I found messaging system really useful and clear to understand.

  12. “The scripting team are designing a whole new approach”
    Unity team: don’t change anything. Implementing managers in managed code – that’s what many developers already doing. In our case we have whole framework on top of unity api implemented in managed code: for fsm, messaging, etc.
    Unity is already great! Most important – it’s currently free!

    1. Collision events are completely out of our control. So yes, they DO have to fix this

      1. Collision detection already performed by physics engine. You want to perform polling yourself (in managed code)? To avoid undesired collision/trigger events – just disable that object/component when you don’t need it.

        1. I mean collision events also suffer a severe performance loss due to being called by the messaging system. And we can’t make a “manager” for that like we can with Update calls. This is something only unity can fix.

          (Also sorry for accidental double post)

    2. We have no control over OnCollisionStay events, for example. So they DO have to fix this

  13. This is extremely depressing. Depressing enough to make me want to switch to another game engine and tell all my acquaintances to avoid Unity.

    Completely replacing this atrocious messaging system should be Unity’s #1 priority right now, even if it pushes back everything everything else and/or isn’t backwards compatible. Can you at least reassure us that the team is working on a fix?

    1. The scripting team are designing a whole new approach that will (amongst other things) allow for faster scripting invocations, yes.

      1. Can i say something? This is not so bad…think about it…when you profile an application you see it…if you had profile some of your games, you already know that the Update call’s isn’t a bottleneck or something that decreases the performance deeply. It’s just consuming a little bit of time of the proccesor. So, it’s not so chaotical.

      2. Please, if you make such core change make it optional!
        For many people who already work smartly the current version is perfectly fine and the new version will for sure introduce new issues.
        So please if you do make such a big change make it completely separated from the main code like IL2CPP so that it won’t break existing systems.
        I couldn’t stand to wait god know how many version for a stable version because of a feature I don’t need.

      3. I know that folks here wouldn’t agree with me but I would really like to have a C API (not C++ but pure C) exposed, too. We’re using unity but only as “backend” and all the game core (90%) is done in C++ (as a native plugin) using mono embedded API. Another advantage of C API would be one can implement their own scripting subsystem. I don’t/can’t/want to use Unreal 4 because of some missing features/complications.

      4. Please don’t do that. Seriously. Don’t. All you’re going to do is introduce bugs and other issues into games that are already working just perfectly fine with the existing infrustructre just to accomodate a very small minority.

        If you want to do something, redesign Unity’s device input system. That needs a massvie overhaul. How about adding the ability to call gameObject.Find() on objects that are inactive? Lots of things that need improving, but the core architecture of your system works just fine. While I obviously can’t speak for everyone, I personally have no interest in rewriting my whole game to accomodate a massive change that isn’t being asked for.

    2. You can use single MonoBehavior for game logic (not talking about triggers/collision detection). Treat it’s Update() like WinMain(). Manually update your subsystems, like:
      //not a real code, just example
      void Update()
      {
      FSM.update();
      EntityManager.update();

      }

      1. This is what I’m doing. I have 1 monobehaviour script attached to 1 empty game object in the scene. That passes its Update() call to the various loaded managers and tasks the characters have to perform. Only the loaded classes get the calls. So there’s never a problem with 10000 update calls. There’s 1, and it calls the methods it needs to.

        I’m getting outstanding performance even at the highest rendering settings (I never drop below Unity’s capped 60 frames a second).

        There is **zero** reason to completely rewrite the core of Unity. It would very likely do nothing but break many millions of lines of code.

    3. Don’t forget that a lot of game studios made successful games even with these “awful” limitations. Unity is already pretty good as it is, any improvements will just make it better, not “more acceptable”.

  14. Thanks a lot for this post since it clarifies a lot of things.

    Having said that, I don’t think the comparison is fair with Mono, for the following reasons:

    1. The version of Mono that Unity is using is rather old. The newest one 4.2.1 has lots of improvements:

    http://www.mono-project.com/docs/about-mono/releases/4.0.0/
    http://www.mono-project.com/docs/about-mono/releases/4.2.1/

    These include, inter ália: better implementations of GC, optimizations in AOT compilation, SIMD and the inclusion of MSFT’s open sourced .NET code.

    2. Any interop call -either from .NET to Native or viceversa- has a penalty, due to, for example, the overhead of mapping unmanaged datatypes to managed ones. So, it’s not a surprise that updating behaviors from within a managed handler turns out to be faster than an interop call between the native part of the engine and behaviors.

    3. C# it-self, as a language, has been and still is improved over time, what in turn means that its methods are periodically reviewed and refactored to increase performance. What is more, the current trend is to go “Universal” with .NET Native so that, marginally, perf diferences between “pure” native code and “converted” one tend to zero. Something similar to what you’re doing with IL2CPP (and as said before, Mono has AOT compilation).

    4. Let me remind you that Unity also has its own version of .NET assemblies (based on C# 3.5) which, compared to .NET’s and Mono’s, could introduce unwanted overhead, specially in comparison to the latest version of the language: C# 6.

    5. UnityEngine was conceived and developed to do interop with other languages than C# (that is, UnityScript and Boo) -I don’t know whether the fact that the first versions of the Unity Editor were meant for MacOSX strongly influenced that decision or not, but its architecture wasn’t tailored to fit C# practices. As a matter of fact, in the attempt of avoiding deep hierarchies in favor of an entity-component-system approach, it does not fully comply with OOP. The fact that you have to query behavior in search of certain operations (like Update) as well as approaches like SendMessage/BroadcastMessage seem like a hack that had to be used as a result of that, which in turn doesn’t bring the best UX out-of-the-box on any IDE.

    6. A corolary of 5 above is how Unity handles events. Not only doesn’t it use C# approach to events but also it avoid the use of the Observable pattern at all. In fact, it goes the other way around: in the editor, for an event like button pressed, you have to specify not only the objects but the operations of those objects to call, instead of just registering as an observer and sending the same customized event args to all observers when the event happens. The result? A cumbersome way of handling events on the editor, in particular, when classes and operations change during game development, and finally,

    7. Lookups on arrays and lists shouldn’t differ in a way that using lists over arrays lead to overheads when traversing lists since the only moment when such penalties should exist is when adding elements obliges the list to expand its inner array. If they differ, then you should check up the version of the Mono compiler you are using, since lookups are optimized in .NET for lists to avoid such thing (and I guess that also in current versions of Mono).

    Again, this post is valuable since it help us deal with perf with Unity, but it also exposes some issues that could be considered design flaws. I don’t want to offend anyone with this. On the contrary, I want it to become a better engine. In this sense, imvho, performance of the UnityEngine not only could be improved with native compilation (IL2CPP) but also by a full redesign so that on the behaviors side it fully complies with OOP and C# standards avoiding interop calls as much as possible. In short: a) get rid of SendMessage approach, b) implement a managed handlers for calls to Update, LateUpdate, and so on so forth, c) either include Update (and other) methods where appropriate in Monobehaviors or create interfaces for them, d) re-implement how you handle events, e) support c# 6, f) upgrade your Mono backend to 4.x, and g) try not to use you own versions of .NET assemblies unless really necessary and optimized.

    I hope you guys consider/are considering this. Thanks again.

  15. Valentin I know back at one of the Unite roadmap sessions this year they had mentioned that there was work being started on a MonoBehaviour replacement/alternative where they might move away from magic methods and make some new choices, do you think that we will here more about it in the near future?

    1. Valentin Simonov

      12월 25, 2015 7:04 오전

      Sure, as Richard said one comment above the scripting team are designing a whole new approach to MonoBehaviours. Expect to hear about it soon.

  16. OK, i read about this somewhere else, so in my current project instead of using Update() as the main loop for my characters i start coroutine in Start(), the coroutine stops when the characters is dead.
    should this improve performance?

    1. I took a look at StartCoroutine, it’s actually a call into the native code. That means it’s handled on the unmanaged side so it should be about the same system as Update, only temporarily. Maybe Overhead is a bit lower, as it does not need to fetch Update from your MonoBehaviour explicitly, but idk. I wouldn’t count on it, but you could actually perform a similar test;)

      1. Valentin Simonov

        12월 24, 2015 12:55 오후

        I updated the test on GitHub and it looks that this Coroutines approach is 5 times slower than using Updates. Logically it should be slower at least because the engine has to make 2 calls to the returned enumerator: moveNext and current.

        1. There’s also the fact that co-routines have a per frame GC alloc, whereas Update does not.

  17. I wouldn’t expect this kind of articles on the unity blog. It’s like to admit that the framework is old and inefficient, but instead to do something about it, let you know that there are alternatives. Luckily there are alternatives.

    1. Valentin Simonov

      12월 25, 2015 7:20 오전

      Developers want to make beautiful and successful games, we help them providing the tools and support services so they’d use the tool right. Meanwhile we are working on improving various subsystems of the engine.

    1. Screwed up the formatting on that.

      Of course, in the future we want to have a more convenient way to specify execution order, using an attribute in code for example.

      I made an asset which does just that.

      It would be pretty cool if the attribute they implement can be used on individual methods. So you could have a script Awake before everything else, but Update after.

  18. Nice. These are the types of posts I’d like to see more of.

  19. What happens with the Update methods from built in scripts that we cannot edit (Camera component, Collider component, MeshRenderer, etc) ?

    1. They’re not scripts, so they don’t work like MonoScripts do. Instead, all components of a particular type are added to a ‘manager’ when they’re enabled, and the manager is called directly at the appropriate point in the main game loop; it then does the work for the components in the most efficient way possible (looping through them one at a time, dispatching them to worker threads, or whatever makes sense for the subsystem in question).

      1. And what about other functions that are Mono Behaviour called repeatedly like LateUpdate (We must manage them too?)?

        Thank you Richard.

        1. Valentin Simonov

          12월 24, 2015 9:03 오전

          Yes. Update, LateUpdate, FixedUpdate — these go through the same code path.

  20. Wow! Thanks for the tip!

  21. Unless I’m missing something, it seems that having a manager in managed code that calls all the update functions is significantly more efficient than letting the engine call them all from c++ code.

    In that case, I ask myself, why not create this manager, in managed code, as part of Unity’s engine, and have it always be the one that calls update? It sounds to me like this should be an architecture decision by Unity, that update calls (and other calls that happen a lot) should be handled from a central part of the engine that runs in managed code to start with.

    Is this something you guys in Unity are thinking about?

    1. Valentin Simonov

      12월 25, 2015 7:13 오전

      See the previous reply.

  22. Nice article, although I personally still hadn’t hit this limit myself.

    A couple of questions:

    1. You mention that you cache the method reference and then invoke it from a list; in case of an empty method – can’t you discard the method so it won’t even be considered for execution? (you’re already using mono.cecil for a bunch of internal tools, you could use it to check if the method body is empty).

    2. Did you consider creating a managed-side Update manager that will avoid most of these costs of native-to-managed calls? (or are those checks expensive for other reasons too?)

    3. The title for one of the sections is “INTERFACE CALLS, VIRTUAL CALLS AND ARRAY ACCESS”, but nothing is mentioned regarding those topics… how do interface/virtual calls affect the performance ?

    Again, nice post and keep ’em coming.

    1. Valentin Simonov

      12월 25, 2015 7:11 오전

      1. I suspect that nobody did this because why would you have an empty Update method in the first place?! o.O But this seems to be a good candidate for the next Hack Week.

      2. I don’t know all the details but it’s not that simple to make it easy and fast. The scripting team is designing the whole new approach as we speak.

      3. This was meant to be the reference to http://blogs.unity3d.com/2015/06/03/il2cpp-internals-method-calls/ since list[i] is an interface call.

      1. Regarding #1, I added that in the base ruleset in “Unity Gendarme” (a fork of the fantastic Gendarme analysis tool). I did that was a while ago while between jobs, so the code is probably a bit crap, but apparently relevant to the discussion.
        https://github.com/fderudder/unity-gendarme/tree/master/gendarme/rules/Unity.Rules.Performance

        Funnily enough, empty methods are mode widespread that you might think at first. Like having a “create new script” template with all the methods by default, so you don’t have to look them up.

        One of the projects I worked on had several empty methods, and I regularly check some released Unity games to check that. You’d be surprised.

      2. Cameron Bonde

        1월 7, 2016 4:05 오전

        If you did detect empty functions, you could actually do the virtual methods interface without punishment.

      3. anonymized to prevent reprisals

        1월 19, 2016 11:24 오전

        There might be an empty Update() method because every single Unity-generated script contains Start() and Update() definitions by default; this was actually mentioned previously in the thread.

        Not to be rude, but your question seems nonsensical, condescending, asinine, and a bit obtuse. Unity3D might do well to reconsider having you in a customer-facing position, or at least moderate your comments to make sure you aren’t alienating new developers with your inconsiderate and thoughtless remarks.

  23. Very interesting article. Am i wrong of concluding that if we have a lot of Update calls on MonoBehaviours it is better to use our own implementation of the Update Calls (Iterates trough monobehaviours with simple arrays and call UpdateMe and so on). I am about to check the project that Valentin uploaded but first i have this doubt.

    And in the other hand, i think that this is something that Unity has to solve as fast as possible.

    Thank you and happy holidays.

  24. I realize this post isn’t meant to compare Mono and IL2CPP, but man, it sure does make IL2CPP look bad. It’s supposed to be innately faster than Mono, yet months into its existence it’s 100% slower in this scenario? Calling MonoBehaviour methods isn’t exactly an edge case.

    1. Valentin Simonov

      12월 24, 2015 9:54 오전

      Well, IL2CPP is faster than Mono in a lot of things:
      http://blogs.unity3d.com/2015/01/29/unity-4-6-2-ios-64-bit-support/
      In this particular case it might not be very well optimized yet. I ran the test with array on Mono and got 0.23ms vs. 0.22ms in IL2CPP. But once again, the post is not to compare virtual machines.

  25. Great to see unity taking a look at this – I’ve been reporting the issue of slow callbacks through bugs, feedback site and talking directly to Unity devs.

    The Update calls are bad enough, but everything using SendMessages (OnEnable etc.) are even worse to a point where we had to seriously re-engineer stuff to make streaming not hickup on unity calling OnEnable. Not the code in of OnEnable, them, just _calling_ them. This was for about 2000 objects on PC level hardware, and ~10 ms was spent on calling those. Yikes.

    The last paragraph slighty concers me. Are we really expected to avoid using Unity callbacks for performance? 10K is not a lot, 2k OnEnable’s is nothing. We can do some special case handling but it feels like it really shouldn’t be a concern to use these callbacks.

    It feels like Unity really needs to take a hard look on how to improve the performance on these callbacks. Looking at the numbers there’s a lot of stuff in there that seems duplicate (checking if the call is valid, then if it exists, then if the arguments are valid….), redundant (lots of time spent in argument handling – when there’ll never be arguments to Update) or cachable (whether method exists etc.).

    It’s good to hear that the IL2CPP runtime is being optimized for this case but it seems that generally it needs to be considered how to structure the engine to make this faster.

    And good to see it’s at least on the radar that it’s slow now :)

    1. Valentin Simonov

      12월 24, 2015 9:19 오전

      OnEnable actually uses the same mechanism as Update. Have you profiled the code and witnessed that it was really just calling this method and not awaking other components on your game objects? Physics component, Animator and other components are pretty costly to create, enable and disable.

      > Are we really expected to avoid using Unity callbacks for performance?
      No, you need to think about this limitation when architecting your game and vigorously profile. If this particular issue affects your game you know what to do.

      Also, as I said recent scripting optimizations remove some of the overhead you mentioned.

  26. It turns out that if you’d wanted to iterate through a list of 10000 elements every frame you’d better use an array instead of a List because in this case generated C++ code is simpler and array access is just faster.

    This kind of comment irks me to no end. On the one hand, Unity is saying “hey, you can program your game with C#, which is an easy-to-use, safe, productive and powerful language!”. But then it’s like “oh yeah, all those features that make the language so easy and productive, yeah, you shouldn’t use those because they’re slow”. It sends a conflicting message and leads to a lot of unnecessary arguments about what ‘good’ code is. At this point everyone would be better off if we could just write our games directly in C++.

    Not only that, but a List in C# is nothing more than a wrapper around an array with automatic array resizing features. If the IL2CPP compiler had a special case to recognize and deal with Lists specifically (which is what a lot of Microsoft’s .NET compiler tools do too), then there’s no reason why iterating over a List should be any slower than iterating over an array.

    1. I’m guessing it’s because indexing a list is technically a member function, which means il2cpp will do a null check before each call, and maybe not properly inlining this call etc.

      However, I agree that it’s really bad that we have to worry about that. Unity mentioned they’d like to special case optimize IL2CPP for unity code. Seems list access should definitely be one of them.

      1. Yes, indexing a List invokes a call to the getter of the Item property, which in turn indexes the _items array field inside the List object. And since the Item property is an implementation of the IList interface, that call is a virtual method call too, bringing with it all sorts of overhead. Null checks don’t even make the difference here; C# being a safe language will also null-check a plain array when indexing it.

        This is precisely why you would want the IL2CPP compiler to know about Lists and create a shortcut for the indexing operator that accesses the internal array directly. From a puristic standpoint, it’s not nice to give a compiler knowledge about classes inside the framework, but it does open up a lot of possibilities for optimization. And the .NET compiler does it too, e.g. by translating large switch statements into a Dictionary-based lookup table.

        My bigger issue however is that with all these edge cases, people end up writing C# code for Unity as if it is plain old C, which basically means throwing out everything that makes C# worthwhile as a language in the first place. Writing idiomatic C# code (using LINQ, foreach statements, relying on the GC for memory management, etc) in Unity is considered by many as ‘wrong’, leading to a lot of micro-optimization work, and you kind of end up with the worst of both worlds. It makes you wonder why we’re still using Mono at all.

        1. Valentin Simonov

          12월 24, 2015 9:33 오전

          Nico, I understand your concerns and I am the person usually talking to customers explaining the things you mentioned advising them to “make their code dumber”. There’s nothing inherently bad in Lists, Lambdas, LINQ, foreach and other .NET features, but considering mobile devices limitations one have to use the most CPU and GC efficient code they can write. This is how mobile devices and Unity as a platform work right now and developers need to know that.

          Not to mention that to write efficient and error-prone code in C++ you need to have a lot of knowledge and experience as well.

          We are working on improving current situation, on Mono/.NET upgrade, implementing various scripting and IL2CPP optimizations. But scripting is only a small part of the engine, we also have a lot of other really important things to do and unfortunately not enough people. Patience, my friend (8

    2. Valentin Simonov

      12월 24, 2015 9:57 오전

      Another thing is that Lists are just slower or more precisely our Mono compiler generates slower code for them. I ran the test with array on Mono and got 0.23ms (was 0.52ms with List) vs. 0.22ms in IL2CPP.

  27. Instead of using an abstract “BaseMonobehaviour” class which implements all these methods, I recommend creating granular, individual interfaces for the particular behaviours you want your objects to implement. For example:


    public interface IUpdate
    {
    void Update();
    }

    public interface IAwake
    {
    void Awake();
    }

    Any “MonoBehaviour” you create implements only the interfaces you intend for it to do:


    public class MyBehaviour : MonoBehaviour, IUpdate
    {
    public void IUpdate()
    {
    //do something
    }
    }

    Of course at this point, you have compile-time safety to catch those nasty typo’d case sensitive names (Anyone out there spend too much time trying to figure out and fix a “void update()”?)

    Regarding intellisense/tooling, with Visual Studio (I can’t speak for the other IDEs), you can auto-implement the interface to deposit the placeholder method automatically. Furthermore, this also gives you a maintenance benefit in that it becomes trivial to find all objects that are “Updating” or “Awaking” or have “FixedUpdate” (just “Find All References” to the particular interface)

    Finally, if you find you keep implementing the same combination of interfaces, you can always combine them to a common interface:


    public interface IStandardBehaviour : IAwake, IStart, IUpdate, IOnEnable
    {

    }

    public class MyBehaviour : MonoBehaviour, IStandardBehaviour
    {
    //implement all 4 interfaces inherited by IStandardBehaviour
    }

    (Sorry if the code formatting above got botched)

    1. Woops, typo’d my first “MyBehaviour” class implementation’s “Update” method name. It should read:


      public class MyBehaviour : MonoBehaviour, IUpdate
      {
      public void Update()
      {
      //do something
      }
      }

  28. Very interesting. Is there anyway to increase performance for OnCollision events? I have a project with a large number (10000+) of GameObjects that have no Update() but all have OnCollisionEnter() and a performance boost would be amazing.

    1. Valentin Simonov

      12월 23, 2015 5:47 오후

      OnCollisionEnter and its friends are implemented through SendMessage so it should be even worse*. Of course Unity doesn’t try to send these messages to scripts which don’t have such callbacks, also one rarely has 10000 objects colliding with each other EVERY frame.

      When working with 2D and 3D physics you should look at other things which reduce performance like not moving static colliders or 2D colliders inside a Rigidbody2D.

      1. Robert Cummings

        12월 23, 2015 6:53 오후

        Very interesting article. But regarding physics OnCollision/OnStay – especially stay, needs optimising Unity’s side. It is grotesquely inefficient to the point where we had to change the gameplay and there wasn’t alternatives for it (think physical interactions between characters). We really needed stay but it crapped out with just 30 characters on an i7.

        So going forward, if you’re using Sendmessage internally for collision, I don’t think that’s very optimal, do you? Perhaps a better pattern can be achieved?

        Thanks for hard work, regardless :)

        1. Morten Skaaning

          12월 24, 2015 12:51 오후

          Hi,

          Can you open a bug with a test scene in it, when we can get to work on it. Also post the bug Id here.

          Regards,
          Morten

      2. “2D colliders inside a Rigidbody2D”

        Can you elaborate on this? Don’t colliders need to have a rigidbody to register collisions?

        1. Valentin Simonov

          12월 24, 2015 9:37 오전

          Right, dynamic 2D colliders must be children of a Rigidbody2D. And if you move objects with 2D colliders make sure that you move the objects with Rigidbody2D components and not colliders inside Ribidbody2D themselves. If you move a collider manually Box2D has to destroy it and create again every time since there’s no way to move colliders (or fixtures as they are called) in Box2D. This will give you a very noticeable overhead in Profiler if done every frame.