Speed comparison: 2d Unity vs Monkey

Discussion in 'Public Game Developers Forum' started by Xaron, May 30, 2014.

  1. Xaron

    Xaron Well-Known Member

    Oct 16, 2013
    150
    0
    0
    #1 Xaron, May 30, 2014
    Last edited: May 31, 2014
    Alright guys, I just did a quick 2d test with Monkey and Unity 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 (previous value of 200 was wrong)

    Unity C# code:
    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:
    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

    Actually I'm pretty shocked how much faster Monkey is!

    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?
     
  2. POLYGAMe

    POLYGAMe Well-Known Member

    Jul 3, 2010
    566
    0
    0
    Game Developer (iOS, Mega Drive, C64)
    Auckland, New Zealand
    Now do a test with 200k polygons and see which is faster ;)
     
  3. Xaron

    Xaron Well-Known Member

    Oct 16, 2013
    150
    0
    0
    That was not the question but if you like you can create such a project in Unity and send it to me please? I'd be interested! :)
     
  4. Destined

    Destined Well-Known Member

    Aug 11, 2013
    1,063
    0
    0
    #4 Destined, May 30, 2014
    Last edited: May 30, 2014
    I have run projects in unity with over 200K poly's fine.

    I find the results of your test interesting, however that test isn't really using any of the features of either engine. If you were just going to do that you might as well do it in native code.

    I would expect in this monkey to be faster, but I am surprised how much faster. The benefits of using unity come from using the API.

    Free html5 is going to be a big benefit over unity because it is unlikely the unity html5 implementation will be as flexible as monkey (because it will require webgl)
     
  5. OnlyJoe

    OnlyJoe Well-Known Member

    Sep 29, 2013
    114
    0
    0
    Auckland
    The difference is going to be in how each engine treats objects rather than polygons. And how they are dealing with them to push them to OpenGL.

    Basically the most expensive thing that game engines do is matrix maths on object positions. My guess is that Unity is doing some kind of matrix stack for each object based on its transform, the camera position and parent objects. Then pushing each sprite to the GPU one at a time.

    So my guess is that unity will build a matrix for each object by building a transform, scale and rotation matrix and multiplying them together. This will then get multiplied by the camera matrix, to generate the view matrix for the object. This is then multiplied by the projection matrix, and pushed to the GPU. As it is a 3d engine it is using 4x4 matrix, rather than the 3x3 that a 2d engine can use. So it needs to multiply 5 matrix together before it has the final matrix for the GPU, or 80 multiply operations per sprite per frame, or 16000 for all the sprites. This can be optimised if the matrix object is designed to skip identity matrix multiplications, but most I have seen are not. This matrix maths start to get very expensive as the number of objects gets larger. Plus using a view matrix for each object means that you have to render each object in a separate call to the GPU. Which is fine for a 3d engine, where you are pushing a complex vertex and index buffer, with maybe a few textures. In 2d however you can greatly optimize this.

    So with Monkey it is probably not using a camera, and is probably not using a matrix for each object. Instead it probably just creates 4 2d points whenever you call the draw command, using the x,y and rotation of the object. These points just get added to the end of a vertex, index and texture buffer that is being built each frame. Every time a texture changes a new vertex buffer gets started. But seeing as you are using only one texture, they probably all get added to the same buffer. When the scene is drawn this single vertex buffer gets pushed to the GPU and it means everything is drawn in a single call. Much like how a particle emitter renders all its particles. This will give it a massive speed boost over how a 3d engine works, but puts more limitations on what can be done with the sprites.

    One way to test this better would be to use a range of textures for the sprites, and make sure no two sprites with the same texture are drawn in sequence. This will prevent the engine from optimizing its draw calls so much. But you will probably find that it is still faster because it doesn't need to do so much matrix maths.
     
  6. Mike77

    Mike77 Member

    Jun 8, 2013
    15
    0
    0
    Ping from the Unity forum. :)

    First, you can't have 200 FPS on Android, max 60.

    Second, the author is comparing FPS in Unity to OnRender execution per second in Monkey. This is wrong, these metrics are different and can't be compared.

    Third, no draw call optimization can help with that much fillrate, the test repaints 100% of the screen more than 10 times, mobile devices can't handle that much with a good FPS rate.
     
  7. Xaron

    Xaron Well-Known Member

    Oct 16, 2013
    150
    0
    0
    Mike, 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

    And of course Android is fixed to 60 fps but that doesn't change the fact than OnRender in Monkey only takes 5ms per loop.

    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.
     
  8. Destined

    Destined Well-Known Member

    Aug 11, 2013
    1,063
    0
    0
    some interesting tests would be comparing the game engine side. Like try may 100 2D rigidbody sprites with physics and see how they compare.
     
  9. OnlyJoe

    OnlyJoe Well-Known Member

    Sep 29, 2013
    114
    0
    0
    Auckland
    #9 OnlyJoe, May 31, 2014
    Last edited: May 31, 2014
    @Xaron, you can't just measure the time that the OnRender function takes. This may only be buffering commands on the OpenGL pipeline, and it won't be until the screen needs to render that these get executed. You need to measure the frame rate for each cycle, or do a glFlush() call at the end of the OnRender function to make sure you are getting the real draw time. But even then you can't just measure the OnRender part, as the engine might be doing all kinds of other processing as well in different functions. But you are still going to find that Monkey is probably faster, because it is designed for 2d rather than 3d.

    As you mentioned before, a different way to compare this would be to see how many objects need to be created to bring the frame rate down to a set level. Say 30 frames per second. When measuring the time difference at the same point in your code, not just the time it takes to run the one function.
     
  10. Xaron

    Xaron Well-Known Member

    Oct 16, 2013
    150
    0
    0
    #10 Xaron, May 31, 2014
    Last edited: May 31, 2014
    Will do that next, OnlyJoe! :)

    edit: I have to admit that I was indeed wrong with my fps counter. I've adapted the fps code to measure the time between two render calls now. Using this code, 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. ;)
     
  11. Ovogame

    Ovogame Well-Known Member

    Sep 25, 2010
    570
    0
    0
    Game Developer
    Morestel, France
    Yeah, me too I'm suspicious about your numbers. Something is wrong.

    BTW, matrix computation isn't that expensive on modern devices. So I doubt that 2k sprite matrix computation are responsible for the low unity FPS.

    JC
     
  12. Xaron

    Xaron Well-Known Member

    Oct 16, 2013
    150
    0
    0
    I've corrected them. You were right. Still 65% faster though. ;)
     
  13. Pixelosis

    Pixelosis Well-Known Member

    Jan 28, 2013
    157
    0
    0
    You might want to compare memory use and data collector/recyling.
    Considering that RAM is very important on smartphones, we might want to see what it looks like when pushed hard.

    Also another test with some colliders and somewhat physics elements.

    I'm less concerned about the average FPS than the sudden spike in CPU usage btw.
     

Share This Page