Swift Bêta de la performance: tableaux de tri
J'ai été la mise en œuvre d'un algorithme rapide de la Bêta et a remarqué que la performance était très pauvre. 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
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
En C++, une opération similaire prend 0.06 s sur mon ordinateur.
En Python, il faut 0,6 s (pas de trucs, juste y = sorted(x) pour une liste d'entiers).
Dans Swift prend 6s si je compile avec la commande suivante:
xcrun swift -O3 -sdk `xcrun --show-sdk-path --sdk macosx`
Et il faut autant que 88s si je compile avec la commande suivante:
xcrun swift -O0 -sdk `xcrun --show-sdk-path --sdk macosx`
Timings 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: météo remarqué que le changement de -O3
à -Ofast
fait ce code exécuté presque aussi rapide que la version C++! Cependant, -Ofast
la sémantique de la langue beaucoup dans mes tests, il désactivé le vérifie les débordements d'entiers et le tableau d'indexation des débordements. Par exemple, avec -Ofast
suivantes Swift code s'exécute en mode silencieux sans s'écraser (et imprime des ordures):
let n = 10000000
print(n*n*n*n*n)
let x = [Int](repeating: 10, count: n)
print(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, nous avons obtenu -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 des performances acceptables en 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 les -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 reçois 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 la boucle interne!
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
var x = [Int](repeating: 1, count: n)
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 la changer 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 unoptimized 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 -O
produit d'aussi bons code.
- Voici un autre "Swift 100 fois plus lent que C" question: stackoverflow.com/questions/24102609/...
- Et voici la discussion sur l'Apple matériel de marketing liées à la Swift est une bonne performance dans le tri: programmers.stackexchange.com/q/242816/913
- Il serait plus intéressant et instructif de voir une comparaison à une fonction de tri mis en œuvre en Python. Python
sorted()
fonction est une partie de son exécution, ce qui (je crois) est écrit en C. - Voir modifier 3. (Ce n'est pas une fonction de tri, mais je pense que cela montre très bien ce genre de code fonctionne mal dans Swift en comparaison avec tout le reste, y compris Python.)
- Pouvez-vous le comparer Java trop?
- Fait. (En passant, un naïf compilateur Java devrait produire plus lent que le code naïf Swift compilateur. En Java pour calculer
x[i]
vous devez d'abord vérifier quex != null
et alors quex.length > i
. En Swift, nous pouvons ignorer la première case. Néanmoins, comme nous le voyons dans les indices de référence, Java gagne Swift -O3 par un facteur env. 100.) - Avez-vous vu la pièce de la "La Swift en Langage de Programmation" iBook sur les boucles for? Il est dit que "[i] est une constante dont la valeur est automatiquement définie au début de chaque itération de la boucle.". Peut-être déclarer comme
var i: Int
avant la boucle va changer les choses? - Dépend de la plate-forme. Null check pas nécessaire si la plate-forme virtuelle de la mémoire et de ne pas utiliser le peu de mémoire que les adresses valides emplacements de mémoire (par exemple, Windows et je pense que d'autres Systèmes d'exploitation de trop); la MMU les poignées de la valeur null est à vérifier dans ce cas. Pas surprenant du tout que une nouvelle marque de front-end pour une nouvelle langue n'est pire qu'un vieux de 6 ans, mature, front-end. Je soupçonne Apple va corriger cela avant de Swift est sortie de la bêta.
- Vous pouvez compiler avec:
xcrun --sdk macosx swift -O3
. C'est plus court. - Ceci lien montre quelques autres opérations de base de comparaison Objective-C.
- Rappelez-vous que Java est déjà vérifie les limites du tableau, lié contrôles sont très susceptibles d'être retirés que lorsque le compilateur peut prouver que. Java devrait fonctionner à peu près comme C (une fois bien réchauffé) dans ce cas simple. Null contrôles ne sont généralement pas effectuées directement, mais pris au piège par le matériel et le compilateur peut prouver x[i] n'est pas nul, bien sûr, le compilateur doit être au-delà de stupide pour vérifier pour x
null
. - quel est le problème avec l'aide de swift "filets de sécurité" dans le développement et l'épargne-Ofast pour la libération?
- vous avez besoin de la "filet de sécurité" au cours de la production que l'entrée varie. C'est différent pour traiter les valeurs entre 1 et 10 et de les multiplier par rapport à la multiplication des valeurs de l'ordre de 2^31. Par exemple, l'infâme heartbleed bug a été causé par un manque de contrôle de portée.
- bien sûr, mais si vous êtes conscient des risques alors sûrement, vous pouvez désinfecter vos entrées si nécessaire, afin de garantir que le trop-plein ne se produira pas
- pas dire que c'est l'idéal, mais si la performance est la priorité, puis les risques au moins semblent gérables
- pour le dire simplement, nous ne vivons pas dans un monde parfait, et en essayant de faire ce que vous suggérez dans 1M LoC projets est beaucoup plus difficile alors que vous ne l'imaginez. Les Bugs ne le exis, de débordement de pile (nom du site) a été l'un des plus répandus (et est toujours) et devant le non-exécution de bits utilisés pour permettre l'exécution de code arbitraire très souvent. Java fonctionne avec la gamme complète des vérifications de tous les temps et il n'a pas vraiment d'incidence sur la performance, ayant les contrôles et à défaut gracieusement est un grand exploit pour la langue. Au cours des dernières années, il y a une énorme faille de sécurité en raison de contournement via Dangereux en apparence bien fait le code.
- Tout le monde sait que tout itération sur iOS ou OS X qui a plus de 10000 itérations doit être fait en C ou C++. Où est la surprise? Est-ce un rethorical question?
- Par la voie,
-Ofast
désactive également des contrôles pour déballer nils; vous pouvez compiler et exécuter ce "succès":let s: Double? = nil; println(s!)
- Avec la version Beta 5 il y a eu une amélioration substantielle de Swift vitesse -- voir ce post par Jesse Squires pour plus de détails.
- Serez-vous également de mettre à jour cette Swift 2.0 comme il le prétend augmentation des performances. Dans mes tests, j'ai découvert que si vous compilez avec -Ounchecked c'est 100 000 plus lent, même pour une simple boucle de tests. Avec -Ounchecked il est "seulement" 50 fois plus lent. Encore, il souffle en Python hors de l'eau dans les deux cas.
- La java chiffre semble élevé, de sorte que j'ai testé moi-même et a obtenu de temps de 50-60 ms pour exécuter le code pour le "=" et 60-80 ms si j'utilise le "^=". Avez-vous d'inclure le temps de démarrage de la VM dans ces chiffres, ou peut-être vous avez voulu dire .02? Java est généralement aussi vite que C pour ce type d'opération. Aussi java installe sur .04(=) et .06(^=) quand je lance la boucle (permettant de Java le temps de les compiler en une machine optimisée de la langue). Le .04 peut inclure d'essai-rupture des optimisations bien.
Vous devez vous connecter pour publier un commentaire.
tl;dr Swift 1.0 est maintenant aussi vite que C par ce repère à l'aide de la version par défaut de l'optimisation du niveau de [-O].
Ici est un lieu de quicksort dans Swift Beta:
Et la même en C:
À la fois le travail:
Les deux sont appelés dans le même programme qu'à l'écrit.
Cette fonction convertit l'absolu fois de secondes:
Voici, en résumé, le compilateur optimazation niveaux:
Temps en secondes avec [Individualisé] pour n=10_000:
Ici est de Swift builtin sort() pour n=10_000:
Ici est [-O] pour n=10_000:
Comme vous pouvez le voir, de Swift, de l'amélioration du rendement d'un facteur 20.
Comme par mweathers réponse, réglage [-Ofast] fait la véritable différence, résultant en ces temps de n=10_000:
Et pour n=1_000_000:
À des fins de comparaison, c'est avec [Individualisé] pour n=1_000_000:
Tellement rapide avec aucun des optimisations était presque 1000x plus lent que C dans ce repère, à ce stade de son développement. D'autre part, avec les deux compilateurs définie sur [-Ofast] Swift effectivement accompli au moins aussi bien, si pas un peu mieux que les C.
Il a été souligné que [-Ofast] la sémantique de la langue, ce qui en fait potentiellement dangereux. C'est ce que Apple états dans Xcode 5.0 notes de version:
Ils ont tous, mais l'avocat il. Si c'est judicieux ou pas, je ne pouvais pas le dire, mais de ce que je peux dire, il semble assez raisonnable d'utiliser [-Ofast] dans un communiqué de presse si vous ne faites pas de haute précision en arithmétique à virgule flottante et que vous en êtes sûr pas de nombre entier ou un tableau des débordements sont possibles dans votre programme. Si vous avez besoin de haute performance et dépassement de contrôles /précis d'arithmétique, puis choisir une autre langue pour le moment.
BETA 3 MISE À JOUR:
n=10_000 avec [-O]:
Swift en général est un peu plus rapide et il semble que Swift intégré de sorte a bien changé de manière significative.
DERNIÈRE MISE À JOUR:
[Individualisé]:
[-O]:
[-Ounchecked]:
TL;DR: Oui, la seule Swift de la langue mise en œuvre est lente, maintenant. Si vous avez besoin rapide, numérique (et d'autres types de code, sans doute) du code, il suffit d'aller avec un autre. Dans l'avenir, vous devez réévaluer votre choix. Il pourrait être assez bon pour la plupart des applications de code est écrit à un niveau plus élevé, cependant.
De ce que je vois dans SIL et IR LLVM, il semble comme ils ont besoin d'un tas d'optimisations pour le retrait de conserve et restitue, qui pourrait être mise en œuvre dans Clang (Objective-C), mais ils n'ont pas porté encore. C'est la théorie, je vais avec (pour l'instant... j'ai encore besoin de confirmer que Clang fait quelque chose à ce sujet), depuis un profileur de fonctionner sur le dernier test de cette question, les rendements de cette “jolie” résultat:
Comme cela a été dit par beaucoup d'autres,
-Ofast
est totalement dangereux et change la langue de la sémantique. Pour moi, c'est à la “Si vous allez l'utiliser, il suffit d'utiliser une autre langue” de la scène. Je vais re-évaluer ce choix plus tard, si elle change.-O3
nous permet d'obtenir un tas deswift_retain
etswift_release
appels qui, honnêtement, ne me regardez pas comme ils devraient être là pour cet exemple. L'optimiseur doit avoir élidés (la plupart de) leur AFAICT, car il sait que la plupart des informations sur le tableau, et il sait qu'il a (au moins) une référence forte pour elle.Il ne devrait pas émettre plus de conserve quand il n'est même pas à appeler des fonctions qui pourraient libérer les objets. Je ne pense pas qu'un constructeur array peut retourner un tableau qui est plus petit que ce qui était demandé, ce qui signifie que beaucoup de vérifications qui ont été émises sont inutiles. Il sait aussi que l'entier ne sera jamais au-dessus de 10k, de sorte que le dépassement de contrôles peut être optimisé (pas à cause de
-Ofast
étrangeté, mais à cause de la sémantique de la langue (rien d'autre est en train de changer que la var ne peut y accéder, et en ajoutant jusqu'à 10k est sans danger pour le typeInt
).Le compilateur pourrait ne pas être en mesure de unbox le tableau ou le tableau des éléments, bien que, depuis qu'ils sont passé à
sort()
, qui est une fonction externe et dispose pour obtenir les arguments qu'il attend. Cela va nous faire utiliser leInt
valeurs indirectement, ce qui serait aller un peu plus lent. Cela pourrait changer si lesort()
fonction générique (pas dans le multi-way méthode) était disponible pour le compilateur et a obtenu inline.C'est un très nouveau (publiquement) de la langue, et il va par ce que je suppose que beaucoup de changements, car il y a des gens (très) impliqués avec la Swift de la langue pour obtenir des commentaires et ils disent tous la langue n'est pas fini et sera changement.
Code utilisé:
P. S: je ne suis pas un expert sur Objective-C, ni de toutes les installations de Cacao, Objective-C, ou la Swift runtimes. Je pourrais aussi être en supposant que certaines choses que je n'ai pas écrit.
-Ofast
"totalement" dangereux"? En supposant que vous savez comment faire pour tester votre code et d'écarter les débordements.-Ofast
aussi change la langue de la sémantique, mais je ne peut pas financer tous les docs pour elle. Comment pouvez-vous être sûr que vous savez ce qu'elle fait?Int[]
" depuis que dépend le niveau le compilateur en ligne et il devrait être en mesure de l'inclure des boucles serrées avec/après quelques conseils.J'ai décidé de prendre un coup d'oeil à cela pour le plaisir, et voici les horaires que je reçois:
Swift
Résultats:
Swift 1.1
Swift 1.2
Swift 2.0
Il semble être le même rendement que si je compile avec
-Ounchecked
.Swift 3.0
Il semble y avoir eu une régression de la performance de Swift 2.0 Swift 3.0, et je vois aussi une différence entre
-O
et-Ounchecked
pour la première fois.Swift 4.0
Swift 4 améliore la performance de nouveau, tout en conservant un écart entre
-O
et-Ounchecked
.-O -whole-module-optimization
ne semble pas faire une différence.C++
Résultats:
Apple Clang 6.0
Apple Clang 6.1.0
Apple Clang 7.0.0
Apple Clang 8.0.0
Apple Clang 9.0.0
Verdict
Que de la rédaction de ce document, Swift est un peu rapide, mais pas encore aussi rapide que du C++de tri lors de la compilation avec
-O
, avec les compilateurs & les bibliothèques. Avec-Ounchecked
, il semble être aussi rapide que du C++ dans Swift 4.0.2 et Apple LLVM 9.0.0.De
Le Langage de Programmation Swift
:La
sort
fonction a deux déclarations.La déclaration par défaut qui vous permet de spécifier une comparaison de fermeture:
Et une seconde déclaration qui ne font qu'un seul paramètre (le tableau) et est "codé en dur pour utiliser le moins de point de comparaison."
J'ai testé une version modifiée de votre code dans une aire de jeux avec la fermeture ajouté sur afin que je puisse contrôler la fonction d'un peu plus près, et j'ai trouvé que, avec n fixé à 1000, la fermeture a été appelé à environ 11 000 fois.
Il n'est pas un fonctionnement efficace, je vous conseille d'utiliser un meilleur tri fonction de mise en œuvre.
EDIT:
J'ai pris un coup d'oeil à la Quicksort page de wikipédia et a écrit une mise en œuvre rapide pour elle. Voici le programme complet j'ai utilisé (dans une aire de jeux)
À l'aide de ce avec n=1000, j'ai trouvé que
Il semble que le haut-méthode de tri est (ou à proximité) tri rapide, et il est vraiment lent...
2*n*log(n)
. C'est 13815 comparaisons pour trier n = 1000 éléments, de sorte que si la fonction de comparaison est appelé environ 11000 temps qui ne semble pas si mauvais.log(n)
de la complexité algorithmique désigne classiquement à log en base 2. La raison pour ne pas en déclarant la base est que la modification de la loi de base pour les logarithmes seulement introduit une constante multiplicateur, qui est rejetée pour l'application de l'O-notation.C(n) = 2n ln n ≈ 1.39n log₂ n
. Pour n = 1000 cela donne C(n) = 13815, et c'est pas un "big-O de notation".De Xcode 7, vous pouvez activer
Fast, Whole Module Optimization
. Cela devrait accroître vos performances immédiatement.Swift performances de l'ensemble revisité:
J'ai écrit ma propre référence de la comparaison de Swift avec C/Objective-C. Ma référence en matière calcule les nombres premiers. Il utilise le tableau de la précédente nombres premiers à rechercher les facteurs premiers de chaque nouveau candidat, donc c'est assez rapide. Cependant, il n'TONNES de tableau de lecture, et moins de l'écriture à des tableaux.
L'origine, j'avais fait cette référence à l'encontre de Swift 1.2. J'ai décidé de mettre à jour le projet et l'exécuter à l'encontre de Swift 2.0.
Le projet permet de choisir entre l'utilisation normale swift tableaux et l'utilisation de Swift dangereux de la mémoire tampon à l'aide du tableau de la sémantique.
Pour C/Objective-C, vous pouvez soit opter pour l'utilisation NSArrays, ou C malloc ed tableaux.
Les résultats des tests semblent assez similaires avec le plus rapide, le plus petit d'optimisation de code ([-0]) ou plus rapide, agressif ([-0fast]) l'optimisation.
Swift 2.0 performance est toujours horrible avec l'optimisation du code éteint, alors que C/Objective-C rendement n'est que modérément plus lent.
La ligne de fond est que C malloc avais tableau basé sur les calculs sont les plus rapides, par une modeste marge
Swift à l'insécurité des tampons prend environ 1.19 X 1.20 X plus de C malloc avais tableaux lors de l'utilisation la plus rapide, le plus petit d'optimisation de code. la différence semble un peu moins rapide, agressif optimisation (Swift prend plus de 1,18 x à 1,16 x plus longtemps que C.
Si vous utilisez régulièrement Swift tableaux, la différence avec C est légèrement plus. (Swift prend ~1.22 à 1,23 plus.)
Régulière Swift tableaux sont
DRAMATICALLY
plus vite qu'ils ne l'étaient dans Swift 1.2/Xcode 6. Leur performance est si près de Swift dangereux tampon de base de tableaux que l'utilisation dangereuse de la mémoire tampon ne semble pas vraiment la peine plus, ce qui est grand.BTW, Objective-C NSArray performance pue. Si vous allez utiliser le conteneur natif des objets dans les deux langues, Swift est CONSIDÉRABLEMENT plus rapide.
Vous pouvez consulter mon projet sur github à SwiftPerformanceBenchmark
Il a une INTERFACE utilisateur simple qui rend la collecte de statistiques assez facile.
Il est intéressant de noter que le tri semble être légèrement plus rapide qu'en C maintenant, mais que ce nombre premier algorithme est encore plus rapide en Swift.
La question principale qui est mentionné par d'autres, mais ne l'appelle pas assez, c'est que
-O3
ne fait rien du tout dans Swift (et n'a jamais) donc lorsqu'il est compilé avec qui il est effectivement non-optimisés (-Onone
).Option noms ont changé au fil du temps de sorte que certains autres réponses ont obsolètes drapeaux pour les options de compilation. Corriger les options actuelles (Swift 2.2) sont:
Ensemble de module d'optimisation a un ralentissement de la compilation, mais la possibilité d'en optimiser tous les fichiers dans le module, c'est à dire à l'intérieur de chaque cadre et dans le code de l'application, mais pas entre eux. Vous devriez l'utiliser pour quoi que ce soit critique pour les performances)
Vous pouvez également désactiver les contrôles de sécurité pour encore plus de vitesse, mais avec toutes les affirmations et les conditions préalables et pas seulement les personnes à mobilité réduite optimisée sur la base qu'ils sont corrects. Si vous avez jamais touché à une assertion cela signifie que vous êtes dans un comportement indéterminé. Utiliser avec une extrême prudence et seulement si vous déterminez que le gain de vitesse est intéressant pour vous (par des tests). Si vous trouvez qu'il est précieux pour le code, je vous recommandons de séparer ce code dans un cadre distinct et seulement désactiver les contrôles de sécurité pour ce module.
C'est mon Blog à propos de la fonction de Tri Rapide- Github échantillon de Tri Rapide
Vous pouvez prendre un coup d'oeil sur Lomuto de partitionnement de l'algorithme de Partitionnement de la liste. Écrit en Swift
Swift 4.1 introduit de nouvelles
-Osize
optimisation de la mode.Pour en savoir plus : https://swift.org/blog/osize/