2010-11-20 6 views
4

Je n'ai jamais fait de profilage. Hier, j'ai programmé une classe ProfilingTimer avec un calendrier statique (une carte < std :: string, long long >) pour le stockage de temps.Pourquoi ma classe de profilage est-elle extrêmement lente?

Le constructeur stocke la tique de départ, et le destructor calulates le temps écoulé et ajoute à la carte:

ProfilingTimer::ProfilingTimer(std::string name) 
: mLocalNameLength(name.length()) 
{ 
sNestedName += name; 
sNestedName += " > "; 

mStartTick = Platform::GetTimerTicks(); 
} 

ProfilingTimer::~ProfilingTimer() 
{ 
long long totalTicks = Platform::GetTimerTicks() - mStartTick; 

sTimetable[sNestedName] += totalTicks; 

sNestedName.erase(sNestedName.length() - mLocalNameLength - 3); 
} 

Dans toutes les fonctions (ou {bloc}) que je veux le profil que je dois ajouter :

ProfilingTimer _ProfilingTimer("identifier"); 

Ce profil fonctionne très bien lorsque je compile une version de Visual C++ 2010 Professional. Mais quand je construis comme Debug, je reçois une énorme fps (de 63 à ~ 20).

Ce sont les chiffres que je reçois quand j'imprimer mon emploi du temps (Debug):

Update() > Tower::Update > : 2551 ms (84100m%) 
Update() > Tower::Update > Tower::Update1 > : 1313 ms (43284m%) 
Update() > Tower::Update > Tower::Update1 > Tower::FindNewTarget > : 6 ms (204m%) 
Update() > Tower::Update > Tower::Update1 > Tower::HasTargetInRange > : 5 ms (184m%) 
Update() > Tower::Update > Tower::Update2 > : 659 ms (21756m%) 
Update() > Tower::Update > Tower::Update2 > Tower::HasTargetInRange > : 5 ms (187m%) 

fois Update1 et Update2 la première et la deuxième moitié respectivement de mise à jour. Pourquoi n'atteignent-ils pas 84,1%?

encore ce 84% est un grand nombre - dans la version build je reçois cette sortie:

Update() > : 770 ms (1549m%) 
Update() > Tower::Update > : 722 ms (1452m%) 
Update() > Tower::Update > Tower::FindNewTarget > : 44 ms (89m%) 
Update() > Tower::Update > Tower::HasTargetInRange > : 92 ms (187m%) 

1,4% au lieu de 84,1%. C'est une énorme différence!

Quelqu'un sait pourquoi?

EDIT: Je suppose que la version est beaucoup plus rapide que Debug, mais pourquoi ce profilage prend-il du temps? Est-ce que std :: map est l'horreur ou est-ce que je fais quelque chose de très mal?

EDIT: Mise à jour du code. L'initialisation n'était pas nécessaire et stocke maintenant la longueur de mLocalName au lieu de la chaîne actuelle.

+0

Vous pouvez trouver pourquoi tout est lent en le mettant quelques fois en pause: http://stackoverflow.com/questions/375913/what-can-i-use-to-profile-c-code-in-linux/378024 # 378024 –

+0

Comment puis-je pauler avec VS? – Moberg

+1

C'est un petit bouton avec deux barres verticales (||). Obtenez votre code en cours d'exécution, et pendant qu'il fonctionne, appuyez sur le bouton. Si le temps est trop court pour faire une pause, entourez-le d'une longue boucle. Lorsque vous l'interrompez, examinez la pile d'appels. Cela vous dira exactement ce qui se passe. Si vous le voyez faire la même chose (même si vous définissez «même») deux fois ou plus, vous le faites, vous savez que cela prend beaucoup de temps. Si vous avez> 1 thread, regardez chacun d'eux, mais ignorez celui qui attend la saisie de l'utilisateur. –

Répondre

2

