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)...
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. –
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
@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