Quelqu'un peut-il battre les performances de mon entier en code std :: string, lié ci-dessous?
Il y a déjà plusieurs questions qui expliquent comment convertir un entier en un std::string
en C ++, comme celui-ci , mais aucune des solutions proposées n'est efficace.
Voici le code prêt à la compilation pour certaines méthodes courantes à affronter:
- La "manière C ++", en utilisant stringstream: http://ideone.com/jh3Sa
- sprintf, que les SO-ers recommandent généralement aux amateurs de performances: http://ideone.com/82kwR
Contrairement à la croyance populaire , boost::lexical_cast
a sa propre implémentation ( livre blanc ) et n'utilise pas d' stringstream
opérateurs d'insertion numérique. J'aimerais vraiment voir ses performances comparées, car cette autre question suggère que c'est misérable .
Et ma propre contribution, qui est compétitive sur les ordinateurs de bureau, et démontre une approche qui fonctionne à pleine vitesse sur les systèmes embarqués également, contrairement aux algorithmes dépendant du module entier:
- Les algorithmes de Ben: http://ideone.com/SsEUW
Si vous souhaitez utiliser ce code, je le rendrai disponible sous une licence BSD simplifiée (utilisation commerciale autorisée, attribution requise). Il suffit de demander.
Enfin, la fonction ltoa
n'est pas standard mais largement disponible.
- version ltoa, pour tous ceux qui ont un compilateur qui le fournit (ideone ne le fait pas): http://ideone.com/T5Wim
Je publierai sous peu mes mesures de performance comme réponse.
Règles pour les algorithmes
- Fournissez le code pour une conversion d'au moins 32 bits entiers signés et non signés en décimal.
- Produire une sortie sous forme de fichier
std::string
. - Aucune astuce incompatible avec les threads et les signaux (par exemple, les tampons statiques).
- Vous pouvez supposer un jeu de caractères ASCII.
- Assurez-vous de tester votre code sur
INT_MIN
une machine à complément à deux où la valeur absolue n'est pas représentable. - Idéalement, la sortie doit être caractère pour caractère identique à la version C ++ canonique en utilisant
stringstream
, http://ideone.com/jh3Sa , mais tout ce qui est clairement compréhensible que le nombre correct est correct aussi. - NOUVEAU : Bien que vous puissiez utiliser toutes les options de compilateur et d'optimisation (sauf complètement désactivées) que vous voulez pour la comparaison, le code doit également compiler et donner des résultats corrects sous au moins VC ++ 2010 et g ++.
Discussion espérée
Outre de meilleurs algorithmes, j'aimerais également obtenir des points de repère sur plusieurs plates-formes et compilateurs différents (utilisons le débit Mo / s comme unité de mesure standard). Je crois que le code de mon algorithme (je sais que le sprintf
benchmark prend des raccourcis - maintenant corrigé) est un comportement bien défini par la norme, au moins sous l'hypothèse ASCII, mais si vous voyez un comportement ou des entrées non définis pour lesquels la sortie n'est pas valide, veuillez le signaler.
Conclusions:
Différents algorithmes fonctionnent pour g ++ et VC2010, probablement en raison des différentes implémentations de std::string
chacun. VC2010 fait clairement un meilleur travail avec NRVO, se débarrassant du retour par valeur aidé uniquement sur gcc.
Le code a été trouvé qui surpasse sprintf
d'un ordre de grandeur. ostringstream
prend du retard d'un facteur de 50 et plus.
Le gagnant du défi est user434507 qui produit du code qui exécute 350% de sa vitesse sur gcc. D'autres inscriptions sont fermées en raison des caprices de la communauté SO.
Les champions de vitesse actuels (finaux?) Sont:
- Pour gcc: user434507, à 8 fois plus rapide que
sprintf
: http://ideone.com/0uhhX - Pour Visual C ++: Timo, à 15 fois plus rapide que
sprintf
: http://ideone.com/VpKO3
la source
Réponses:
Cela va exploser sur les systèmes qui interdisent les accès mémoire non alignés (dans ce cas, la première affectation non alignée via
*(short*)
provoquerait un segfault), mais devrait fonctionner très bien sinon.Une chose importante à faire est de minimiser l'utilisation de
std::string
. (Ironique, je sais.) Dans Visual Studio, par exemple, la plupart des appels aux méthodes de std :: string ne sont pas insérés, même si vous spécifiez / Ob2 dans les options du compilateur. Ainsi, même quelque chose d'aussi trivial qu'un appel àstd::string::clear()
, auquel vous vous attendez peut-être très rapide, peut prendre 100 clics d'horloge lors de la liaison CRT en tant que bibliothèque statique, et jusqu'à 300 clics d'horloge lors de la liaison en tant que DLL.Pour la même raison, le retour par référence est préférable car il évite une affectation, un constructeur et un destructeur.
la source
sprintf
. Et avec VC ++ 2010, il obtient environ 50 Mo / s, soit environ deux fois la vitesse du sprintf.sprintf
implémentation, je l'ai déjà mentionné dans ma question, mais je crois que le code-to-beat donne exactement le même résultat que stringstream.sprintf
wrapper, pour éviter toute confusion.Ah, un défi génial au fait ... Je me suis beaucoup amusé avec ça.
J'ai deux algorithmes à soumettre (le code est en bas si vous avez envie de sauter dessus). Dans mes comparaisons, j'exige que la fonction renvoie une chaîne et qu'elle puisse gérer les int et unsigned int. Comparer des choses qui ne construisent pas de chaîne à celles qui le font n'a pas vraiment de sens.
Le premier est une implémentation amusante qui n'utilise aucune table de recherche précalculée ni division / modulo explicite. Celui-ci est compétitif avec les autres avec gcc et avec tout sauf Timo sur msvc (pour une bonne raison que j'explique ci-dessous). Le deuxième algorithme est ma soumission actuelle pour les meilleures performances. Dans mes tests, il bat tous les autres sur gcc et msvc.
Je pense que je sais pourquoi certains des résultats sur MSVC sont très bons. std :: string a deux constructeurs pertinents
std::string(char* str, size_t n)
et
std::string(ForwardIterator b, ForwardIterator e)
gcc fait la même chose pour les deux ... c'est-à-dire qu'il utilise le second pour implémenter le premier. Le premier constructeur peut être implémenté beaucoup plus efficacement que cela et MSVC le fait. L'avantage secondaire de ceci est que dans certains cas (comme mon code rapide et celui de Timo), le constructeur de chaînes peut être inséré. En fait, le simple changement entre ces constructeurs dans MSVC est presque une différence 2x pour mon code.
Mes résultats de test de performance:
Sources de code:
- Voigt
- Timo
- ergosys
- user434507
- user-voigt-timo
- hopman-fun
- hopman-fast
gcc 4.4.5 -O2 sur Ubuntu 10.10 64 bits, Core i5
MSVC 2010 64 bits / Ox sous Windows 7 64 bits, Core i5
Voici quelques résultats et un cadre de test / timing sur ideone
http://ideone.com/XZRqp
Notez que ideone est un environnement 32 bits. Mes deux algorithmes en souffrent, mais hopman_fast est au moins toujours compétitif.
Notez que pour ceux qui ne construisent pas de chaîne, j'ai ajouté le modèle de fonction suivant:
Maintenant pour mon code ... d'abord le plus amusant:
Et puis le rapide:
la source
Données de référence pour le code fourni dans la question:
Sur ideone (gcc 4.3.4):
Core i7, Windows 7 64 bits, 8 Go de RAM, Visual C ++ 2010 32 bits:
cl /Ox /EHsc
Core i7, Windows 7 64 bits, 8 Go de RAM, Visual C ++ 2010 64 bits:
cl /Ox /EHsc
Core i7, Windows 7 64 bits, 8 Go de RAM, cygwin gcc 4.3.4:
g++ -O3
edit : J'allais ajouter ma propre réponse, mais la question était fermée donc je l'ajoute ici. :) J'ai écrit mon propre algorithme et j'ai réussi à obtenir une amélioration décente par rapport au code de Ben, bien que je ne l'ai testé que dans MSVC 2010. J'ai également fait un benchmark de toutes les implémentations présentées jusqu'à présent, en utilisant la même configuration de test qui était dans l'original de Ben code. - Timo
Intel Q9450, Win XP 32 bits, MSVC 2010
cl /O2 /EHsc
-
la source
std::string
ou une mauvaise optimisation du code arithmétique. Je vais créer une autre version qui ne sera pas convertiestd::string
à la fin et voir si gcc s'en sort mieux.Bien que les informations que nous obtenons ici pour les algorithmes soient plutôt intéressantes, je pense que la question est "cassée", et je vais expliquer pourquoi je pense ceci:
La question demande de prendre les performances de
int
->std::string
conversion, et cela peut être intéressant lors de la comparaison d'une méthode couramment disponible, telle que différentes implémentations de chaînes de caractères ou boost :: lexical_cast. Cela n'a cependant pas de sens quand on demande un nouveau code , un algorithme spécialisé, de faire cela. La raison en est que int2string impliquera toujours une allocation de tas de std :: string et si nous essayons d'extraire le dernier de notre algorithme de conversion, je ne pense pas qu'il soit logique de mélanger ces mesures avec les allocations de tas effectuées par std: :chaîne. Si je veux une conversion performante, j'utiliserai toujours un tampon de taille fixe et n'allouerai certainement jamais rien sur le tas!Pour résumer, je pense que les horaires devraient être fractionnés:
Ces aspects ne doivent pas être mélangés en un seul moment, à mon humble avis.
la source
std::string
exigence de «production en tant que » a été placée là simplement pour rendre les choses justes et cohérentes pour toutes les soumissions. Les algorithmes plus rapides à produire desstd::string
résultats seront également plus rapides à remplir une mémoire tampon préallouée.Je ne peux pas tester sous VS, mais cela semble être plus rapide que votre code pour g ++, environ 10%. Il pourrait probablement être réglé, les valeurs de décision choisies sont des suppositions. int seulement, désolé.
la source
char
àunsigned
produit une amélioration de vitesse similaire dans mon code, au moins sur gcc / ideone ideone.com/uthKK . Je vais tester sur VS demain.Mise à jour de la réponse de user2985907 ... modp_ufast ...
la source
Mismatch found: Generated: -99 Reference: -9099999 Mismatch found: Generated: -99 Reference: -9099998 Mismatch found: Generated: -99 Reference: -9099997
Voici ma petite tentative de ce puzzle amusant.
Au lieu d'utiliser des tables de recherche, je voulais que le compilateur comprenne tout. Dans ce cas en particulier - si vous lisez Hackers 'Delight, vous voyez comment fonctionnent la division et le modulo - ce qui rend très possible l'optimisation à l'aide des instructions SSE / AVX.
Benchmark de performance
Quant à la vitesse, mon benchmark ici me dit que c'est 1,5 fois plus rapide que le travail de Timo (sur mon Intel Haswell il tourne à environ 1 Go / s).
Choses que vous pourriez considérer comme une triche
En ce qui concerne la triche de ne pas fabriquer une chaîne std que j'utilise - bien sûr, j'ai également pris cela en considération pour mon point de repère de la méthode de Timo.
J'utilise un intrinsèque: BSR. Si vous le souhaitez, vous pouvez également utiliser les tables DeBruijn à la place - ce qui est l'une des choses sur lesquelles j'ai beaucoup écrit dans mon article «Le plus rapide 2log». Bien sûr, cela a une pénalité de performance (* eh bien ... si vous faites beaucoup d'opérations itoa, vous pouvez en fait faire un BSR plus rapide mais je suppose que ce n'est pas juste ...).
La façon dont ça marche
La première chose à faire est de déterminer la quantité de mémoire dont nous avons besoin. Il s'agit essentiellement d'un 10log, qui peut être implémenté de plusieurs manières intelligentes. Voir les " Bit Twiddling Hacks " fréquemment cités pour plus de détails.
La prochaine chose à faire est d'exécuter la sortie numérique. J'utilise la récursivité de modèle pour cela, donc le compilateur le comprendra.
J'utilise «modulo» et «div» juste à côté de l'autre. Si vous lisez Hacker's Delight, vous remarquerez que les deux sont étroitement liés, donc si vous avez une réponse, vous avez probablement l'autre aussi. J'ai pensé que le compilateur pouvait comprendre les détails ... :-)
Le code
Obtenir le nombre de chiffres à l'aide d'un journal (modifié) 10:
Obtenir vous-même la chaîne:
la source
J'ai eu ça assis pendant un certain temps et j'ai finalement réussi à le poster.
Quelques méthodes de plus par rapport au double mot à la fois hopman_fast . Les résultats sont pour le std :: string optimisé pour les chaînes courtes du GCC, sinon les différences de performances sont obscurcies par la surcharge du code de gestion des chaînes de copie à l'écriture. Le débit est mesuré de la même manière qu'ailleurs dans cette rubrique, le nombre de cycles concerne les parties de sérialisation brutes du code avant de copier le tampon de sortie dans une chaîne.
Commutateurs de compilation:
-DVSTRING - active les chaînes SSO pour les anciennes configurations GCC
-DBSR1 - active le
journal rapide10 -DRDTSC - active les compteurs de cycles
la source
Je crois avoir créé l'algorithme entier-à-chaîne le plus rapide. C'est une variante de l'algorithme Modulo 100 qui est environ 33% plus rapide, et surtout plus rapide pour les petits et les grands nombres. Cela s'appelle l'algorithme Script ItoS. Pour lire l'article qui explique comment j'ai conçu l'algorithme @see https://github.com/kabuki-starship/kabuki-toolkit/wiki/Engineering-a-Faster-Integer-to-String-Algorithm . Vous pouvez utiliser l'algorithme, mais pensez à contribuer à la VM Kabuki et consultez Script ; surtout si vous êtes intéressé par AMIL-NLP et / ou les protocoles réseau définis par logiciel.
Auteur
la source
std::string
pour que la comparaison avec les autres méthodes répertoriées ici soit valide. Au début, je ne pouvais pas comprendre l'utilisation de l'opérateur de décalage pour l'arbre de recherche binaire, car une comparaison est déjà exceptionnellement rapide, mais maintenant je me rends compte qu'elle serait utile pour précalculer cette valeur décalée si vous en aviez besoin. Vous ne l'utilisez pas, cependant. D'un autre côté, vous ne vous retrouvez pas avec de gros littéraux encodés à l'intérieur des instructions, alors peut-être que c'est une raison suffisante en soi.Modification de la solution de user434507. Modifié pour utiliser un tableau de caractères au lieu d'une chaîne C ++. Fonctionne un peu plus vite. Également déplacé le chèque pour 0 plus bas dans le code ... car cela ne se produit jamais pour mon cas particulier. Déplacez-le en arrière si c'est plus courant pour votre cas.
la source
Mismatch found: Generated: -9999999990 Reference: -999999999 Mismatch found: Generated: -9999999980 Reference: -999999998 Mismatch found: Generated: -9999999970 Reference: -999999997
Nous utilisons le code suivant (pour MSVC):
Modèle de tBitScanReverse:
char / wchar_t helpers:
Pouvoirs de 10 tables:
Impression réelle:
La dernière boucle peut être déroulée:
L'idée principale est la même que celle suggérée précédemment par @atlaste: https://stackoverflow.com/a/29039967/2204001
la source
Je viens de rencontrer cela à cause d'une activité récente; Je n'ai pas vraiment le temps d'ajouter des benchmarks, mais je voulais ajouter ce que j'ai écrit dans le passé lorsque j'ai besoin d'une conversion rapide d'entier en chaîne ...
https://github.com/CarloWood/ai-utils/blob/master/itoa.h
https://github.com/CarloWood/ai-utils/blob/master/itoa.cxx
L'astuce utilisée ici est que l'utilisateur doit fournir un std :: array suffisamment grand (sur sa pile) et que ce code écrit la chaîne à l'envers, en commençant par les unités, puis en renvoyant un pointeur dans le tableau avec un décalage à l'endroit où le résultat commence réellement.
Cela n'alloue ni ne déplace donc la mémoire, mais cela nécessite toujours une division et un module par chiffre de résultat (ce qui, à mon avis, est assez rapide car il s'agit simplement d'un code exécuté en interne sur le processeur; l'accès à la mémoire est généralement le problème à mon humble avis).
la source
Pourquoi personne n'utilise la fonction div de stdlib alors que le quotient et le reste sont nécessaires?
En utilisant le code source de Timo, je me suis retrouvé avec quelque chose comme ceci:
Ok, pour les int non signés, la fonction div ne peut pas être utilisée mais les unsigned's peuvent être gérés séparément.
J'ai défini la macro COPYPAIR comme suit pour tester les variations de copie des 2 caractères des paires digit_pairs (je n'ai trouvé aucun avantage évident de l'une de ces méthodes):
la source