Speed comparison: 2d Unity vs Monkey

Dear all, being both a Unity3d and a Monkey (language! lol) lover I just made a quick test regarding 2d performance of both and would like to share the numbers:

I’ve used 2000 sprites which move randomly over the screen. Results are:

Unity 4.5
APK size: 8,404,358 bytes
FPS: ~20

Monkey v79c
APK size: 67,331 bytes
FPS: ~33 (previously I did a wrong fps calculation)

Unity C# code:

using UnityEngine;
using System.Collections;

public class Sprites : MonoBehaviour
{
  public GameObject _sprite;
  
  private ArrayList _spriteList;
  
  // Use this for initialization
  void Start()
  {
    _spriteList = new ArrayList();
    for( int i = 0; i < 2000; i++ )
    {
      GameObject sprite = (GameObject)Instantiate( _sprite, new Vector3( Random.Range( -6.0f, 6.0f ), Random.Range( -4.0f, 4.0f ) ), Quaternion.identity );
      _spriteList.Add( sprite );
    }
  }

  // Update is called once per frame
  void Update()
  {	
    for( int i = 0; i < _spriteList.Count; i++ )
    {
      GameObject s = (GameObject)_spriteList[i];
      Vector3 position = s.transform.position;
      float deltaTime = Time.deltaTime;
      position += new Vector3( Random.Range( -5.0f, 5.0f ), Random.Range( -5.0f, 5.0f ) ) * deltaTime;
      position.x = Mathf.Clamp( position.x, -6.0f, 6.0f );
      position.y = Mathf.Clamp( position.y, -4.0f, 4.0f );
      s.transform.position = position;
    }
  }
}

Monkey code:

Strict

Import mojo

Function Main:Int()
  New Game()
  Return 0
End Function

Class Sprite
  Field _img:Image
  Field _x:Float
  Field _y:Float
  
  Method New( img:Image, x:Float, y:Float )
    _img = img
    _x = x
    _y = y
  End Method
End Class

Class Game Extends App
  Field _sprite:Image
  Field _spriteList:List<Sprite>
  Field _resX:Float
  Field _resY:Float
  
  'FPS stuff
  Field _previousTime:Int
  Field _currentTime:Int
  Field _passedTime:Int
  Field _loopTimeAll:Int
  Field _fpsCounter:Int
  Field _fpsLastUpdate:Int
  Field _fps:Float
 
  Method OnCreate:Int()
    _sprite = LoadImage( "sprite.png" )
    _spriteList = New List<Sprite>
    _resX = Float( DeviceWidth() )
    _resY = Float( DeviceHeight() )
    For Local i:Int = 0 Until 2000
      Local s:Sprite = New Sprite( _sprite, Rnd( 0, _resX ), Rnd( 0, _resY ) )
      _spriteList.AddLast( s )
    Next
    _currentTime = Millisecs()
    SetUpdateRate(60)
    Return 0
  End Method

  Method OnUpdate:Int()
    Return 0
  End Method
 
  Method OnRender:Int()
    _previousTime = _currentTime
    _currentTime = Millisecs()
    _passedTime = _currentTime - _previousTime

    Cls()
    For Local s:Sprite = EachIn _spriteList
      Local dx:Float = Rnd( -0.1, 0.1 ) * _passedTime
      Local dy:Float = Rnd( -0.1, 0.1 ) * _passedTime
      s._x += dx
      s._y += dy
      s._x = Clamp( s._x, 0.0, _resX )
      s._y = Clamp( s._y, 0.0, _resY )
      DrawImage( s._img, s._x, s._y )
    Next

    DrawText( _fps + " FPS", 10, 10 )
    updateFPS()
    Return 0
  End Method
 
  Method OnSuspend:Int()
    Return 0
  End Method
 
  Method OnResume:Int()
    Return 0
  End Method

  Method updateFPS:Void()
    _loopTimeAll += _passedTime
    
    _fpsCounter += 1
    If( ( _currentTime - _fpsLastUpdate ) > 500 )
      Local renderTime:Float = Float( _loopTimeAll ) / Float( _fpsCounter )
      If( renderTime < 1.0 ) Then renderTime = 1.0
      _loopTimeAll = 0
      _fpsLastUpdate = _currentTime
      _fps = 1000.0 / renderTime
      _fpsCounter = 0
    End If
  End Method
End Class

Sample APKs:
Unity: http://www.leidel.net/dl/monkey/2dtest/unitytest.apk
Monkey: http://www.leidel.net/dl/monkey/2dtest/monkeytest.apk

Project files:
http://www.leidel.net/dl/monkey/2dtest/unityproject.zip
http://www.leidel.net/dl/monkey/2dtest/monkeyproject.zip

Test device was a Sony Xperia S. I’d be interested in more comparison values and stuff how (especially) the Unity code could be done better? Have I done something terribly stupid in my Unity code?

Looks interesting, It seems Unity is not that efficient at 2D stuff after all. Although I cannot read your monkey code equivalent, I would say that calling Random.range 4000 times per frame on mobile is why FPS is dropping so far. Maybe you could do a comparison that does not use Random.range as I find always find this to be a Unity choke point in performance.

Thanks for that, I’ll check it out! Don’t get me wrong I don’t want to make Unity bad because it’s not but maybe I just did something terribly wrong in my code? Something obvious?

Nothing that would cause a 10x frame rate difference. Maybe try removing the cast when setting gameobject S.