Il y a quelques problèmes de performances avec votre code.

  1. Vous construisez inutilement une chaîne std :: dans votre constructeur ProfilingTimer. Je recommande d'utiliser un const const * comme paramètre et d'utiliser une structure personnalisée Twine/Rope pour effectuer vos ajouts. Pourquoi mLocalName existe-t-il? Il suffit de se référer directement au name.
  2. Comme cela a été mentionné précédemment, ne profilez jamais en mode débogage. C'est au-delà de rien.
  3. En pratique, les cartes sont assez lentes. Je suggère d'utiliser une hashtable. Malheureusement, les implémentations sont spécifiques au compilateur. Si vous utilisez Microsoft, je crois qu'ils ont unordered_map disponible pour votre usage.
  4. Au lieu de faire sTimetable[sNestedName] = 0;, utilisez l'itérateur que vous avez déjà récupéré.

    Timetable::iterator loc = sTimetable.find(sNestedName); 
    if(loc == sTimetable.end()) 
        sTimetable[sNestedName] = 0; 
    

Addendum: Visual Studio est livré avec un profileur j'ai vérifié dernier. Pourquoi ne pas simplement l'utiliser?

+0

Existe-t-il une implémentation STL :: de cordes? Oui, j'ai lu quelque part sur ce profileur VS, mais je ne sais pas comment l'utiliser, et c'est amusant de faire votre propre code;) – Moberg

+0

Je suis d'accord que c'est amusant de faire ça, c'est comme ça que je le sais; Je ne pense pas que VS ait une implémentation native de Rope, mais il y en a une publique que je connais: LLVM. http://llvm.org/docs/doxygen/html/Twine_8h_source.html –

+0

Mon ami a essayé de changer toute la chaîne en caractère char *, et a laissé la carte ordonner son contenu en fonction des pointeurs (plus d'ordre alphabétique mais c'est OK). Mais c'est devenu plus lent! >. Moberg

2

Microsoft ajoute beaucoup de vérification de sécurité à leurs bibliothèques de conteneur en mode débogage. C'est bénéfique. Vous préférez attraper des exceptions hors limites et autres dans des fonctions telles que vector::operator[] au lieu de déchiffrer les corruptions de mémoire (vous recommanderiez toujours d'appeler vector::at.) Cependant, il y a aussi beaucoup d'autres choses qui entrent dans l'insertion de hooks de débogueur qui affectent votre code et c'est la performance.

2

L'utilisation de longues chaînes comme index dans un std::map n'est probablement pas le moyen le plus rapide de le faire. Les chaînes longues avec des débuts communs signifient que chaque fois que vous comparez deux de ces chaînes, il faut regarder beaucoup de caractères pour voir si les chaînes sont égales ou non. std::map est fondamentalement un arbre binaire et fait des comparaisons O (log (n)) sur chaque recherche/insertion. L'utilisation de touches plus courtes ou numériques devrait accélérer toutes les opérations de la carte. En outre, l'utilisation de std::unordered_map peut (ou non) améliorer la vitesse, en fonction du nombre d'éléments contenus dans la carte et du type de clé utilisé.

Dans la classe de profilage, le constructeur doit prendre le nom comme référence pour éviter de créer une copie inutile de cette chaîne:

ProfilingTimer::ProfilingTimer(const std::string &name) 

En général, en évitant des copies inutiles est une bonne idée. Par exemple, vous n'avez peut-être pas vraiment besoin du membre mLocalName et il peut suffire de simplement stocker la longueur de chaîne à la place.

Les fonctions de profilage sont probablement appelées très souvent, de sorte que les petits retards peuvent s'ajouter au cours de l'exécution du programme.

+0

Oui trop souvent. Au lieu d'avoir dans la fonction nous l'avons déplacé en dehors des boucles for appelant ces fonctions. Beaucoup mieux perfomance! :) Mais je pense toujours qu'il a besoin d'une certaine amélioration .. Merci aussi pour l'astuce sur le stockage de la longueur au lieu de la chaîne réelle mLocalName! – Moberg

Questions connexes