Swift nested for loops cause performance hit

My issue is that the following code brings my frame rate (in a debug build) from 60 fps to 2 fps. (iPad Mini) Release is faster but still very slow (15fps)


let objects = world!.children // world is an SKSpriteNode, the children added are a subclass of SKSpriteNode - this line alone causes no problems

for object1 in objects

{

for object2 in objects

{

// there is no code executed here - it's just empty but still very slow

}

}


With ~200 nodes in the array, I realize it's about 40000 executions but I don't think it should be so slow with nothing going on in the loop.

I would like to know if there is a way to setup a double nested loop like this which still has good performance for a debug build.


I'm completely new to Swift so perhaps I'm inadvertently allocating things by looping like this.


Please let me know if there is a more efficient way to loop through a list of SKSpriteNodes.


thanks.

Answered by ahltorp in 10780022

If you change the line


let objects = world!.children


to


let objects = Array(world!.children)


you should get some speedup. With 1600 nodes on a couple of years old MacBook Pro I got 0.34 seconds with your code and 0.13 seconds with my change. With a simple collision detection thrown in, the time increased to 0.18 seconds. I don't know how this translates to your iPad, but it should at least be a little bit faster.

It's "only" 40,000 iterations of the inner loop, but you're trying to do it 60 times a second. That comes out to 2.4 million iterations per second, constantly. It's actually a bit worse than that, because you can't reasonably consume all 1/60th of a second, because Sprite Kit has to do a fair amount of work in the same 1/60th of a second.


The two obvious ways to this approach this are:


— Use logic that isn't O(n^2) — isn't of the order of n-squared, where n is the number of objects — to reduce the number of pairs you need to consider. For example, your existing logic is considering each pair twice, which seems unneccessary. Or, if you're loooking for collisions, let Sprite Kit find them for you.


— Move your compare-every-pair-of-objects logic to a background thread, so that it can run in parallel with updating. The comparisons might run slower than the 60 fps frame rate, but might still be fast enough.


Both of these are likely to be difficult. The first requires developing a new algorithm. The second requires some deeper techniques to ensure thread safety. Then again, depending on what the inner loop code is going to be, there may be other approaches.

Thanks for responding. To clarify I am asking how Swift works, not generally how to optimize. I'm interested in the fastest way to have Swift loop nodes regardless of what the actual code is for. I'm not convinced that specific code should cause so much slow down. However I could be wrong as I am new to Swift. I will post again once I've had a chance to evaluate this issue. I do appreciate the response.

SKSpriteNode are reference types, thus a retain and release is necessary for item coming out of the array.


Switching to value types being stored will be faster, but the fundamental problem is your O(n^2) algorithm. That's always going to be slow.

Accepted Answer

If you change the line


let objects = world!.children


to


let objects = Array(world!.children)


you should get some speedup. With 1600 nodes on a couple of years old MacBook Pro I got 0.34 seconds with your code and 0.13 seconds with my change. With a simple collision detection thrown in, the time increased to 0.18 seconds. I don't know how this translates to your iPad, but it should at least be a little bit faster.

Sorry, I didn't mean to offer unwanted optimization advice, I was really just trying to emphasize that you'd need a radically different approach to make the problem go away. I don't believe you'll ever find a code optimization that solves the problem without reducing the number of iterations.


I will offer one other piece of possibly-unwanted advice. There is no answer to the question you originally asked, because you're running a micro-benchmark, and you're trying optimize prematurely.


— The problem with micro-benchmarking is that you're measuring nothing but the performance of the benchmark, and this rarely leads to anything that helps your actual code. The lure of "but if I write faster code everywhere, everything will run faster, and so my app will run faster" is almost irresistible, but it's actually wrong for all practical purposes.


