2010-06-15 6 views
7

ich eine Profilierung Problem haben - man stelle ich den folgenden Code haben ...Profilieren Hilfe erforderlich

void main() 
{ 
    well_written_function(); 
    badly_written_function(); 
} 
void well_written_function() 
{ 
    for (a small number) 
    { 
     highly_optimised_subroutine(); 
    } 
} 
void badly_written_function() 
{ 
    for (a wastefully and unnecessarily large number) 
    { 
     highly_optimised_subroutine(); 
    } 
} 
void highly_optimised_subroutine() 
{ 
    // lots of code 
} 

Wenn ich dies unter VTune (oder einem anderen Profilometer) führen Sie es sehr schwer ist, dass irgendetwas falsch zu erkennen. Alle Hotspots erscheinen in dem Bereich "// viele Codes", der bereits optimiert ist. Die Funktion wrong_written_function() wird in keiner Weise hervorgehoben, obwohl sie die Ursache aller Probleme ist.

Gibt es eine Funktion von vtune, die mir hilft, das Problem zu finden?

Gibt es eine Art Modus, in dem ich die Zeit finden kann, die von schlecht_schrieben_funktion() und all seinen Unterfunktionen?

+3

Das ist normalerweise bekannt als ein "Callgraph-Profil", und ich bin ziemlich sicher, dass Visual Studio das tun wird. Obwohl meine Erinnerung ein wenig verschwommen ist, da es seit mehreren Jahren keine Windows-Entwicklung mehr gibt. –

+1

Ich bin ziemlich sicher, vtune ermöglicht eine Aufschlüsselung, wo Sie nach der Gesamtzeit sehen und sortieren können, einschließlich der Funktionen, die es aufruft. Damit dies jedoch nützlich ist, benötigen Sie eine vernünftige Intuition, wie viel Zeit in welcher Funktion verbracht werden sollte. – torak

+0

@torak: Können Sie sich den Namen des Features merken? - Ich habe eine grobe Vorstellung davon, wie lange bestimmte Funktionen dauern sollten, und ich bin mir sicher, dass ein solcher Zusammenbruch wirklich sehr nützlich wäre. – Mick

Antwort

1

Das ist normalerweise bekannt als ein "Callgraph-Profil", und ich bin ziemlich sicher, dass Visual Studio das tun wird.

1

Rolling your own sehr einfach Profiler ist nicht so schwer. Legen Sie in main():

int main() 
{ 
    profileCpuUsage(1);     // start timer #1 
    well_written_function(); 
    profileCpuUsage(2);     // stop timer #1, and start timer #2 
    badly_written_function(); 
    profileCpuUsage(-1);    // print stats for timers #1 and #2 
    return 0; 
} 

wo:

#define NUMBER(a) ((int)(sizeof(a)/sizeof(a)[0])) 

void profileCpuUsage(int slice) 
{ 
    static struct { 
     int iterations; 
     double elapsedTime; 
    } slices[30];        // 0 is a don't care slice 

    if (slice < 0) {       // -1 = print 
     if (slices[0].iterations) 
      for (slice = 1; slice < NUMBER(slices); slice++) 
       printf("Slice %2d Iterations %7d Seconds %7.3f\n", slice, 
        slices[slice].iterations, slices[slice].elapsedTime); 
    } 
    else { 
     static int i;       // = previous slice 
     static double t;      // = previous t1 
     const double t1 = realElapsedTime(); // see below for definition 
     assert (slice < NUMBER(slices)); 
     slices[i].iterations += 1; 
     slices[i].elapsedTime += t1 - t;  // i = 0 first time through 
     i = slice; 
     t = t1; 
    } 
} 

Jetzt zugegebenermaßen in Ihrem einfaches Beispiel mit dieser profileCpuUsage() hinzufügen, nicht viel Nutzen. Und es hat den Nachteil, Sie zu manuell Instrument Ihren Code durch Aufruf von profileCpuUsage() an geeigneten Standorten.

aber Vorteile sind:

  • Sie können Zeit jedes Codefragment, nicht nur Verfahren.
  • Es ist schnell zu hinzufügen und zu löschen, wie Sie eine binäre Suche zu finden und/oder entfernen Code Hotspots.
  • Es konzentriert sich nur auf den Code, der Sie interessiert.
  • Portable!
  • KISS

Eine knifflige nicht tragbare Sache ist die Funktion realElapsedTime() zu definieren, so dass es genug Granularität bietet gültige Zeiten zu erhalten. Dies funktioniert in der Regel für mich (den Windows-API unter Cygwin):

#include <windows.h> 
double realElapsedTime(void) // <-- granularity about 50 microsec on test machines 
{ 
    static LARGE_INTEGER freq, start; 
    LARGE_INTEGER count; 
    if (!QueryPerformanceCounter(&count)) 
     assert(0 && "QueryPerformanceCounter"); 
    if (!freq.QuadPart) {  // one time initialization 
     if (!QueryPerformanceFrequency(&freq)) 
      assert(0 && "QueryPerformanceFrequency"); 
     start = count; 
    } 
    return (double)(count.QuadPart - start.QuadPart)/freq.QuadPart; 
} 

Für gerade Unix gibt es das üblich ist:

double realElapsedTime(void)      // returns 0 first time called 
{ 
    static struct timeval t0; 
    struct timeval tv; 
    gettimeofday(&tv, 0); 
    if (!t0.tv_sec) 
     t0 = tv; 
    return tv.tv_sec - t0.tv_sec + (tv.tv_usec - t0.tv_usec)/1000000.; 
} 

realElapsedTime() Wandtaktzeit ergibt, keine Zeit verarbeiten, die ist normalerweise was ich will.

