I read the famous Why is it faster to process a sorted array than an unsorted array? and I decided to play around and experiment with other languages such as Swift. I was surprised by the run time differences between 2 very similar snippets of code.
In Swift one can access elements in an array either in a direct way or with a subscript while in a for-in loop. For instance this code:
for i in 0..<size {
sum += data[i]
}
Could be written:
for element in data {
sum += element
}
With size
the data
length and data
an array of summable elements.
So, I just implemented in Swift (code bellow) the same algorithm as in the question I mentioned in the first paragraph and what surprised me is that the first method is roughly 5 times faster than the second method.
I don't really know the backstage subscript implementation but I thought that accessing directly the elements in a Swift for-in loop was just syntactic sugar.
Question
My question is what is the difference between the two for-in
syntaxes and why it is faster to use subscript?
here is the detail of timers. I'm using Xcode 9.4.1 with Swift 4.1 on an early 2015 MacBook Air with a Commande Line Project.
// Using Direct Element Access
Elapsed Time: 8.506288427
Sum: 1051901000
vs
// Using Subscript
Elapsed Time: 1.483967902
Sum: 1070388000
Bonus question: why the execution is 100 times slower in Swift than in C++ (both executed on the same Mac in a n Xcode project)? For instance 100,000 repetitions in C++ take nearly the same time as 1,000 repetitions in Swift. My first guess is that Swift is a higher level language than C++ and that Swift operates more safety checks for instance.
Here is the Swift code I used, I only modified the second nested loop:
import Foundation
import GameplayKit
let size = 32_768
var data = [Int]()
var sum = 0
var rand = GKRandomDistribution(lowestValue: 0, highestValue: 255)
for _ in 0..<size {
data.append(rand.nextInt())
}
// data.sort()
let start = DispatchTime.now()
for _ in 0..<1_000 {
// Only the following for-in loop changes
for i in 0..<size {
if data[i] <= 128 {
sum += data[i]
}
}
}
let stop = DispatchTime.now()
let nanoTime = stop.uptimeNanoseconds - start.uptimeNanoseconds
let elapsed = Double(nanoTime) / 1_000_000_000
print("Elapsed Time: \(elapsed)")
print("Sum: \(sum)")
-O
, I'm seeing about a 10% cost at most, not 10x. Also you'd need to compare to-Ounchecked
if you're comparing to C++. – Buckinghamshire-0unchecked
, every basic arithmetic operation does a branch (if checks for overflow, and crashes rather than allowing overflown results to be used) – Damiondamita-O
. So to sum up when unoptimized the second loop method is far slower than using subscript. – BaalbeerSequence.makeIterator(), IteratorProtocol.next()
), that would slow things down if they're not optimized out (which they are, in-O
) – Damiondamita