I have encountered a performance issue in Swift. I compared the same method between C# and Swift: Swift is much slower than C#.
Here is the code I'm working with. Could some one help me to speed it up in Swift?
CSharp version (for reference only - it takes 73 ticks):
var nodes = new Node[328]; for (int i = 0; i < nodes.Length; i++) { nodes[i] = new Node(); } var oneNode = new Node(); var count = 1; Stopwatch sw = Stopwatch.StartNew(); foreach(var node in nodes) { if (oneNode.Equals(node)) { count++; } } sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds);
Swift version (it takes 1.78 ms):
import Foundation
public class Node : Equatable {
var cost : Int = 0
var x : Int = 100
var y : Int = 100
}
public func == (a : Node, b : Node) -> Bool {
return (a.x == b.x) && (a.y == b.y)
}
var t = NSDate()
let a = [Node](count: 328, repeatedValue: Node())
let b = Node()
var count = 1
for i in a {
if i == b {
count++
}
}
var elapsed = t.timeIntervalSinceNow * -1000
println(elapsed)
1 Answer 1
What's absolutely crucial when doing time comparisons between languages is that we make sure we're actually testing the same thing. In this case, we're not.
But before I get into that into too much detail, I'm going to suggest a slightly better method of testing this code.
First of all, I've moved the actual running code (everything but the class declaration and the ==
function definition) into an IBAction
method so that I can press a button and test it multiple times all in the same build.
Second, I'm going to change the definition of the class for this test to assign random values to its properties. So the class definition will look like this for my test:
public class Node : Equatable {
var cost : Int = 0
var x : Int = Int(arc4random_uniform(100))
var y : Int = Int(arc4random_uniform(100))
}
This forces the timing to be a little more realistic since branch prediction might easily figure out that the result of ==
is always true and just fly through our loop.
And third of all, I'm going to test with a much larger array. I want 10,000 elements. This may be unrealistically large for actual usage, but when testing speed and trying to optimize time, it's good to work with very large sample sizes.
So, without changing anything else, this is what I'm going to test, and I will include results from the time profiler:
public class Node : Equatable {
var cost : Int = 0
var x : Int = Int(arc4random_uniform(100))
var y : Int = Int(arc4random_uniform(100))
}
public func == (a : Node, b : Node) -> Bool {
return (a.x == b.x) && (a.y == b.y)
}
class ViewController: UIViewController {
@IBAction func test(sender: UIButton) {
var t = NSDate()
let a = [Node](count: 10_000, repeatedValue: Node())
let b = Node()
var count = 1
for i in a {
if i == b {
count++
}
}
var elapsed = t.timeIntervalSinceNow * -1000
println(elapsed)
}
}
Completely as written, and using whatever optimization defaults you get when you create a new project in Swift, this takes my desktop 47-50 milliseconds. On an iPad Mini, this takes about 265-270 milliseconds.
enter image description here
This is the time profile of the code I posted. I pressed the button to run the test 8 times. Notice a few lines:
- 84.9% is where our Swift version of our
IBAction
method is. The first 15.1 percent is the time it took to launch our app, initialize, run the view controller, receive the touch events, etc. The other 84.9% is code that runs from inside ourIBAction
method. - 1.1% is how much of the total time is spent comparing our
Node
objects with our custom==
function. The array is 10,000 elements large, and we're iterating through it completely 8 times. We're calling the==
function 80,000 times, and it only takes 1.1% of the total time. Our==
function is perfectly fine.
The code that's taking the most time is here:
enter image description here
Unfortunately, we can't see exactly what these are. They're all from within the Swift library though. And since Swift is still growing and rapidly changing, it'll probably be a while before the Time Profiler actually gives us the actual method names for these.
I tried moving code around, trying to get different results, but they're all pretty much the same. I suspected that the array initialization was taking up about half the time and tried moving that out of the timer, but it doesn't seem to make a difference.
What I can tell you though, is this:
- Your
==
method, which is what is all that you've written which is being tested here, is perfectly fast. It takes almost no time at all. - It's relatively well known that at this point in time, Swift is absolutely horrendous with accessing arrays in loops like this. But Swift is very much so a growing and evolving language. Hopefully the Apple engineers are working on improving it.
-
\$\begingroup\$ Thanks, as I tested, the speed is badly affect by the build condition. When I switch from debug to release to run, it becomes the same speed as the C# one. While it is slower running on the iPhone, but it is acceptable. Lack of experience on iOS development. \$\endgroup\$Howard– Howard2014年12月11日 10:35:02 +00:00Commented Dec 11, 2014 at 10:35
-
\$\begingroup\$ Slower processors run code slower. That is to be expected. Did you test the C# code on the same device to be sure you had an apples-to-apples? \$\endgroup\$nhgrif– nhgrif2014年12月11日 11:38:22 +00:00Commented Dec 11, 2014 at 11:38
-
\$\begingroup\$ Unfortunately not; I will try it with Xamarin.iOS later to see how the performance is. \$\endgroup\$Howard– Howard2014年12月12日 01:07:10 +00:00Commented Dec 12, 2014 at 1:07
struct Node
instead ofclass Node
, this should be faster. \$\endgroup\$