Es gibt auch andere weniger portable Methoden, um eine feinere Granularität mit RDTSC zu erreichen; siehe zum Beispiel http://en.wikipedia.org/wiki/Time_Stamp_Counter, und seine Links, aber ich habe diese nicht versucht.

Edit: Ravens ist sehr nette Antwort scheint von meinem nicht allzu unähnlich zu sein. Und seine Antwort verwendet nette beschreibende Strings, anstatt nur hässliche Zahlen, mit denen ich oft frustriert war.Aber das kann mit nur etwa einem Dutzend zusätzlichen Zeilen behoben werden (aber das fast verdoppelt die Zeilenanzahl!).

Beachten Sie, dass wir jede Verwendung von malloc() vermeiden wollen, und ich bin sogar ein bisschen zweifelhaft über strcmp(). So wird die Anzahl der Scheiben nie erhöht. Und Hash-Kollisionen werden einfach markiert und eher aufgelöst: Der menschliche Profiler kann das beheben, indem er manuell die Anzahl der Schichten von 30 erhöht oder die Beschreibung ändert. Ungeprüfte

static unsigned gethash(const char *str) // "djb2", for example 
{ 
    unsigned c, hash = 5381; 
    while ((c = *str++)) 
     hash = ((hash << 5) + hash) + c; // hash * 33 + c 
    return hash; 
} 

void profileCpuUsage(const char *description) 
{ 
    static struct { 
     int iterations; 
     double elapsedTime; 
     char description[20];    // added! 
    } slices[30]; 

    if (!description) { 
     // print stats, but using description, mostly unchanged... 
    } 
    else { 
     const int slice = gethash(description) % NUMBER(slices); 
     if (!slices[slice].description[0]) { // if new slice 
      assert(strlen(description) < sizeof slices[slice].description); 
      strcpy(slices[slice].description, description); 
     } 
     else if (!!strcmp(slices[slice].description, description)) { 
      strcpy(slices[slice].description, "!!hash conflict!!"); 
     } 
     // remainder unchanged... 
    } 
} 

Und ein weiterer Punkt, dass in der Regel sollten Sie diese Profilierung für Release-Versionen deaktivieren; Dies gilt auch für die Antwort von Ravenspoint. Dies kann durch den Trick der Verwendung eines bösen Makro definieren weggetan werden:

#define profileCpuUsage(foo)    // = nothing 

Wenn dies geschehen ist, werden Sie natürlich müssen Klammern der Definition hinzuzufügen, um das Deaktivieren Makro zu deaktivieren:

void (profileCpuUsage)(const char *description)... 
1

Darf ich meinen eigenen Open-Source-Profiler raven :: set :: cRunWatch vorschlagen? Es ist für genau dieses Problem konzipiert und funktioniert unter Windows mit Visual Studio 2008 Standard Edition, sodass Sie nicht für die Version bezahlen müssen, in der der Profiler enthalten ist.

ich Ihren Code genommen haben, ist es neu geordnet leicht, so dass es ohne Vorwärtsdeklarationen erstellt und die notwendigen Anrufe hinzugefügt produziert badly_written_function Dies zeigt an cRunWatch

// RunWatchDemo.cpp : Defines the entry point for the console application. 
// 

#include "stdafx.h" 

void highly_optimised_subroutine() 
{ 
    raven::set::cRunWatch runwatch("highly_optimised_subroutine"); 
    Sleep(2); 
} 


void badly_written_function() 
{ 
    raven::set::cRunWatch runwatch("badly_written_function"); 
    for (int k = 1; k < 1000; k++) 
    { 
     highly_optimised_subroutine(); 
    } 
} 

void well_written_function() 
{ 
    raven::set::cRunWatch runwatch("well_written_function"); 
    for (int k = 1; k < 10; k++) 
    { 
     highly_optimised_subroutine(); 
    } 
} 


int _tmain(int argc, _TCHAR* argv[]) 
{ 
raven::set::cRunWatch::Start(); 

    well_written_function(); 
    badly_written_function(); 

raven::set::cRunWatch::Report(); 

    return 0; 
} 

Wenn er gestartet wird dies die Ausgabe

raven::set::cRunWatch code timing profile 
        Scope Calls  Mean (secs)  Total 
highly_optimised_subroutine  1008 0.002921  2.944146 
    badly_written_function  1  2.926662  2.926662 
    well_written_function  1  0.026239  0.026239 

sei der sehr nahe Zweitnutzer und damit der Täter.

Sie können cRunWatch erhalten von here Sie den Beispielcode in der Bedienungsanleitung :-)

+0

+1 Aber erfordert BOOST. –

+0

Es erfordert Boost. Fast jedes Programm, das ich schreibe, verwendet Boost auf die eine oder andere Weise. Boost ist kostenlos und unglaublich nützlich. – ravenspoint

+0

Aber ich arbeite an 15 Jahre alten HPUX 10.20 Plattformen. Frag nicht :( –

0

Im Allgemeinen erkennen, dies ist etwas, wo Sie Funktion der Gesamtzeit beobachten wollen, wie sich selbst Zeit gegenüber, um sicherzustellen, dass Sie betrachten die Zeit, die die Zeit der aufgerufenen Funktionen enthält.

In VTune würde ich empfehlen, die Registerkarte Top-down dafür zu verwenden. Oder, noch besser, und wenn Sie ein aktuelles Update verwenden, probieren Sie die neue experimentelle Caller-Calle-Ansicht aus. Details dazu finden Sie hier - http://software.intel.com/en-us/forums/topic/376210. Es erhält eine flache Liste von Funktionen mit ihren Gesamtzeiten, so dass Sie sehen können, welche die zeitaufwendigsten Teilbäume in Ihrem Programm sind.

Verwandte Themen