Search Unity

Character Controller Memory Allocation

Discussion in 'iOS and tvOS' started by zbuffer, Nov 1, 2009.

  1. zbuffer

    zbuffer

    Joined:
    May 5, 2009
    Posts:
    18
    Hi All,

    I'm a programming noob so please go easy on me. I'm working on a 2D sidescroller game and I've noticed that the Character Controller allocates memory while isGrounded = true.

    As a test, I used FPSWalker.js on a GO with the character controller attached. There is nothing else in the scene except for a ground plane.

    While the player is in the air (jumping) the used heap does NOT increase. But as soon as the character is grounded the heap increases in increments of 4,096 which eventually leads to garbage collection as the allocated heap is inevitably exceeded.

    This is very bad for the iphone as I believe it to be the source of performance hitches in my game.

    Has anybody experienced this problem and is there an alternative that will prevent or manually release these allocations.

    thanks,
    scott.
     
  2. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    The only thing in the script that I could imagine being the cause of this is the GetComponent call on line 24. You could try removing that line and replacing it with a single call in the Start method:-
    Code (csharp):
    1. var controller : CharacterController;
    2.  
    3. function Start() {
    4.     controller = GetComponent(CharacterController);
    5. }
     
  3. zbuffer

    zbuffer

    Joined:
    May 5, 2009
    Posts:
    18
    Hi andeeee,

    Thanks for your reply. Unfortunately, that is not the case. Here is the code I was using for the test:

    Code (csharp):
    1.  
    2. var speed = 6.0;
    3. var jumpSpeed = 20.0;
    4. var gravity = 20.0;
    5.  
    6. private var moveDirection = Vector3.zero;
    7. private var controller : CharacterController;
    8.  
    9. function Start () {
    10.    
    11.     controller = GetComponent(CharacterController);
    12. }
    13.  
    14. function Update() {
    15.  
    16.     if (controller.isGrounded) {
    17.        
    18.         moveDirection = Vector3(Input.GetAxis("Horizontal"), 0,
    19.         Input.GetAxis("Vertical"));
    20.         moveDirection = transform.TransformDirection(moveDirection);
    21.         moveDirection *= speed;
    22.  
    23.     if (Input.GetMouseButtonDown(0)) {
    24.         moveDirection.y = jumpSpeed;
    25.     }
    26.  }
    27.  
    28. // Apply gravity
    29. moveDirection.y -= gravity * Time.deltaTime;
    30.  
    31. // Move the controller
    32. controller.Move(moveDirection * Time.deltaTime);
    33. }
    34.  
    I changed the jump to the GetMouseButtonDown so a touch would activate it.

    As long as the player is in the air, no allocations are done. When the character controller is grounded the used heap increases.
     
  4. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    sure it continues to raise and thats the used heap not the allocated heap?

    Because a given raise is normal, thats just the pooling that stacks up to a given


    Generally: why do you allocate a new vector3 each frame instead of just altering the x and z component of moveDirection with the axis values? That will stack up a lot of garbage on its own.
    Same goes for any further line which always generates another new vector (you actually have 5 new vectors there unless unity implements the scalar multiply and addition / substraction there insitu without generating a new vector)

    You could reduce that to 1 new vector by doing step 1 (and 3, 4, 5 depending on the implementation) of the movedirection handling on your own
     
  5. zbuffer

    zbuffer

    Joined:
    May 5, 2009
    Posts:
    18
    Yes, it is the used heap increasing. So that is normal? I was under the impression that, if properly managed, the used heap will not increase, which is optimal to avoid GC.

    I'm new to programming and I've borrowed this locomotion script from the manual. On an abstract level I certainly understand what the script is doing, but I don't understand where the allocations are occurring.

    I assume you mean here:
    Code (csharp):
    1.  
    2.         moveDirection = Vector3(Input.GetAxis("Horizontal"), 0,
    3. Input.GetAxis("Vertical"));
    4.  
    or here:
    Code (csharp):
    1.  
    2. controller.Move(moveDirection * Time.deltaTime);
    3.  
    My understanding (or lack thereof) was that declaring variables outside of Update would prevent the used heap from increasing. I thought the value for moveDirection was being tossed and re-assigned every update.

    Can you give me an example?
     
  6. zbuffer

    zbuffer

    Joined:
    May 5, 2009
    Posts:
    18
    After doing some more research, it doesn't seem that vector3 allocations are causing the used heap to increase.

    1. In the iphone roadmap the following is stated:

    2. I did another experiment (same setup: only thing in the scene in the player and ground plane) except I used a rigidbody based system with the physics controller from the wiki and the used heap did NOT increase.

    Maybe its not apples to apples because its physics based, but the locomotion script is similar to the character controller based FPSWalker by making use of vector3 to determine velocity and direction.

    I can only assume at this point that either Controller.isGrounded or Controller.Move is the source of heap allocations.

    Is this a bug, or just the given cost of using the Character Controller?
     
  7. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    I don't think the CharacterController thing is a bug. The idea of automatic memory management is that you should be freed from having to worry about the exact details. Memory allocation and GC certainly take time, but you need to bear in mind that you would have to do all this explicitly in your code if it weren't done for you. It would still take time, but you just wouldn't be conscious of it.

    There are a few GC-killing algorithms, and some are surprisingly mundane (eg, string concatenation in a loop). However, unless you use something like that, GC is generally no more of a performance drain than manual MM.
     
  8. zbuffer

    zbuffer

    Joined:
    May 5, 2009
    Posts:
    18
    For this particular test scene, GC is invoked approximately every 30 seconds. This is assuming that the character is always in contact with the ground. The average collection duration is 6.0 ms.

    That doesn't sound like enough (on its own) to cause a perceivable hitch. Would that be compounded if there were multiple character controllers in the scene? ie. a player character and a few enemy characters.

    So not a bug, just expensive is what I'm taking away from this.

    Thanks andeeee.
     
  9. Big Pig

    Big Pig

    Joined:
    Feb 21, 2009
    Posts:
    92
    I'm also getting this kind of behavior from CharacterController and in my case this is really noticeable since I'm using several CharacterControllers and heap memory usage just climbs and climbs whenever I call Move.

    I'm going to submit this as bug as soon as I find some time to whip up a simple project.

    Alex
     
  10. Aiursrage2k

    Aiursrage2k

    Joined:
    Nov 1, 2009
    Posts:
    4,835
    Cant say why it would be allocating memory. But character controllers are usually pretty expensive especially the PhysX controller. I would look at the character controller parameter minMoveDistance. I know that Unity suggests setting this at 0 but I think that might not be a good idea, you see the controller (assuming its using the PhysX character controller).

    From the PhysX documenation.
    So if possible try setting this value to something like 0.0001 or whatever is possible.

    If possible try using a rigidBody for the enemies as it should be less expensive.
     
  11. zbuffer

    zbuffer

    Joined:
    May 5, 2009
    Posts:
    18
    The highest workable setting I could get was .01

    This did reduce the frequency of allocations. GC was invoked approx every min, instead of 30 seconds. However, the heap still grows at a fairly rapid rate and the average GC duration increased to 7.2

    I've been toying with rigidbodies and am contemplating converting the enemy NPC's to that system, and possibly the player character itself. It's unfortunate I've invested a significant amount of time into the character controllers. It certainly handles collision detection well so its hard to complain about the results. I feel like there should at least be a warning label in a documentation.

    Ah well, welcome to iphone development.
     
  12. HanulTech

    HanulTech

    Joined:
    Apr 5, 2009
    Posts:
    312
    As dreamora mentioned, this is caused by this line:

    Code (csharp):
    1.  
    2. moveDirection = Vector3(Input.GetAxis("Horizontal"), 0,
    3. Input.GetAxis("Vertical"));
    4.  
    You are creating a new instance of Vector3 (which I believe is implemented as a struct under the covers) each frame.

    So instead of doing that, do this:

    this.moveDirection.x = ....
    this.moveDirection.y = ....
    this.moveDirection.z = ....
     
  13. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    I'm reasonably sure this doesn't create a new heap object each time.
     
  14. HanulTech

    HanulTech

    Joined:
    Apr 5, 2009
    Posts:
    312
    You are creating a new struct each time you call Vector3(), no?

    If creating a new structure doesn't require allocating memory, then there is magic going on behind the scenes that I am unaware of.
     
  15. HanulTech

    HanulTech

    Joined:
    Apr 5, 2009
    Posts:
    312
    Hmmm, looks you might be right. Magic afterall. I remembered seeing Nicholas post this:

    Which makes me wonder if when you write this in javascript:

    transform.position = Vector3(...)

    The code is "compiled" into property sets on an existing structure under the covers, rather than creation of a new struct.
     
  16. rozgo

    rozgo

    Joined:
    Feb 7, 2008
    Posts:
    158
    CharacterController does allocate heap, tons of it. I had to switch to a rigidbody solution, and coding a custom character controller.

    Unity's Vector3 are value types that do not get allocated on the heap, but on the stack, which is immediately release on leaving the function.

    In the example giving by zbuffer, CharacterController is the culprit and I suspect the work to generate the OnCharacterControllerHit data is resposible.
     
  17. HanulTech

    HanulTech

    Joined:
    Apr 5, 2009
    Posts:
    312
    Heap allocation in both Mono and Microsoft implementations of C# is actually implemented via a large stack, and "heap allocation" is implemented as simple push operations on that stack. So the heap vs. stack allocation question is pretty meaningless in terms of the impact on GC behavior. (All sorts of discussion on this on stackoverflow if your interested.)

    Regarding the question of when a value type's memory is reclaimed, this happens precisely when the variable goes out of scope. If the value is passed to a function or declared within the function's scope, then, yes, this occurs when the function call returns. If a variable is a member of an object, then the variable's scope is that of its parent object.
     
  18. rozgo

    rozgo

    Joined:
    Feb 7, 2008
    Posts:
    158
    The profiler has the final say, stack-based or not, it clearly states heap vs. stack (behavior, semantics aside) is meaningful and the effort must be put to understand the allocation pattern of the engine and its effect on the garbage collection time.
     
  19. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    The Mono implementation of .NET actually uses the Boehm conservative GC. Undoubtedly, stacks will be used in its operation, but this is not how allocation is achieved.

    Talking about structs, when we refer to "stack allocation", it means that the entire memory needed by the structure is allocated among other local variables on the call stack. This in contrast to what usually happens - the object itself is allocated on the heap and only a pointer is added to the stack frame.

    The significance of stack data is that it is implicitly reclaimed after the function returns, so there is no collection required. For a struct, the whole of its data is recovered after a function return, but for a class object, only the small pointer is freed up - the referenced heap object is reclaimed at the next GC cycle.
     
  20. bumba

    bumba

    Joined:
    Oct 10, 2008
    Posts:
    358
    im using character controller too and my heap is not rising. I add this code in my update, dont know if this will solve the problem for you:

    Code (csharp):
    1.  
    2.  
    3.     if (Time.frameCount % 30 == 0)
    4.     {
    5.       System.GC.Collect();
    6.  
    7.     }
    8.  
    9.  
     
  21. HanulTech

    HanulTech

    Joined:
    Apr 5, 2009
    Posts:
    312
    Yes, but that doesn't change the fact that the heap is actually implemented as a stack and therefore choosing between heap and stack allocation, in general, does not alter the impact on GC performance. Of course, the question of variable scope does impact *when* something is marked for collection. If you have a struct that has function scope then it is marked for collection when the function returns. If it is a member variable on an object that it is not marked for collection until cessation of the object's scope. If it is marked as static, then never. Are you arguing that this is not the case?
     
  22. saneera

    saneera

    Joined:
    Sep 21, 2011
    Posts:
    9
    I just had notice if u move the Update to Fixed Update the memory allocation is gone

    if u do calculate the character controller.move in Update there will be a performance hit
     
  23. diekeure

    diekeure

    Joined:
    Jan 25, 2013
    Posts:
    221
    This is an old thread, but I still run into this issue.

    Executing CharacterController.Move in FixedUpdate does not resulve this issue
    Every frame there is a 60B alloc in CharacterController.INTERNAL_CALL_Move()
     
  24. xDavidLeon

    xDavidLeon

    Joined:
    Jun 9, 2014
    Posts:
    123
    Hey man, I know this is an old thread, but did you find a way to solve this? We've got around 50 enemies in the level all using Character Controllers, and moving them around is taking around 30% of the CPU right now.
     
  25. diekeure

    diekeure

    Joined:
    Jan 25, 2013
    Posts:
    221
    Nope, it's still there, using Unity 5.3.1f1 and still Unity allocates quite some memory every frame.
     
    VertexSoup likes this.
  26. artaka

    artaka

    Joined:
    Feb 19, 2013
    Posts:
    128
    I'm also experiencing the same issue with CharacterController.INTERNAL_CALL_Move() allocating 80B every frame. I'm using Unity 5.6.0.f3.
     
  27. CswiedlerTrion

    CswiedlerTrion

    Joined:
    Dec 6, 2014
    Posts:
    15
    It looks like this only happens if you have a OnControllerColliderHit method declared (and then I assume only if there's a collision, but with gravity that will nearly always be the case), because Unity has to allocate the hit information and provide it to you.
     
  28. artaka

    artaka

    Joined:
    Feb 19, 2013
    Posts:
    128
    I narrowed it down to that as well. I wonder why ControllerColliderHit was made into a class rather than a struct.
     
  29. yant

    yant

    Unity Technologies

    Joined:
    Jul 24, 2013
    Posts:
    596
    I'd say the only reason for it being a class rather than a struct is historical. We can't change the signature now since all the pre-compiled dlls won't work then. One way to upgrade would be to introduce another callback that doesn't allocate. However, a viable point is that you arguably shouldn't use CharacterController in any bigger project. It's based on a old example code from a decade ago and is not really customizable.

    That said, there is a team here that aims to rework CharacterController so that it's completely in C# and as customizable as it gets (and doesn't employ any GC-allocating callbacks neither).
     
  30. JSwigartPlayful

    JSwigartPlayful

    Joined:
    Feb 29, 2016
    Posts:
    18
    Is there any progress on this reworked CharacterController? 5 months later and it still seems like Unity recommends against using one of the most fundamental components of virtually any game.
     
    Billy4184 likes this.
  31. hungrybelome

    hungrybelome

    Joined:
    Dec 31, 2014
    Posts:
    336
    Bump. So is using the built-in CharacterController recommended against due to OnControllerColliderHit GC alloc?
     
  32. DebugLogError

    DebugLogError

    Joined:
    Jul 24, 2013
    Posts:
    60
    I believe this is the reworked character controller. However, the repo says it is in beta and there have been no updates for almost a year...

    https://github.com/Unity-Technologies/Standard-Assets-Characters

    Edit: It also appears that removing the OnControllerCollider hit from your player class eliminates the GC allocations.
     
    Last edited: Sep 9, 2020
  33. GloriaVictis

    GloriaVictis

    Joined:
    Sep 1, 2016
    Posts:
    133
    Any update on the matter?
     
  34. toyhunter

    toyhunter

    Joined:
    Mar 5, 2018
    Posts:
    76
  35. toddw

    toddw

    Joined:
    May 9, 2010
    Posts:
    130
    I realize this thread is extremely old, but for the benefit of other devs who may read I'd like to clarify. I've spent a lot of time in my career working in C# and VB.NET, optimizing bottlenecks and it almost always comes down to excessive heap allocations. When for example, you have a loop concatenating several strings, you will eventually see the CPU spike and depending on the number of bytes already occupied by the process, plus the bytes in the strings being concatenated, the CPU spike will vary in both intensity and duration.

    Reference type variables are comprised of both the stack and the heap. For a single reference type variable, such as an instance of a class or even a string literal, a chunk of contiguous memory is allocated in the heap in the first memory address large enough to hold the expected size of the reference type. Additionally, an int32 is allocated from the stack holding the pointer of the memory address in the heap. When a reference type variable goes out of scope, assuming it's not an un-managed resource (reference types not controlled by the GC which must be explicitly disposed of by a class destructor, System.IO.Stream is an example of an un-managed resource) the heap memory address to which the variable pointed will remain occupied until the garbage collector runs it's next cycle, determines there is no longer a reference to the memory and clears the memory location plus removes the reference from the GC so it no longer analyzes it during each cycle.

    The performance hit you get from reference types are due to two primary factors:
    1. how long each cycle of the GC takes, which is dependent upon how many reference types are currently being tracked by the GC. If you have 1,000 objects in the GC you will likely see no impact on frame rate, but 1,000,000 will certainly cause latency. Same idea as any "for loop" you write, it's going to take substantially longer to iterate 1,000,000 times as it will 1,000.
    2. the process of growing the heap in order to allow for new reference type instances to be allocated. This will happen more frequently if you are creating new instances of classes, concatenating strings or even calling methods with string literals. Additionally it's worth noting that if you are instantiating and destroying reference types frequently, the heap will become fragmented, which will result in more growth of the heap because the system will not be able to find contiguous chunks of memory in the heap the process already owns, so in some cases you may see the heap continue to grow even though you've destroyed most of your objects.

    As a general rule, I try to ensure that all my objects are pooled in the scope of the Start or Awake methods. Sometimes this is beyond your control, so there will always be a little bit of memory allocation throughout the life of a game, however, if you can ensure that it's not occurring every frame you'll be in much better shape, especially on consoles and mobile devices.
     
    Last edited: Dec 31, 2020
  36. toddw

    toddw

    Joined:
    May 9, 2010
    Posts:
    130
    Now for why I'm here on this thread in the first place...

    I too noticed I was getting 80 b of heap allocation per frame in CharacterController.Move in Unity 2020.1.2f1. I use the character controller for all enemies and the player in my game, so when there's 10 enemies on the screen, it's not the sole cause of latency, but it is a factor. It turns out this was due to my implementation of ControllerColliderHit as stated by @artaka and the 80 b allocation was due to the ControllerColliderHit instance being sent by the engine as explained by @yant .

    I was able to achieve the same functionality by commenting out the ControllerColliderHit method and instead using a simple Raycast to obtain the ground normal.

    Code (CSharp):
    1.  
    2.     void GetGroundNormal()
    3.     {
    4.         if (Physics.Raycast(this.m_Transform.position, Vector3.down, out RaycastHit hit, CharacterController.height * 0.6f, GlobalSettingManager.EnvironmentLayers, QueryTriggerInteraction.Ignore))
    5.         {
    6.             m_GroundNormal = hit.normal;
    7.         }
    8.     }
    I then call this method in LateUpdate after calling CharacterController.Move and testing if my character is currently touching the ground.

    Code (CSharp):
    1.     private void LateUpdate()
    2.     {
    3.  
    4.         IsGrounded = (CharacterController.Move(DeltaPositionWithGravity + DeltaPositionOffset) & CollisionFlags.CollidedBelow) != 0;
    5.  
    6.         if (IsGrounded)
    7.         {
    8.             GetGroundNormal();
    9.         }
    10.  
    11.     }
     
    tw00275, mrtenda and xDavidLeon like this.
  37. rafaelcraftero_unity

    rafaelcraftero_unity

    Joined:
    Jun 30, 2020
    Posts:
    8
    Any advance in 4 years about the new character controller?
     
  38. Jakub_Machowski

    Jakub_Machowski

    Joined:
    Mar 19, 2013
    Posts:
    647
    SO basically anyone achieved a way of fixing it using Move Character collider without CGI of 80B?
     
  39. gfas

    gfas

    Joined:
    Dec 30, 2020
    Posts:
    13
    Thanks @toddw !

    I was getting a 80B gc.alloc every frame and thanks to your post got rid of it replacing the `OnControllerColliderHit ` with a custom raycast.

    The only difference is colliderhit exposes a `.moveDirection` property, so instead I use:
    `Vector3 moveDirection = _controller.velocity.normalized;` in my hit handler and it seems to work.