That loop isn’t perfect. You’d better create list of transforms and move these directly instead of getting component (transform/gameobject) 2 times inside each loop “tick”.

I would suggest something like this:

public List<Transform> trans;

void Start() {
    trans = new List<Transform>();
    for( int i = 0; i < 2000; i++ ) {
      Transform sprite = (Transform)Instantiate( _sprite, new Vector3( Random.Range( -6.0f, 6.0f ), Random.Range( -4.0f, 4.0f ) ), Quaternion.identity );
      trans.Add( sprite );
    }
  }

void Update() { 
    for( int i = 0; i < _spriteList.Count; i++ )  {
      Transform s = _trans[i];
      Vector3 position = s.position;
       float deltaTime = Time.deltaTime;
      position += new Vector3( Random.Range( -5.0f, 5.0f ), Random.Range( -5.0f, 5.0f ) ) * deltaTime;
      position.x = Mathf.Clamp( position.x, -6.0f, 6.0f );
      position.y = Mathf.Clamp( position.y, -4.0f, 4.0f );
      s.position = position;
    }
  }

It’s just a draft. It may not work because I didn’t tested that.

Try moving rigidbody (iskinematic) instead of transform position, I tried it a few years ago, moving rigidbody(iskinematic) is faster than moving transform position. I am not sure if that is still the case tho

As others have said:

  • Use List instead of ArrayList
  • If your sprite has a collider, it needs to have a rigidbody2D (with isKinematic=true) as well

Try that and see what your FPS goes up to…

I think you messed up how you are calculating the fps in Monkey. You seem to be basing it on the time between assigning _loopTimeStart and _loopTimeEnd rather than measuring the frequency of calls to OnRender. I doubt those calls to DrawImage are actually doing much and the real work happens when the OnRender method returns.

I don’t know anything about Monkey though so I could be wrong but I see you are calling SetUpdateRate(60). What is the point of that? Surely the FPS would be 60 max? Given the amount of overdraw in your example I wouldn’t be surprised to see 20fps on a mobile device. I’d be really surprised to see a mobile device running at 200fps for anything.

Edit: The resolution on a Xperia S is 720x1280 so that is 921,600 pixels. Each frame you are rendering 2000x64x64 or 8,192,000 pixels. So basically over 8 full screens worth of pixels. I find it really hard to believe that Monkey can do that at 200fps.

Android phones can’t run at 200 FPS, because they are limited to 60 at best, and this can’t be changed.

And I am sure there is something wrong with your Monkey project, because there is no mobile device on Earth that can support that much overdraw. You have 41 draw calls, more than 1000 batched dynamically, and repaint 100% of the screen more than 10 times.

Thanks for the answers, I’ll try these optimizations.

@soldmeadow and mikhail: I know that Android devices can’t go beyond 60 fps. SetUpdateRate just says how often the OnUpdate is called per second. I do nothing in the Update loop. I just measure the net render time and compute the fps from that. So actually one OnRender call takes 5ms (which leads to these 200 fps), which doesn’t mean that OnRender is more often called than 60 times per second.

I’ll rewrite that test to see how many sprites can be displayed per second. That might be more helpful?

@Xaron - I don’t believe you are measuring the net render time though. As I said before, my guess is that the rendering occurs after OnRender returns. The calls to DrawImage are probably just being used to build up some OpenGL commands that get submitted when OnRender returns, otherwise Monkey can’t be using any batching which makes it even less likely it is outperforming Unity. I don’t know anything about the architecture of Monkey though. You certainly aren’t comparing apples to apples at the moment.

Why don’t you try a more realistic test with less sprites or smaller sprites? There is no way you can visually inspect your example so you have to rely entirely on the fps calculation.

He is stress testing it, a few sprites makes it difficult to measure difference between them.

I said less sprites not a few sprites. 8 screens worth of pixels per frame is totally unrealistic for a mobile device.

You are doing it wrong. You must measure FPS, and you are calculating execution time of a function, which actually doesn’t render anything.

As are some of the more common graphics and CPU benchmarks, its not about being realistic, its about showing the efficiency of each engine.

You can’t compare engines with the test, because such fillrate is a death to all known mobile GPUs. Instead, a more realistic, but not necessary an easy weight, test should be created and correctly measured.

What’s causing problems with your frame rate may be Unity 4.5 more than bad code. They’re already working on a several fix.

Ok I got your points even though they don’t convince me yet. For the Unity FPS computation I use the HUDFPS script from here: http://wiki.unity3d.com/index.php?title=FramesPerSecond

Actually it’s almost the same stuff I use. frames per second are usually 1/rendertime. I guess you mean that I don’t measure the rendertime but just this loop and rendering happens after that, did I get you right?

If so, how about this test: I will render as much as many sprites till I have one Update call (in case of Monkey OnRender) per second. Would that be the same? In that case I would get the numbers of sprite per second.

something like:

previous_time = current_time
current_time = Milliseconds()
milliseconds_passed = current_time-previous_time
if ( milliseconds_passed > 0 ) fps = 1000/milliseconds_passed

Thanks for all those answers! I have to admit that I was indeed wrong with my fps counter. Using the code cannon posted, I get 33 fps for Monkey which is more realistic and still 65% faster than Unity. Thanks again and sorry for that! I’m still very satisfied with these results. :wink:

Another thing that seems to be not in focus is the APK size. I think it’s really something UT should do something about.