Shape vs. Sprite
Flash 9 and AS3 provide a lot of nice structure that Flash 8 and AS2 lacked. Instead of just MovieClip and TextField, we have a whole hierarchy of DisplayObject derivatives, including MovieClip and TextField. Most AS3 programmers know that Sprite is a more efficient class than MovieClip and have grown to use them when animation is not required. However, I find that very few AS3 programmers ever use some of the other, more obscure DisplayObject derivatives. Today I’ll talk a little about one of them: Shape.
I rarely see any AS3 programmer ever use a Shape. I think this is because Shapes are more limited than Sprites in many ways: they cannot have children, they make poor buttons, they cannot be a drop target, and so forth. They are rarely noticed too since they are not a superclass of the more common DisplayObjects like Sprite, MovieClip, TextField, or Bitmap. All in all they offer less of everything to the AS3 programmer. However, they exist for the reason pointed out in the Adobe documentation: a lightweight alternative to the heavyweight Sprite. Let’s see just how lightweight they are! Here’s a little app to see the difference in construction time:
const NUM_REPS:int = 100000; var i:int; var beforeTime:int; beforeTime = getTimer(); var sprite:Sprite; for (i = 0; i < NUM_REPS; ++i) { sprite = new Sprite(); } trace("Constructing Sprites: " + (getTimer()-beforeTime)); beforeTime = getTimer(); var shape:Shape; for (i = 0; i < NUM_REPS; ++i) { shape = new Shape(); } trace("Constructing Shapes: " + (getTimer()-beforeTime));
And here are the results:
Environment | Sprite | Shape |
---|---|---|
2.2 Ghz Intel Core 2 Duo, 2GB RAM, Mac OS X 10.6 | 809 | 418 |
3.0 Ghz Intel Core 2 Duo, 4GB RAM, Windows XP | 465 | 255 |
Right there we have a 89% speedup! Now how about real time performance?
package { import flash.display.*; import flash.events.*; import flash.text.*; import flash.utils.*; [SWF(backgroundColor=0xEEEADB,frameRate="1000")] /** * A simple app to display the difference in performance between Shape * and Sprite * @author Jackson Dunstan */ public class ShapesSpritesPerformanceTest extends Sprite { /** Last time we updated FPS */ private var __lastFPSUpdateTime:uint; /** Last time we entered a frame */ private var __lastFrameTime:uint; /** Count of frames since the last time we updated the FPS */ private var __frameCount:uint; /** Framerate display */ private var __framerate:TextField; /** Text field to show the type of object we're displaying */ private var __mode:TextField; /** Time when we started timing */ private var __beginTime:int; /** Container of the example Sprites */ private var __sprites:Sprite; /** Container of the example Shapes */ private var __shapes:Sprite; /** Current container */ private var __curContainer:Sprite; /** * Application entry point */ public function ShapesSpritesPerformanceTest() { // Setup stage stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; var format:TextFormat = new TextFormat(); format.font = "_sans"; // Setup framerate display __framerate = new TextField(); __framerate.autoSize = TextFieldAutoSize.LEFT; __framerate.background = true; __framerate.backgroundColor = 0xEEEADB; __framerate.selectable = false; __framerate.text = "Gathering FPS data..."; __framerate.setTextFormat(format); __framerate.defaultTextFormat = format; addChild(__framerate); addEventListener(Event.ENTER_FRAME, onEnterFrame); __mode = new TextField(); __mode.y = __framerate.height; __mode.autoSize = TextFieldAutoSize.LEFT; __mode.setTextFormat(format); __mode.defaultTextFormat = format; __mode.text = "Sprites"; addChild(__mode); const NUM_TO_DISPLAY:int = 1000; var i:int; __sprites = new Sprite(); var sprite:Sprite; __shapes = new Sprite(); var shape:Shape; for (i = 0; i < NUM_TO_DISPLAY; ++i) { sprite = new Sprite(); sprite.graphics.beginFill(0x261c13); sprite.graphics.drawCircle(0, 0, 10); sprite.graphics.endFill(); sprite.x = Math.random()*500; sprite.y = Math.random()*(500-__mode.height)+__mode.y+__mode.height; __sprites.addChild(sprite); shape = new Shape(); shape.graphics.beginFill(0x261c13); shape.graphics.drawCircle(0, 0, 10); shape.graphics.endFill(); shape.x = sprite.x; shape.y = sprite.y; __shapes.addChild(shape); } __curContainer = __sprites; addChild(__sprites); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); } /** * Callback for when a key is pressed * @param ev KEY_DOWN event */ private function onKeyDown(ev:KeyboardEvent): void { if (__curContainer == __sprites) { removeChild(__sprites); addChild(__shapes); __mode.text = "Shapes"; __curContainer = __shapes; } else { removeChild(__shapes); addChild(__sprites); __mode.text = "Sprites"; __curContainer = __sprites; } } /** * Callback for when a frame is entered * @param ev ENTER_FRAME event */ private function onEnterFrame(ev:Event): void { __frameCount++; var now:int = getTimer(); var dTime:int = now - __lastFrameTime; var elapsed:int = now - __lastFPSUpdateTime; if (elapsed > 1000) { var framerateValue:Number = 1000 / (elapsed / __frameCount); if (__framerate.visible) { __framerate.text = "FPS: " + framerateValue.toFixed(4); } __lastFPSUpdateTime = now; __frameCount = 0; } __lastFrameTime = now; for (var i:int = 0; i < __curContainer.numChildren; ++i) { var cur:DisplayObject = __curContainer.getChildAt(i); cur.x = Math.random()*500; cur.y = Math.random()*(500-__mode.height)+__mode.y+__mode.height; } } } }
I get the following results:
Environment | Sprite | Shape |
---|---|---|
2.2 Ghz Intel Core 2 Duo, 2GB RAM, Mac OS X 10.6 | 39 FPS | 39 FPS |
3.0 Ghz Intel Core 2 Duo, 4GB RAM, Windows XP | 63 FPS | 63 FPS |
It seems that there is no rendering difference between the two. Therefore if you go with a Shape, you won’t have to worry about the performance after you have them constructed. How about memory usage?
package { import flash.system.*; import flash.display.*; import flash.events.*; import flash.text.*; import flash.utils.*; [SWF(backgroundColor=0xEEEADB)] /** * A simple app to display the difference in memory between Shape * and Sprite * @author Jackson Dunstan */ public class ShapesSpritesMemoryTest extends Sprite { /** Number of objects to allocate */ private static const NUM_REPS:int = 100000; /** Memory display */ private var __memory:TextField; /** Objects allocated */ private var __objects:Array = []; /** Current mode */ private var __mode:String = "Sprites"; /** * Application entry point */ public function ShapesSpritesMemoryTest() { // Setup stage stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; var format:TextFormat = new TextFormat(); format.font = "_sans"; // Setup memory display __memory = new TextField(); __memory.autoSize = TextFieldAutoSize.LEFT; __memory.background = true; __memory.backgroundColor = 0xEEEADB; __memory.selectable = false; __memory.text = "Gathering memory data..."; __memory.setTextFormat(format); __memory.defaultTextFormat = format; addChild(__memory); addEventListener(Event.ENTER_FRAME, onEnterFrame); allocateSprites(); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); } /** * Callback for when a key is pressed * @param ev KEY_DOWN event */ private function onKeyDown(ev:KeyboardEvent): void { __objects.length = 0; if (__mode == "Sprites") { __mode = "Shapes"; allocateShapes(); } else { __mode = "Sprites"; allocateSprites(); } } /** * Allocate sprites to the __objects list */ private function allocateSprites(): void { for (var i:int = 0; i < NUM_REPS; ++i) { __objects.push(new Sprite()); } } /** * Allocate shapes to the __objects list */ private function allocateShapes(): void { for (var i:int = 0; i < NUM_REPS; ++i) { __objects.push(new Shape()); } } /** * Callback for when a frame is entered * @param ev ENTER_FRAME event */ private function onEnterFrame(ev:Event): void { __memory.text = "Memory (KB) for " + __mode + ": " + (System.totalMemory)/1024; } } }
And the results are:
Environment | Sprite | Shape |
---|---|---|
2.2 Ghz Intel Core 2 Duo, 2GB RAM, Mac OS X 10.6 | 85172 | 36916 |
3.0 Ghz Intel Core 2 Duo, 4GB RAM, Windows XP | 49508 | 32672 |
Here we see Sprites taking 36% more memory as Shapes, but 2.3x as much on Mac OS X. This is another big win on behalf of the Shape class. So in summary, we see that Shapes beat Sprites for construction time and memory usage, but make no difference in rendering performance. So if you’re in need of better performance and can live with the limited feature set of Shapes, it may be time to consider a switch from Sprites to Shapes!
#1 by Simon Altschuler on October 26th, 2009 ·
Very interesting, I’m suprised theres no rendering difference!
btw. I think you switched the memory usage values in the table :-)
#2 by jackson on October 26th, 2009 ·
Thanks for catching this! I’ve updated the memory usage and also added stats for Windows XP. The memory usage is quite different there, but still very heavily in favor of Shapes.
#3 by Og2t on October 26th, 2009 ·
To me the main advantage (using less memory) of using Shape than Sprite would when using graphics property together with beginBitmapFill.
#4 by Jonnie on October 26th, 2009 ·
I’ve been wanting to run this comparison for quite some time now. In my personal framework, there’s an fSprite class, which is pretty much the base of everything visual. Now, I’ll definitely need to create a similar class using Shape for when I don’t need the extra jazz. It’s always exciting to find a way to cut memory, especially in half! Many thanks, Jackson.
#5 by jackson on October 26th, 2009 ·
You’re very welcome. I too am thinking of adding a BaseShape class to my own framework based on these results. By the way, check out my updated memory findings. It’s not quite double the memory on Windows XP.
#6 by gio on October 31st, 2009 ·
Without considering the differences in memory dimensions of the single properties Sprite is three level down the hierarchy so it has more than 50% more properties than Shape.
28 (number of Shape Properties)
+6 (added InteractiveObject Properties)
+4 (added DisplayObjectContainer Properties)
+6 (added Sprite Properties)
About the rendering differences.
does the comparison make sense?
As you noted at the beginning of your post “Shapes are more limited than Sprites in many ways”
A shape rendering performance can be evaluated and measured only in relation to the type of graphics used inside it…
Shapes don’t make poor buttons, they don’t make buttons at all…
Shape exist almost exclusively to use the ‘graphics’ inside them…
A Sprite rendering performance depend not only on the graphics but on all the added stuff…
A Sprite being a DisplayObjectContainer has children to take care of…
A sprite being an InteractiveObject may have to take care of ‘interactivity’ issues.
A Sprite rendering performance can be evaluated and measured only in relation to the type of children and interactive events used inside it.
imho it’s like comparing weight and performance of a pair of legs and an entire body:
– you don’t need to create 100000 of legs and the same number of bodies to confirm that it takes more time to create a body than a pair of legs…
– they both run, and (without the upper body doing anything else) they run the same.
– legs are clearly lighter, it’s pretty obvious that they weight less than a body that normally contains them. (read ‘legs’ as ‘graphics’ property here)
#7 by jackson on October 31st, 2009 ·
Yes, Sprites clearly have more functionality. Hence my conclusion:
So if you’re in need of better performance and can live with the limited feature set of Shapes…
As for your disagreement about Shapes not being buttons at all, I think differently. While they would be very poor buttons directly given that they are not InteractiveObjects and thus have no mouse events, you could use a stage event listener, derive which shape was clicked, and respond as you would. This makes them poor buttons in my view.
Thank you for the lengthy comment! It’s good to see others are passionate about such things. :)
#8 by RR on September 20th, 2011 ·
Thank you for posting this analysis, I was looking all over for such comparisons and tests and also wanted to understand the differences between shape and sprite – your’s was the most thorough and lucid. Thanks!
#9 by Arby on March 7th, 2012 ·
I know I’m late to the party here, but can I point out that you should also be testing a sprite bitmap, VS a shape with a bitmap fill !
Please email me if you try this.
#10 by jackson on March 7th, 2012 ·
You mean a comparison between
Bitmap
andShape
+beginBitmapFill
?#11 by Gonçalo on March 23rd, 2012 ·
Thank you! I enjoyed this article, just what I was looking for.
#12 by Sourceskyboxer on February 9th, 2015 ·
Very intressing! Why is shape better than sprite?
I see memory of shape and sprite.
Is shape’s graphic drawer faster than sprite’s graphic drawer. Is it correctly?
What does it happen when i create 2D Grid like grid sees 3D drawing mode for cylinder, pyramide or cube creatings, right? Sorry for bad english!
Same Flash Player loads too slow while width and height of sprite 32768. Can shape load in Flash Player faster than sprite? Because i still develop own 3D Game Creator with pure AS3 Project.
Thanks!
#13 by jackson on September 30th, 2015 ·
The test results showed that rendering performance was the same but memory usage was much lower with
Shape
than it was withSprite
.I’m not sure I understand your question about cylinder, pyramid, and cube. All
Shape
andSprite
are 2D objects. You could put whatever you want in them, render it to aBitmapData
then upload that as a texture for aStage3D
-based 3D object though.Hope that helps.
#14 by Dave on September 30th, 2015 ·
OK. So it’s been ~6 years since this has been written/published but that’s google for you! I’m currently trying to replace a Sprite gfx context in my test project with a Shape gfx context but it does not work. It simply ceases to render. I’m pretty new to the x86 flash runtime which doesn’t seem to output any errors which is quite unfortunate. Any ideas? Anyone? Can anyone hear me?! ;-)
#15 by jackson on September 30th, 2015 ·
I can hear you. :)
Can you post a small snippet of code that shows
Shape
not rendering?#16 by Dave on September 30th, 2015 ·
Wow! Now it doesn’t work for Sprite. Native code would’ve given me an error by now :-(