Creating SKSpriteNodes by name is very slow

In a game I'm writing, I noticed that generating the contents of each level is really slow. There was like a 1-second delay when creating even a simple scene with under a hundred sprites.


When I started measuring where this slowness is coming from, it turned out that the culprit was [SKSpriteNode spriteNodeWithImageNamed:].


It's incredibly slow. Creating something like a hundred sprites like this takes over a second on the Apple TV.


I noticed that sprites support copying. Thus I tried what happens if I create a sprite only once and then copy it a hundred times. This was extremely fast (a tiny fraction of a second.)


I ended up creating a sprite caching scheme where I create sprites only once and store them in a map based on their name, and then if a sprite with the same name is created again, it just copies the cached sprite. This improved the speed of the level generation enormously.


But one would think that SpriteKit would itself do this already. But apparently not.


Is [SKSpriteNode spriteNodeWithImageNamed:] really supposed to be that slow? I have never noticed such a thing with cocos2d.


This makes me think how slow it makes loading an .sks file. (Unless the .sks loader performs some similar optimizations.)

If you use many nodes with the same texture you could try to use reference nodes, this may(I didn't test it) also increase the loading performance.

I think this makes a pefect sense. You create everything you need while showing a beautiful picture with "LOADING.." text in it to the user. And once you get all sprites ready, you launch the game.


spriteNodeImageReady should read the file from HDD (well, flash storage) to RAM, you can't expect it to be in RAM already.

Why would I do that, when using a sprite caching scheme the sprites are created in a tiny fraction of a second?


My question was not "what do I show the user when creating a hundred sprites per second?" My question was: "Is it normal that creating a hundred sprites using [SKSpriteNode spriteNodeWithImageNamed:] takes one second, when copying a hundred sprites takes a minuscule fraction of that?"

When you copy a sprite you create a second object by copying a certain amount of bytes in RAM.


When you use spriteNodeWithImageNamed you read SKS file, parse it and build a new object based on whatever is there in your SKS file.


The difference is like between copying a few pages of "War and peace" using copy machine or writing a short story of your own 🙂


So I guess it is perfectly normal.

Pavel already answered 😉 Here's was mine :

> My question was: "Is it normal that creating a hundred sprites using [SKSpriteNode spriteNodeWithImageNamed:] takes one second, when copying a hundred sprites takes a minuscule fraction of that?"


I'd say it makes sense, yes. Your image file needs to be read and decoded (probably a png), and translated to a SKTexture before it can be used. It definitely makes sense that this takes a bit of time : even if the file is somewhere in the L3 cache, it will take some time to process, get decoded and translated to the texture format used by SpriteKit. You should not expect that SpriteKit should optimize this already by magically copying something that you could have already modified in many ways. Most optimizations in SpriteKit are about displaying at 60 FPS with the lowest CPU usage possible.


Your solution, using copies, will definitely be faster than reloading the pngs. You could also consider preloading your images as SKTextures and then create your sprites on the fly with those textures. This is probably slower than a simple copy but faster than withImageNamed. Each have it's pros and cons.

Firstly, it's not an individual image; it's a spritesheet. It's already loaded into memory.


Secondly, even if it were an individual image, spritekit loads and unloads images smartly. In other words, if an image has already been loaded, it won't load it again. (Which makes sense. If you had the same image loaded into then thousand sprites, and spritekit were to load the image every time, you would run out of memory.)

Definitely sounds like a bug to me. I would suggest to file a bug report with a small test project.(Maybe two buttons which execute the two approaches and log the time needed)

Hi WarpRulez,


From you description it does sound like there is an issue manifesting for you in your particular setup. When using spriteNodeWithImageNamed: the framework has to run through a name resolution scheme that can't assume you haven't changed an asset on disk though there is opportunity for caching that is in general done. The resolution scheme has to check the following places (and roughly in this order):


- Any loose .png files in your project that match the name

- Any static atlases that contain the name

- Any runtime atlases that contain the name (Created with SKTextureAtlas atlasWithDictionary:)

- Any asset catalogs that may contain an image or atlas with the matching name


When compared to using SKTextureAtlas textureNamed: and then passing that to the creation of your SKSpriteNode you are always going to be faster doing it that way.


Normally the name resolution is a very small overhead but something in your setup may be causing it to take much longer than anticipated. If you have time to package your project up in a radar and send it over to us we could either fix the underlying issue or at least give you feedback on what you can do in your project to speed up the load.


For now I recommend you load the atlas and then create the sprites from that if you know it is all coming off one sheet.


Cheers

I'm running into a ton of similar problems-- how did you go about implementing the sprite caching system? Are you just using `.copy` on the given sprite and storing it in an array? I'm already preloading my textures with an atlas. Thanks!

How would one go about displaying this load screen exactly? I have a "start scene" and then my "game scene" but I'm not sure how to display an animation or even a static image while I preload my textures (Right now its just a white screen and then the game starts) or better yet how to instantiate the objects that I'll later need?

Creating SKSpriteNodes by name is very slow
 
 
Q