Windows XP SP3. Core 2 Duo 2.0 GHz. Je trouve que les performances boost :: lexical_cast sont extrêmement lentes. Je voulais trouver des moyens d'accélérer le code. Utiliser les optimisations/O2 sur visual c ++ 2008 et en comparant avec Java 1.6 et python 2.6.2 je vois les résultats suivants.
Casting entier:
c++:
std::string s ;
for(int i = 0; i < 10000000; ++i)
{
s = boost::lexical_cast<string>(i);
}
Java:
String s = new String();
for(int i = 0; i < 10000000; ++i)
{
s = new Integer(i).toString();
}
python:
for i in xrange(1,10000000):
s = str(i)
Les fois que je vois sont
c ++: 6700 millisecondes
Java: 1178 millisecondes
python: 6702 millisecondes
c ++ est aussi lent que python et 6 fois plus lent que Java.
Double casting:
c++:
std::string s ;
for(int i = 0; i < 10000000; ++i)
{
s = boost::lexical_cast<string>(d);
}
Java:
String s = new String();
for(int i = 0; i < 10000000; ++i)
{
double d = i*1.0;
s = new Double(d).toString();
}
python:
for i in xrange(1,10000000):
d = i*1.0
s = str(d)
Les fois que je vois sont
c ++: 56129 millisecondes
Java: 2852 millisecondes
python: 30780 millisecondes
Donc, pour les doubles, c ++ est en fait la moitié de la vitesse de python et 20 fois plus lent que la solution Java !!). Toutes les idées sur l'amélioration des performances boost :: lexical_cast Cela est-il dû à la mauvaise mise en œuvre du stringstream ou pouvons-nous nous attendre à une diminution générale de 10 fois les performances de l'utilisation des bibliothèques de boost.
rve a très justement commenté les performances de lexical_cast, fournissant un lien:
http://www.boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/performance.html
Je n'ai pas accès pour le moment à la version 1.49, mais je me souviens avoir accéléré mon code sur une ancienne version. Donc je suppose:
Juste pour ajouter des informations sur les excellentes réponses de Barry et Motti:
N'oubliez pas que Boost est écrit par les meilleurs développeurs C++ de cette planète et revu par les mêmes meilleurs développeurs. Si lexical_cast
Était si faux, quelqu'un aurait piraté la bibliothèque soit avec des critiques soit avec du code.
Je suppose que vous avez manqué le point de la valeur réelle de lexical_cast
...
En Java, vous transformez un entier en une chaîne Java. Vous remarquerez que je ne parle pas d'un tableau de caractères ou d'une chaîne définie par l'utilisateur. Vous noterez également: Je ne parle pas de votre entier défini par l'utilisateur. Je parle de strict Java Entier et strict Java String.
En Python, vous faites plus ou moins la même chose.
Comme indiqué dans d'autres articles, vous utilisez essentiellement les Java et Python équivalents de sprintf
(ou le moins standard itoa
).
En C++, vous utilisez un cast très puissant. Pas puissant dans le sens des performances de vitesse brutes (si vous voulez de la vitesse, peut-être que sprintf
serait mieux adapté), mais puissant dans le sens de l'extensibilité.
Si vous souhaitez comparer une méthode Java Integer.toString
, Vous devez la comparer avec les fonctionnalités C sprintf
ou C++ ostream
.
La solution de flux C++ serait 6 fois plus rapide (sur mon g ++) que lexical_cast
, Et bien moins extensible:
inline void toString(const int value, std::string & output)
{
// The largest 32-bit integer is 4294967295, that is 10 chars
// On the safe side, add 1 for sign, and 1 for trailing zero
char buffer[12] ;
sprintf(buffer, "%i", value) ;
output = buffer ;
}
La solution C sprintf
serait 8 fois plus rapide (sur mon g ++) que lexical_cast
Mais beaucoup moins sûre:
inline void toString(const int value, char * output)
{
sprintf(output, "%i", value) ;
}
Les deux solutions sont aussi rapides ou plus rapides que votre Java (selon vos données).
Si vous voulez comparer un C++ lexical_cast
, Alors vous devez le comparer avec ce Java pseudo code:
Source s ;
Target t = Target.fromString(Source(s).toString()) ;
La source et la cible sont de n'importe quel type, y compris les types intégrés comme boolean
ou int
, ce qui est possible en C++ à cause des modèles.
Non, mais cela a un coût bien connu: lorsqu'elles sont écrites par le même codeur, les solutions générales à des problèmes spécifiques sont généralement plus lentes que les solutions spécifiques écrites pour leurs problèmes spécifiques.
Dans le cas actuel, dans un point de vue naïf, lexical_cast
Utilisera les fonctionnalités de flux pour convertir d'un type A
en un flux de chaîne, puis de ce flux de chaîne en un type B
.
Cela signifie que tant que votre objet peut être sorti dans un flux et entré à partir d'un flux, vous pourrez utiliser lexical_cast
Dessus, sans toucher à une seule ligne de code.
lexical_cast
?Les principales utilisations de la coulée lexicale sont:
Le point 2 est très très important ici, car cela signifie que nous avons une et une seule interface/fonction pour convertir une valeur d'un type en une valeur égale ou similaire d'un autre type.
C'est le vrai point que vous avez manqué, et c'est le point qui coûte en termes de performances.
Si vous voulez des performances de vitesse brutes, souvenez-vous que vous traitez avec C++ et que vous disposez de nombreuses fonctionnalités pour gérer efficacement la conversion, tout en conservant la fonctionnalité de facilité d'utilisation lexical_cast
.
Il m'a fallu quelques minutes pour regarder la source lexical_cast et trouver une solution viable. Ajoutez à votre code C++ le code suivant:
#ifdef SPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT
namespace boost
{
template<>
std::string lexical_cast<std::string, int>(const int &arg)
{
// The largest 32-bit integer is 4294967295, that is 10 chars
// On the safe side, add 1 for sign, and 1 for trailing zero
char buffer[12] ;
sprintf(buffer, "%i", arg) ;
return buffer ;
}
}
#endif
En activant cette spécialisation de lexical_cast pour les chaînes et les entiers (en définissant la macro SPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT
), Mon code est allé 5 fois plus vite sur mon compilateur g ++, ce qui signifie, selon vos données, que ses performances devraient être similaires à celles de Java.
Et cela m'a pris 10 minutes pour regarder le code boost et écrire une version 32 bits à distance efficace et correcte. Et avec un peu de travail, cela pourrait probablement aller plus vite et plus en sécurité (si nous avions un accès direct en écriture au tampon interne std::string
, Nous pourrions éviter un tampon externe temporaire, par exemple).
Vous pourriez vous spécialiser lexical_cast
pour les types int
et double
. Utilisez strtod
et strtol
dans vos spécialisations.
namespace boost {
template<>
inline int lexical_cast(const std::string& arg)
{
char* stop;
int res = strtol( arg.c_str(), &stop, 10 );
if ( *stop != 0 ) throw_exception(bad_lexical_cast(typeid(int), typeid(std::string)));
return res;
}
template<>
inline std::string lexical_cast(const int& arg)
{
char buffer[65]; // large enough for arg < 2^200
ltoa( arg, buffer, 10 );
return std::string( buffer ); // RVO will take place here
}
}//namespace boost
int main(int argc, char* argv[])
{
std::string str = "22"; // SOME STRING
int int_str = boost::lexical_cast<int>( str );
std::string str2 = boost::lexical_cast<std::string>( str_int );
return 0;
}
Cette variante sera plus rapide que l'utilisation de l'implémentation par défaut, car dans l'implémentation par défaut, il y a construction d'objets de flux lourds. Et il devrait être un peu plus rapide que printf
, car printf
devrait analyser la chaîne de format.
lexical_cast
Est plus général que le code spécifique que vous utilisez dans Java et Python. Il n'est pas surprenant qu'une approche générale qui fonctionne dans de nombreux scénarios (la distribution lexicale est un peu plus que streaming puis retour vers et depuis un flux temporaire) finit par être plus lent que des routines spécifiques.
(BTW, vous pouvez obtenir de meilleures performances avec Java en utilisant la version statique, Integer.toString(int)
. [1])
Enfin, l'analyse et l'analyse des chaînes ne sont généralement pas très sensibles aux performances, sauf si l'on écrit un compilateur, auquel cas lexical_cast
Est probablement trop polyvalent, et les entiers, etc. seront calculés à chaque analyse.
[1] Le commentateur "stepancheg" doutait de mon indice que la version statique pourrait donner de meilleures performances. Voici la source que j'ai utilisée:
public class Test
{
static int instanceCall(int i)
{
String s = new Integer(i).toString();
return s == null ? 0 : 1;
}
static int staticCall(int i)
{
String s = Integer.toString(i);
return s == null ? 0 : 1;
}
public static void main(String[] args)
{
// count used to avoid dead code elimination
int count = 0;
// *** instance
// Warmup calls
for (int i = 0; i < 100; ++i)
count += instanceCall(i);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; ++i)
count += instanceCall(i);
long finish = System.currentTimeMillis();
System.out.printf("10MM Time taken: %d ms\n", finish - start);
// *** static
// Warmup calls
for (int i = 0; i < 100; ++i)
count += staticCall(i);
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; ++i)
count += staticCall(i);
finish = System.currentTimeMillis();
System.out.printf("10MM Time taken: %d ms\n", finish - start);
if (count == 42)
System.out.println("bad result"); // prevent elimination of count
}
}
Les runtimes, utilisant JDK 1.6.0-14, VM serveur:
10MM Time taken: 688 ms
10MM Time taken: 547 ms
Et dans la machine virtuelle cliente:
10MM Time taken: 687 ms
10MM Time taken: 610 ms
Même si théoriquement, l'analyse d'échappement peut permettre l'allocation sur la pile, et l'inline peut introduire tout le code (y compris la copie) dans la méthode locale, permettant l'élimination de la copie redondante, une telle analyse peut prendre beaucoup de temps et entraîner un peu de l'espace de code, qui a d'autres coûts dans le cache de code qui ne se justifient pas dans le vrai code, par opposition aux microbenchmarks comme on le voit ici.
Ce que fait le cast lexical dans votre code peut être simplifié comme suit:
string Cast( int i ) {
ostringstream os;
os << i;
return os.str();
}
Il se passe malheureusement beaucoup de choses à chaque fois que vous appelez Cast ():
Thn dans votre propre code:
s = Cast( i );
l'affectation implique d'autres allocations et des désallocations sont effectuées. Vous pourrez peut-être réduire légèrement cela en utilisant:
string s = Cast( i );
au lieu.
Cependant, si les performances vous importent vraiment, vous devriez envisager d'utiliser un mécanisme différent. Vous pouvez écrire votre propre version de Cast () qui (par exemple) crée un flux de chaînes statique. Une telle version ne serait pas sécurisée pour les threads, mais cela pourrait ne pas avoir d'importance pour vos besoins spécifiques.
Pour résumer, lexical_cast est une fonctionnalité pratique et utile, mais cette commodité s'accompagne (comme elle doit toujours) de compromis dans d'autres domaines.
Malheureusement, je n'ai pas encore assez de représentants pour commenter ...
lexical_cast
N'est pas principalement lent car il est générique (les recherches de modèles se produisent au moment de la compilation, donc les appels de fonction virtuelle ou d'autres recherches/déréférences ne sont pas nécessaires). lexical_cast
Est, à mon avis, lent, car il s'appuie sur les iostreams C++, qui sont principalement destinés aux opérations de streaming et non aux conversions uniques, et parce que lexical_cast
Doit rechercher et convertir les signaux d'erreur iostream. Donc:
sprintf
, bien que sprintf
ne le fasse pas en toute sécurité gérer les dépassements de tampon)lexical_cast
Doit vérifier les erreurs stringstream
(ss.fail()
) afin de lever des exceptions sur les échecs de conversionlexical_cast
Est sympa parce que les exceptions (IMO) permettent de piéger toutes les erreurs sans effort supplémentaire et parce qu'il a un prototype uniforme. Personnellement, je ne vois pas pourquoi l'une de ces propriétés nécessite un fonctionnement lent (lorsqu'aucune erreur de conversion ne se produit), bien que je ne connaisse pas de telles fonctions C++ qui sont rapides (peut-être Spirit ou boost :: xpressive?).
Edit: Je viens de trouver un message mentionnant l'utilisation de BOOST_LEXICAL_CAST_ASSUME_C_LOCALE
Pour activer une optimisation "itoa": http://old.nabble.com/lexical_cast-optimization-td20817583.html . Il y a aussi un lien article avec un peu plus de détails.
lexical_cast peut ou peut ne pas être aussi lent par rapport à Java et Python comme vos bencharks l'indiquent parce que vos mesures de référence peuvent avoir un problème subtil. Toutes les allocations/désallocations d'espace de travail effectuées par lexical cast ou les méthodes iostream qu'il utilise sont mesurées par vos benchmarks car C++ ne diffère pas ces opérations. Cependant, dans le cas de Java et Python , les désallocations associées peuvent en fait avoir simplement été reportées à un futur cycle de collecte des ordures et manquées par les mesures de référence (sauf si un cycle GC se produit par hasard pendant que la référence est en cours et dans ce cas, vous mesureriez trop) . Il est donc difficile de savoir avec certitude sans examiner les spécificités des implémentations Java et Python combien de "coût" doit être attribué à la charge différée du GC qui peut (ou peut ne pas) être finalement imposé.
Ce type de problème peut évidemment s'appliquer à de nombreux autres benchmarks de langage C++ vs garbage collection.
si la vitesse est un problème, ou si vous êtes simplement intéressé par la rapidité avec laquelle ces transtypages peuvent être avec C++, il y a un thread intéressé à ce sujet.
Boost.Spirit 2.1 (qui doit être publié avec Boost 1.40) semble être très rapide, voire plus rapide que les équivalents C (strtol (), atoi () etc.).
J'utilise cette solution très rapide pour les types de POD ...
namespace DATATYPES {
typedef std::string TString;
typedef char* TCString;
typedef double TDouble;
typedef long THuge;
typedef unsigned long TUHuge;
};
namespace boost {
template<typename TYPE>
inline const DATATYPES::TString lexical_castNumericToString(
const TYPE& arg,
const DATATYPES::TCString fmt) {
enum { MAX_SIZE = ( std::numeric_limits<TYPE>::digits10 + 1 ) // sign
+ 1 }; // null
char buffer[MAX_SIZE] = { 0 };
if (sprintf(buffer, fmt, arg) < 0) {
throw_exception(bad_lexical_cast(typeid(TYPE),
typeid(DATATYPES::TString)));
}
return ( DATATYPES::TString(buffer) );
}
template<typename TYPE>
inline const TYPE lexical_castStringToNumeric(const DATATYPES::TString& arg) {
DATATYPES::TCString end = 0;
DATATYPES::TDouble result = std::strtod(arg.c_str(), &end);
if (not end or *end not_eq 0) {
throw_exception(bad_lexical_cast(typeid(DATATYPES::TString),
typeid(TYPE)));
}
return TYPE(result);
}
template<>
inline DATATYPES::THuge lexical_cast(const DATATYPES::TString& arg) {
return (lexical_castStringToNumeric<DATATYPES::THuge>(arg));
}
template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::THuge& arg) {
return (lexical_castNumericToString<DATATYPES::THuge>(arg,"%li"));
}
template<>
inline DATATYPES::TUHuge lexical_cast(const DATATYPES::TString& arg) {
return (lexical_castStringToNumeric<DATATYPES::TUHuge>(arg));
}
template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::TUHuge& arg) {
return (lexical_castNumericToString<DATATYPES::TUHuge>(arg,"%lu"));
}
template<>
inline DATATYPES::TDouble lexical_cast(const DATATYPES::TString& arg) {
return (lexical_castStringToNumeric<DATATYPES::TDouble>(arg));
}
template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::TDouble& arg) {
return (lexical_castNumericToString<DATATYPES::TDouble>(arg,"%f"));
}
} // end namespace boost