performance - ¿Es Swift realmente lento en el manejo de los números?
primes (2)
Mientras jugaba con un rápido tutorial, comencé a escribir un método isPrime
personalizado para verificar si un determinado Int
es primo o no.
Después de escribirlo, me di cuenta de que funcionaba correctamente, pero me pareció un poco lento realizar isPrime
en algunos números bastante grandes (aún mucho más bajo que Int.max
).
Así que escribí el mismo código en objc y el código se ejecutó mucho más rápido (un factor de 66x).
Aquí está el código rápido:
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;
}
}
Y el código objc:
@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
Y en 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");
Esto produce:
[1040101022027, 1040101022039, 1040101022057, 1040101022099, 1040101022153] took: 3953.82004976273ms
[1040101022027, 1040101022039, 1040101022057, 1040101022099, 1040101022153] took: 66.4250254631042ms
Sé que podría haber usado una extension
en Int
aquí para verificar si un número es primo, pero quería que ambos códigos fueran muy similares.
¿Alguien puede decirme por qué este código rápido es mucho más lento? El factor 66x es bastante aterrador y solo empeora a medida que incremente el rango.
Créditos a @sjeohp por su comentario que es básicamente la respuesta a la pregunta.
Intenté optimizar el código de la forma más agresiva en Release
para las optimizaciones de LLVM y Swift:
Compiló el proyecto en Release
y obtuvo:
[1040101022027, 1040101022039, 1040101022057, 1040101022099, 1040101022153] took: 63.211977481842ms
[1040101022027, 1040101022039, 1040101022057, 1040101022099, 1040101022153] took: 60.0320100784302ms
Nuevamente, gracias @sjeohp por atrapar esto!
Estos son los niveles de optimización para la generación de código del compilador Swift (puede encontrarlos en Configuraciones de compilación):
[-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.
Usando su código obtuve estos tiempos en diferentes niveles de optimización:
[-En uno]
Swift: 6110.98903417587ms
Objc: 134.006023406982ms
[-O]
Swift: 89.8249745368958ms
Objc: 85.5680108070374ms
[-Ofast]
Swift: 77.1470069885254ms
Objc: 76.3399600982666ms
Tenga en cuenta que -Ofrece viene con riesgos. por ejemplo, ignorará silenciosamente los desbordamientos de enteros y matrices, produciendo resultados sin sentido, por lo que si elige usarlos deberá garantizarse que los desbordamientos no son posibles en su programa.