J'ai été la mise en œuvre d'un algorithme rapide et remarqué que la performance a été très mauvaise. Après pour aller plus loin, j'ai réalisé que l'un des goulots d'étranglement a été quelque chose d'aussi simple que le tri des tableaux. La partie pertinente est ici:
let n = 1000000
let x = Int[](count: n, repeatedValue: 0)
for i in 0..n {
x[i] = random()
}
// start clock here
let y = sort(x)
// stop clock here
En C++, une opération similaire prend 0.06 s sur mon ordinateur.
En Python, il prend 0,6 s (pas de trucs, juste y = sorted(x) pour une liste d'entiers).
En Swift, il prend 6 s si je compile avec la commande suivante:
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
Et il faut autant que 88 s si je compile avec la commande suivante:
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
Les périodes de temps dans Xcode avec "Libération" et "Debug" versions sont similaires.
Quel est le problème ici? J'ai pu comprendre certaines pertes de performances en comparaison avec le C++, mais pas 10 fois de ralentissement en comparaison avec pur Python.
Edit: mweathers remarqué qu'en changeant -O3
de -Ofast
rend ce code exécuté presque aussi rapide que la version C++! Toutefois, -Ofast
la sémantique de la langue beaucoup dans mes tests, il a désactivé les chèques pour les débordements d'entiers et le tableau d'indexation des débordements. Par exemple, avec -Ofast
la suite Swift code s'exécute en mode silencieux sans s'écraser (et imprime des ordures):
let n = 10000000
println(n*n*n*n*n)
let x = Int[](count: n, repeatedValue: 10)
println(x[n])
Donc, -Ofast
n'est pas ce que nous voulons; le point de l'ensemble de Swift est que nous avons les filets de sécurité en place. Bien sûr, les filets de sécurité qui ont une incidence sur les performances, mais ils ne devraient pas faire les programmes 100 fois plus lent. Rappelez-vous que Java est déjà vérifie les limites du tableau, et dans les cas typiques, le ralentissement de l'activité par un facteur inférieur à 2. Et dans le Bruit et la GCC que nous avons eu, -ftrapv
pour le contrôle (signé) des débordements d'entiers, et il n'est pas lent, soit.
D'où la question: comment pouvons-nous obtenir un rendement raisonnables dans Swift sans perdre les filets de sécurité?
Edit 2: j'ai fait un peu plus d'analyse comparative, très simples boucles le long de la lignes de
for i in 0..n {
x[i] = x[i] ^ 12345678
}
(Ici, l'opération xor est là juste pour que je puisse plus facilement s'y retrouver boucle dans le code assembleur. J'ai essayé de chercher une opération qui est facile à repérer, mais aussi "inoffensif" dans le sens où il ne devrait pas exiger des vérifications liées à des débordements d'entiers.)
Encore une fois, il y avait une énorme différence dans les performances entre -O3
et -Ofast
. J'ai donc eu un coup d'oeil à l'assemblée de code:
Avec
-Ofast
- je obtenir à peu près ce que je m'attends. La partie pertinente est une boucle avec 5 instructions en langage machine.Avec
-O3
- je obtenir quelque chose qui était au-delà de mon imagination la plus folle. La boucle interne s'étend sur 88 lignes de code assembleur. Je n'ai pas essayer de tout comprendre, mais la plupart des suspects pièces sont 13 invocations de "callq _swift_retain" et l'autre de 13 invocations de "callq _swift_release". C'est, 26 sous-routine appels dans l'intérieur de la boucle!
Edit 3: Dans les commentaires, Ferruccio demandé pour les points de référence qui sont juste dans le sens où ils ne reposent pas sur des fonctions intégrées (par exemple, tri). Je pense que le programme suivant est un assez bon exemple:
let n = 10000
let x = Int[](count: n, repeatedValue: 1)
for i in 0..n {
for j in 0..n {
x[i] = x[j]
}
}
Il n'est pas de l'arithmétique, de sorte que nous n'avons pas besoin de s'inquiéter au sujet des débordements d'entiers. La seule chose que nous faisons est juste beaucoup de références de tableau. Et les résultats sont ici-Swift -O3 perd par un facteur de près de 500 en comparaison avec d'-Ofast:
- C++ -O3: 0,05 s
- C++ -O0: 0,4 s
- Java: 0,2 s
- Python avec PyPy: 0,5 s
- Python: 12 s
- Swift -Ofast: 0,05 s
- Swift -O3: 23 s
- Swift -O0: 443 s
(Si vous craignez que le compilateur peut optimiser l'inutile boucles entièrement, vous pouvez modifier, par exemple, x[i] ^= x[j]
, et ajouter une instruction d'impression que les sorties x[0]
. Cela ne change rien; les horaires sont très similaires.)
Et oui, ici, le Python de la mise en œuvre a été un stupide pur Python de mise en œuvre avec une liste d'entiers et des boucles for imbriquées. Il devrait être beaucoup plus lent que unoptimised Swift. Quelque chose semble être brisé avec Swift et tableau d'indexation.
Edit 4: Ces questions (ainsi que certains autres problèmes de performances) semble avoir été corrigé dans Xcode 6 beta 5.
Pour le tri, j'ai maintenant le minutage suivant:
- clang++ -O3: 0.06 s
- swiftc -Ofast: 0,1 s
- swiftc -O: 0,1 s
- swiftc: 4 s
Pour les boucles imbriquées:
- clang++ -O3: 0.06 s
- swiftc -Ofast: 0,3 s
- swiftc -O: 0,4 s
- swiftc: 540 s
Il semble qu'il n'y a aucune raison de plus pour utiliser le dangereux -Ofast
(un.k.un. -Ounchecked
); plaine de l' -O
produit également du bon code.