— Since you don't have code that represents what you're actually doing (at least, you haven't shared that with us), you don't even know if you're looking at the thing that's actually going to cause you a problem. It may be that when the code is fleshed out, you find you don't need to use the looping approach at all. In that case, your premature optimization work would be merely wasted time.

hi,


thanks everyone who responded - I definitely don't mind any advice and can use all the help I can get - just was trying to state my problem succintly.


Let me be less succint and give more information about what I'm doing and I hope this will help you help me.


My background is in C++ and I wrote my first app essentially in that language while learning a bit of Objective C where I had to. I've got my first app up on the app store but sort of worked around the issue of actually learning Objective-C. I haven't had much free time so this worked out.


Now I'm reading about Swift and it seems very interesting - I'm planning to write a few apps next year and invest more serious time in this. I have some ideas that would be great for the SpriteKit I think.


So the question is, should I continue to learn Swift?


If I have an array of 200 pointers to a class type in C++ and I run the double nested loop in debug mode, it's going to have a bit of impact but nothing like it does on Swift. So clearly the double loop is not a problem in principle, it's just how the language implements it and how much overhead there is due to the higher level things the language does.


I realize Swift is more 'safe' and there will be a price (owensd mentioned the retain and release).

It just seems like too much to me - I'm having a hard time understanding how Swift could be used for an even moderately complex piece of code.


If I wanted to do collisions, I realize I can do that with the built in SpriteKit stuff (which perhaps was written in C++ anyways). However, what if I want to implement my own custom interaction? Shouldn't there be a way to have Swift handle 200 interacting objects with some trivial interaction to reject most of them?


Maybe I won't get 60 fps in debug mode but 1-2 fps is a non starter for me.


So this is why I am asking about this simple loop structure with no code in it. I don't really know exactly what I want to do yet, but I don't want to get started with Swift only to decide later it's too slow or I have to write half the code in C++ anyways.


ahitorp's suggestion about converting to a pointer array looks interesting and maybe closer to what I'm trying to find out. I'll probably have to work on this some more in a week or so and will respond, so thanks for everyone's help! I've been creating and deletig lots of nodes in my test case and seeing some memory loss so seems I have other underlying problems to work out anyways.


I guess to sum up, is Swift swift enough? It seems Apple intends Swift to be a serious complete environment I could use for game development. Or was it always intended that more intensive code would have to be done in an alternate language like C++? Or is it only good for Flappy Bird clones? I like the format of Swift but might have to go back to my dirty unsafe c code. I really would like to use SpriteKit though - maybe pure Objective C solves these problems? I haven't tried it yet.

If you are trying to run the equvalent of 10 million operations per second, every second, then you're at risk of failing to meet your frame rate requirement, no matter what language you use. In that case, the choice of language (at least for that part of your code) is critical, and chances are you wouldn't use Sprite Kit at all. However, the more rational choice is to avoid running 10 million operations per second. (If nothing else, that sort of thing will kill battery life.)


The numerical problem with your original analysis (I claim) is that you were assuming your app was otherwise idle, but you were sharing time with Sprite Kit. In fact, if you wrote the double loop as a simple test app in C++, it would be otherwise idle, because by definition you wouldn't be sharing time with Sprite Kit itself. If you want to microbenchmark and empty Swift loop vs. an empty C++ loop, you need to actually measure the elapsed time for the loop alone, not the frame rate. You'd also need to make sure the C++ compiler didn't optimize your empty loop body away completely, even in non-optimizing mode.


If you're going to write a game, Sprite Kit is a good choice unless your own code is highly CPU-intensive and time critical. If you're going to write a Sprite Kit game, either Objective-C or Swift is a a good choice, though Swift is likely the better performer at least in the future if not now.

hi,


Thanks for this information. I will stick with Swift for now and it will take me some time to evaluate this.


I'd also like to try ahltorp's suggestion and then I promise to return here to properly mark these responses - unfortunately not getting much time to code this week.


I suppose my question is not very well posed and I'd like to setup some simple test cases - for example running this loop just on an array of floats in Swift and C++ with a simple logic test in the loop while in debug more will probably be illuminating for me.

Sorry it took so long for me to get back to this - I marked ahltorp's answer as correct as it's the only thing that I found so far that could improve the speed and I got similar results to that reported by ahltorp. The bottom line is my double nested loop drops from 250 ms to 65 ms using ahltorp's suggestion.


Details below:


To recap, my question is essentially: What is the fastest way to run an empty double nested loop in Swift through the children nodes of an SKSpriteNode in debug mode?


My system is currently XCode Beta 7.0 (so Swift 2), a Mac Mini 2012, and an iPad Mini


I initialized the loop like this:


let dummyNode = SKSpriteNode()

for var i = 0; i < 200; ++i

{

dummyNode.addChild( SKSpriteNode() )

}


I then ran 100 cycles of the following code and manually clocked it using a break point before and after:

for object1 in dummyNode.children

{

for object2 in dummyNode.children

{

}

}


Now using ahltorp's suggestion I ran this:

let objects = Array(dummyNode.children)

for object1 in objects

{

for object2 in objects

{

}

}


In the first case each cycle is ~250 ms

Using ahltorp's setup each cycle is ~65 ms


So this is a big improvement. As I continue to learn Swift I will post here if I learn any further insight.


Thanks ahltorp and everyone else for your responses.

Swift nested for loops cause performance hit
 
 
Q