35

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.

Tom Zych
  • 13,329
  • 9
  • 36
  • 53
apouche
  • 9,703
  • 6
  • 40
  • 45
  • Did you take a look at the generated assembler code? I think, that would be quite instructive. – cmaster - reinstate monica Jun 11 '14 at 12:16
  • First, reverse the order of the tests and see if the results are the same. I don't see why it would matter in this case, but in other contexts you could definitely get caching effects, etc. – Tom Zych Jun 11 '14 at 12:18
  • 1
    @cmaster xcode 6 (6A215l) doens't seem to support `Assembly View` for swif yet. @"Tom Zych" yes i tried that too but the same result. objc just out runs swift in any order. – apouche Jun 11 '14 at 12:23
  • 17
    unoptimized Swift is slow. see here: http://stackoverflow.com/questions/24101718/swift-performance-sorting-arrays – Joseph Mark Jun 11 '14 at 12:23
  • 2
    I think `for i in 2...sqr` should be `for i in 2..sqr`. You include i=sqr in Swift but not Obj-C. ... vs .. such a mistake in swift. – sanz Jun 11 '14 at 17:03

2 Answers2

27

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.

Joseph Mark
  • 9,298
  • 4
  • 29
  • 31
  • 11
    So for the default production level optimization, swift when dealing with numbers is *not* the 2x faster that apple claimed at wwdc!? – Ryan Jun 11 '14 at 15:13
  • 10
    @ryan I'm sure they chose a test case very carefully. – Kevin Jun 11 '14 at 17:43
  • 18
    @ryan it runs faster inside the reality distortion field. – hobbs Jun 11 '14 at 20:56
  • 7
    Don't forget that Swift isn't at v1 yet, it will be a good test to run this code again when Swift is released. – Evonet Jun 12 '14 at 09:59
5

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:

enter image description here

enter image description here

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 !

apouche
  • 9,703
  • 6
  • 40
  • 45