Stavo implementando un algoritmo in Swift Beta e ho notato che le prestazioni erano molto scarse. Dopo aver scavato più a fondo mi sono reso conto che uno dei colli di bottiglia era qualcosa di semplice come gli array di ordinamento. La parte pertinente è qui:
let n = 1000000
var x = [Int](repeating: 0, count: n)
for i in 0..<n {
x[i] = random()
}
// start clock here
let y = sort(x)
// stop clock here
In C ++, un'operazione simile richiede 0.06s sul mio computer.
In Python sono necessari 0,6 s (nessun trucco, solo y = ordinato (x) per un elenco di numeri interi).
In Swift ci vogliono 6 secondi se lo compilo con il seguente comando:
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
E ci vogliono fino agli 88 se lo compilo con il seguente comando:
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
I tempi in Xcode con build "Release" vs. "Debug" sono simili.
Cosa c'è che non va qui? Potrei capire alcune perdite di prestazioni rispetto a C ++, ma non un rallentamento di 10 volte rispetto a Python puro.
Modifica: il meteo ha notato che il passaggio -O3
a -Ofast
questo codice viene eseguito quasi alla stessa velocità della versione C ++! Tuttavia, -Ofast
cambia molto la semantica della lingua: nei miei test ha disabilitato i controlli per overflow di interi e overflow di indicizzazione di array . Ad esempio, con -Ofast
il seguente codice Swift viene eseguito silenziosamente senza arresti anomali (e stampa alcuni rifiuti):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(x[n])
Quindi -Ofast
non è quello che vogliamo; il punto centrale di Swift è che abbiamo le reti di sicurezza in atto. Naturalmente, le reti di sicurezza hanno un certo impatto sulle prestazioni, ma non dovrebbero rallentare i programmi 100 volte. Ricorda che Java controlla già i limiti dell'array e, in casi tipici, il rallentamento è di un fattore molto inferiore a 2. E in Clang e GCC abbiamo il -ftrapv
controllo degli overflow di numeri interi (firmati) e non è neanche così lento.
Da qui la domanda: come possiamo ottenere prestazioni ragionevoli in Swift senza perdere le reti di sicurezza?
Modifica 2: ho fatto un po 'più di benchmarking, con loop molto semplici lungo la linea di
for i in 0..<n {
x[i] = x[i] ^ 12345678
}
(Qui l'operazione xor è lì solo per poter trovare più facilmente il loop pertinente nel codice assembly. Ho cercato di scegliere un'operazione che sia facile da individuare ma anche "innocua", nel senso che non dovrebbe richiedere alcun controllo relativo a overflow di numeri interi.)
Ancora una volta, c'è stata un'enorme differenza nelle prestazioni tra -O3
e -Ofast
. Quindi ho dato un'occhiata al codice assembly:
Con
-Ofast
ottengo praticamente quello che mi aspetterei. La parte pertinente è un loop con 5 istruzioni in linguaggio macchina.Con
-O3
ottengo qualcosa che andava oltre la mia più sfrenata immaginazione. Il ciclo interno comprende 88 righe di codice assembly. Non ho provato a capirlo tutto, ma le parti più sospette sono 13 invocazioni di "callq _swift_retain" e altre 13 invocazioni di "callq _swift_release". Cioè, 26 chiamate di subroutine nel loop interno !
Modifica 3: Nei commenti, Ferruccio ha chiesto parametri di riferimento equi, nel senso che non si basano su funzioni integrate (ad es. Ordinamento). Penso che il seguente programma sia un buon esempio:
let n = 10000
var x = [Int](repeating: 1, count: n)
for i in 0..<n {
for j in 0..<n {
x[i] = x[j]
}
}
Non esiste aritmetica, quindi non dobbiamo preoccuparci di overflow di numeri interi. L'unica cosa che facciamo è solo un sacco di riferimenti di array. E i risultati sono qui: Swift -O3 perde di un fattore quasi 500 rispetto a -Ofast:
- C ++ -O3: 0,05 s
- C ++ -O0: 0,4 s
- Java: 0,2 s
- Python con PyPy: 0,5 s
- Python: 12 s
- Rapido-Rapido: 0,05 s
- Swift -O3: 23 s
- Swift -O0: 443 s
(Se sei preoccupato che il compilatore possa ottimizzare completamente i loop inutili, puoi cambiarlo in ad es x[i] ^= x[j]
. E aggiungere un'istruzione di stampa che produca x[0]
. Questo non cambia nulla; i tempi saranno molto simili.)
E sì, qui l'implementazione di Python era una stupida implementazione pura di Python con un elenco di ints e annidato per loop. Dovrebbe essere molto più lento di Swift non ottimizzato. Qualcosa sembra seriamente rotto con Swift e l'indicizzazione dell'array.
Modifica 4: questi problemi (così come altri problemi relativi alle prestazioni) sembrano essere stati risolti in Xcode 6 beta 5.
Per l'ordinamento, ora ho i seguenti tempi:
- clang ++ -O3: 0,06 s
- swiftc -Fast: 0,1 s
- swiftc -O: 0,1 s
- swiftc: 4 s
Per i loop nidificati:
- clang ++ -O3: 0,06 s
- swiftc -Fast: 0,3 s
- swiftc -O: 0,4 s
- swiftc: 540 s
Sembra che non ci sia più motivo di usare il non sicuro -Ofast
(aka -Ounchecked
); plain -O
produce ugualmente un buon codice.
xcrun --sdk macosx swift -O3
. È più corto