2008-11-12 12 views
9

J'utilise un module de consignation qui peut activer/désactiver le reporting à l'exécution. Les appels vont généralement quelque chose comme:Optimisation du compilateur C++ des arguments passés

WARN(
    "Danger Will Robinson! There are " 
    + boost::lexical_cast<string>(minutes) 
    + " minutes of oxygen left!" 
); 

J'utilise une fonction en ligne pour WARN, mais je suis curieux de savoir à quel point l'optimisation qui se passe dans les coulisses - évaluation des arguments tout au long de l'ensemble du programme serait être coûteux. La fonction WARN va quelque chose comme ceci:

bool WARNINGS_ENABLED = false; 
inline void WARN(const string &message) { 
    if (!WARNINGS_ENABLED) { 
     return; 
    } 
    // ... 
} 

Étant donné que la construction de l'argument de chaîne n'a pas d'effets secondaires, sera le compilateur d'optimiser dehors? Un certain niveau d'optimisation est-il requis (-Ox dans g++ pour certains x)?

Répondre

12

Si vous devez être en mesure d'activer et de désactiver sélectivement les avertissements au moment de l'exécution, le compilateur peut et non être en mesure d'optimiser l'appel.

Qu'est-ce que vous avez besoin est de renommer votre fonction -WARN2 et ajouter une chose macro comme:

#define WARN(s) do {if (WARNINGS_ENABLED) WARN2(s);} while (false) 

Cela permettra d'éviter l'évaluation de s au moment de l'exécution, sauf si vous avez activé l'alerte. Le truc do-while est une astuce qui lui permet d'être utilisé n'importe où dans le code (instruction naked, instruction dans un if-block contreventement, instruction dans un if-block non branché, instructions while et non-braced et ainsi de suite).

+0

Cela devrait fonctionner, bien fait. –

+0

Yep - une situation claire où les macros doivent être utilisées sur des fonctions en ligne. – cdleary

+0

+1 mais cela devrait être: #define WARN (s) do {if (WARNINGS_ENABLED) WARN2 (s); } while (false) –

1

Je suppose qu'il a seulement une chance de l'optimiser s'il peut prouver qu'il n'y a pas d'effets secondaires (ce qui peut être difficile pour le compilateur à faire pour un appel de fonction coûteux). Je ne suis pas un expert boost, mais je suppose qu'il existe un moyen de construire un lambda qui ne sera évalué que pour générer la chaîne si WARNINGS_ENABLED est vrai. Quelque chose comme ...

inline void warnFunc(some_boost_lambda &message_generator) { 
    if (WARNINGS_ENABLED) { 
    cerr << message_generator() << endl; 
    } 
} 

#define WARN(msg) warnFunc(...insert boost magic here to turn msg into a lambda...) 
+0

Une idée intéressante! – cdleary

+0

ouais votre idée fonctionne très bien. juste testé avec boost :: lambda (pas d'expert non plus, mais c'était simple) –

+0

ouais ton idée marche super bien. juste testé avec boost :: lambda (pas d'expert non plus, mais c'était simple). Voici l'exemple avec la sortie: http://codepad.org/PmUh7AHj –

6

Vous pouvez vérifier ce que GCC/G ++ faire en utilisant l'option S. Cela affichera le code avant qu'il ne soit assemblé - voir gcc(1). GCC et G ++ se comportent plus ou moins de la même manière dans ce cas.

Donc j'ai traduit le code en C pour faire quelques tests supplémentaires:

char WARNINGS_ENABLED = 0; 

inline void WARN(const char* message) { 
    if (!WARNINGS_ENABLED) { 
     return; 
    } 
    puts(message); 
} 

int main() { 
    WARN("foo"); 
    return 0; 
} 

run gcc -S -O3 file.c et regardez dans le fichier de sortie 'file.s'
Vous allez voir que GCC n'a rien supprimé!

Ce n'est pas ce que vous avez demandé, mais pour donner au compilateur la possibilité d'optimiser ce code, vous auriez à faire WARNINGS_ENABLED constante. Une alternative est de le rendre statique et de ne pas modifier la valeur dans ce fichier. Mais: rendant statique a l'effet de côté que le symbole n'est pas exporté.

static const char WARNINGS_ENABLED = 0; 

inline void WARN(const char* message) { 
    if (!WARNINGS_ENABLED) { 
     return; 
    } 
    puts(message); 
} 

int main() { 
    WARN("foo"); 
    return 0; 
} 

GCC nettoie ensuite complètement le code. Vous ne pouvez pas simplement définir le tout en utilisant le préprocesseur

+0

Très belle réponse! Malheureusement, le comportement de journalisation doit pouvoir être manipulé lors de l'exécution. – cdleary

0

void inline void LogWarning(const string &message) 
{ 
    //Warning 
} 

#ifdef WARNINGS_ENABLED 
#define WARN(a) LogWarning(a) 
#else 
#define WARN(a) 
#endif 

C'est exactement comme cela que fonctionne la macro ASSERT(). Tout le code entre parenthèses dans WARN ne passe même pas par le préprocesseur au compilateur. Cela signifie que vous pouvez faire d'autres choses comme

#ifdef WARNINGS_ENABLED 
// Extra setup for warning 
#endif 
//.... 
WARN(uses setup variables) 

Et il compilera les deux façons.Pour ce qui est de faire réaliser à l'optimiseur qu'il n'y a pas d'effets secondaires dans les parenthèses, vous pouvez y mettre des instructions assez complexes (c'est-à-dire une manipulation de haut niveau) difficiles à prouver.

+0

Il doit être capable de basculer vers l'exécution, mais ce serait certainement une solution de compilation réalisable. – cdleary

+0

Donc votre question est: Étant donné une fonction X (params) {if (b) {quit}; ...} le compilateur retarde-t-il le calcul des params jusqu'après le if? Ce serait impressionnant. Comme la branche est dynamique, il est peu probable que le compilateur puisse l'optimiser. –

+0

Oui, c'est ce que je demande! Sauf que le compilateur pourrait se débarrasser de l'appel et intégrer le contenu de votre '...'. Comme d'autres personnes l'ont souligné, le compilateur devrait être capable de prouver qu'il n'y a pas d'effets secondaires externes en passant les arguments, ce qui semble assez difficile. – cdleary

1

Non, le compilateur devrait pas optimiser le code dans tous les cas sauf si le global WARNING_ENABLED est déclaré const. Par ailleurs, si WARN est une fonction inline, vous paierez toujours le prix de la construction du message (ce qui est très inefficace dans votre exemple avec lexical_cast et operator + sur les chaînes), même s'il est désactivé.

Voici quelques préfixes efficaces (minime (proche de zéro avec un prédicteur de branche) lorsque l'exécution est désactivée) logging macros prenant en charge la journalisation du style de flux et de fonction.

Questions connexes