As I was playing around with a swift tutorial, I started to write a custom isPrime
method to check if a given Int
is prime or not.
After writing it I realized it was working properly but found it a bit slow to perform isPrime
on some quite large numbers (still much lower then Int.max
).
So I wrote the same piece of code in objc and the code was executed much faster (a factor of 66x).
Here is the swift code:
class Swift { class func isPrime(n:Int) -> Bool { let sqr : Int = Int(sqrt(Double(n))) + 1 for i in 2...sqr { if n % i == 0 { return false } } return true; } class func primesInRange(start:Int, end:Int) -> Int[] { var primes:Int[] = Int[]() for n in start...end { if self.isPrime(n) { primes.append(n) } } return primes; } }
And the objc code:
@implementation Utils + (BOOL)isPrime:(NSUInteger)n { NSInteger sqr = (NSUInteger)(sqrt(n))+1; for (NSUInteger i = 2; i < sqr; ++i) { if (n % i == 0) { return false; } } return YES; } + (NSArray*)primesInRange:(NSUInteger)start end:(NSUInteger)end { NSMutableArray* primes = [NSMutableArray array]; for (NSUInteger i = start; i <= end; ++i) { if ([self isPrime:i]) [primes addObject:@(i)]; } return primes.copy; } @end
And in main.swift
:
let startDateSwift = NSDate.date() let swiftPrimes = Swift.primesInRange(1_040_101_022_000, end: 1_040_101_022_200) let elapsedSwift = NSDate.date().timeIntervalSinceDate(startDateSwift)*1000 let startDateObjc = NSDate.date() let objcPrimes = Utils.primesInRange(1_040_101_022_000, end: 1_040_101_022_200) let elapsedObjc = NSDate.date().timeIntervalSinceDate(startDateObjc)*1000 println("\(swiftPrimes) took: \(elapsedSwift)ms"); println("\(objcPrimes) took: \(elapsedObjc)ms");
This produces:
[1040101022027, 1040101022039, 1040101022057, 1040101022099, 1040101022153] took: 3953.82004976273ms [1040101022027, 1040101022039, 1040101022057, 1040101022099, 1040101022153] took: 66.4250254631042ms
I know that I could have used an extension
on Int
here to check if a number is prime, but I wanted both code to be very similar.
Can anyone tell me why this swift code is so much slower? The 66x factor is pretty scary and only gets worse as I increment the range.
The Swift loop runs slower because Swift performs arithmetic overflow checks that C does not, and in this case swiftc was unable to optimize them all away. If you use &+ instead of +, or compile with -Ounchecked, then Swift won't perform the overflow checks.
Optimization Level in Build Settings: payment: build size. Use “final” and “private” for methods and classes — payment: constraints with subclass and dispatching. Avoid “print” in release builds — payment: no logs to console. “Inline” Your Code — Payment: duplicate code; code is not “clean”
Here are optimization levels for the Swift compiler's code generation (you can find them in Build Settings):
[-Onone] no optimizations, the default for debug. [-O] perform optimizations, the default for release. [-Ofast] perform optimizations and disable runtime overflow checks and runtime type checks.
Using your code I got these times at different levels of optimization:
[-Onone]
Swift: 6110.98903417587ms Objc: 134.006023406982ms
[-O]
Swift: 89.8249745368958ms Objc: 85.5680108070374ms
[-Ofast]
Swift: 77.1470069885254ms Objc: 76.3399600982666ms
Keep in mind that -Ofast is comes with risks. e.g. It will silently ignore integer and array overflows, producing nonsense results, so if you choose to use it you'll have to guarantee yourself that overflows aren't possible in your program.
Credits to @sjeohp for his comment which is basically the answer to the question.
I tried optimizing the code to the most aggressive way in Release
for both LLVM and Swift optimizations:
Compiled the project in Release
and got:
[1040101022027, 1040101022039, 1040101022057, 1040101022099, 1040101022153] took: 63.211977481842ms [1040101022027, 1040101022039, 1040101022057, 1040101022099, 1040101022153] took: 60.0320100784302ms
Again, thanks @sjeohp for catching this !
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With