Wie kann ich C-Code einfach Benchmarken?

Lesezeit: 8 Minuten

Mikes Benutzeravatar
Mike

Gibt es eine einfache Bibliothek, um die Zeit zu messen, die zum Ausführen eines Teils des C-Codes benötigt wird? Was ich will, ist so etwas wie:

int main(){
    benchmarkBegin(0);
    //Do work
    double elapsedMS = benchmarkEnd(0);

    benchmarkBegin(1)
    //Do some more work
    double elapsedMS2 = benchmarkEnd(1);

    double speedup = benchmarkSpeedup(elapsedMS, elapsedMS2); //Calculates relative speedup
}

Es wäre auch großartig, wenn Sie mit der Bibliothek viele Läufe durchführen, sie mitteln und die Varianz im Timing berechnen könnten!

  • Gute Frage, das hat mir sehr geholfen.

    – Nick Knowlson

    22. Juli 2012 um 0:51 Uhr

  • Alternativen zum programminternen Timing: stackoverflow.com/questions/7456146/…

    – Ciro Santilli OurBigBook.com

    29. Juni 2015 um 14:01 Uhr

  • Große geschlossene Linux-Frage: stackoverflow.com/questions/375913/…

    – Ciro Santilli OurBigBook.com

    29. Juni 2015 um 14:03 Uhr

  • Wenn Sie ähnliche Arbeiten zweimal im selben Programm ausführen, kann der Compiler möglicherweise zwischen ihnen optimieren. Das Erstellen mehrerer ausführbarer Dateien, bei denen jeder Mikrobench eine einzelne Implementierungsstrategie markiert, ist sicherer (aber umständlicher). Da die gesamte Laufzeit eines Programms der Benchmark ist, ist es einfach, die Ergebnisse von Leistungszählern zu vergleichen perf statund bedeutet, dass Sie externe Timing-Sachen wie verwenden können time ./a.out Anstatt Timing-Code in Ihr C aufzunehmen. Mit Timing-Code im Programm können Sie jedoch Timing-Initialisierungscode vermeiden. Und mehrere Ergebnisse aus einem sind einfacher.

    – Peter Cordes

    12. Juni 2016 um 10:35 Uhr


Benutzeravatar von Gaurav
Gaurav

Verwenden Sie die Funktion clock() definiert in time.h:

startTime = (float)clock()/CLOCKS_PER_SEC;

/* Do work */

endTime = (float)clock()/CLOCKS_PER_SEC;

timeElapsed = endTime - startTime;

  • Dies sollte die akzeptierte Antwort anstelle der Windows-spezifischen sein!

    – Simon

    20. Dezember 2017 um 21:58 Uhr

  • clock() gibt die CPU-Zeit statt der Wanduhrzeit zurück, was Sie überraschen kann, wenn beim Benchmarking mehrere Threads Code ausführen.

    – Neevek

    29. April 2018 um 8:35 Uhr

  • @neevek Aber es führt zu korrekten Ergebnissen, wenn Sie nur einen Thread haben, da Ihre Systemlast das Benchmark-Ergebnis beeinflusst, wenn Sie die Uhrzeit verwenden. Wenn Ihr System sehr stark mit Hintergrundaufgaben beschäftigt ist, erhalten Sie schlechtere Benchmark-Ergebnisse als ohne Verwendung der Walllclock-Zeit, aber Sie erhalten die gleichen Ergebnisse mit CPU-Zeit.

    – Mecki

    1. September 2020 um 17:16 Uhr

  • Was ist mit der Präzision? Indem wir den Benchmark-Code N-mal ausführen und die gemessene Zeit durch N dividieren, erhöhen wir die Genauigkeit. Wie bestimmen wir N und die Genauigkeit?

    – Chike

    12. Dezember 2021 um 8:53 Uhr

Joes Benutzeravatar
Jo

Im Grunde ist alles, was Sie wollen, ein hochauflösender Timer. Die verstrichene Zeit ist natürlich nur eine Zeitdifferenz und die Beschleunigung wird berechnet, indem die Zeiten für jede Aufgabe geteilt werden. Ich habe den Code für einen hochauflösenden Timer eingefügt, der zumindest unter Windows und Unix funktionieren sollte.

#ifdef WIN32

#include <windows.h>
double get_time()
{
    LARGE_INTEGER t, f;
    QueryPerformanceCounter(&t);
    QueryPerformanceFrequency(&f);
    return (double)t.QuadPart/(double)f.QuadPart;
}

#else

#include <sys/time.h>
#include <sys/resource.h>

double get_time()
{
    struct timeval t;
    struct timezone tzp;
    gettimeofday(&t, &tzp);
    return t.tv_sec + t.tv_usec*1e-6;
}

#endif

  • Wallclock-Zeit (wie zurückgegeben von gettimeofday) möglicherweise nicht so nützlich – clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ...) wird dort oft gesucht.

    – Café

    28. Februar 2010 um 11:13 Uhr

  • @caf: Ein Programm, das sehr wenig CPU-Zeit verbraucht, aber viel Zeit damit verbringt, E/A zu blockieren oder auf asynchrone E/A zu warten, kann von Benutzern immer noch als langsam empfunden werden. Sowohl die CPU-Zeit als auch die Wanduhrzeit sind wichtig.

    – bk1e

    1. März 2010 um 2:49 Uhr

  • Ja, deshalb habe ich meinen Kommentar mit den Wieselwörtern “kann” und “oft” relativiert 😉 Übrigens, wenn es um die Uhrzeit geht ist dann gewünscht clock_gettime(CLOCK_MONOTONIC, ...) ist eine bessere Option, weil anders gettimeofday es wird nicht durch Änderungen an der Systemuhr während des Zeitintervalls beeinflusst.

    – Café

    1. März 2010 um 2:53 Uhr

  • In meiner typischen Verwendung kümmere ich mich nur um die Uhrzeit, weil ich ressourcenintensive Dinge mache. Ich bin mir nicht sicher, wie clock_gettime mit Multi-Threading funktioniert, aber das scheint ein Bereich zu sein, in dem die Wanduhrzeit das einzige genaue Maß ist.

    – Jo

    2. März 2010 um 3:39 Uhr

  • Übrigens sollte QueryPerformanceFrequency nicht wirklich jedes Mal aufgerufen werden.

    – Jo

    10. September 2014 um 21:42 Uhr

Benutzeravatar von Antonin GAVREL
Antonin GAVREL

Einfaches Benchmarking von C-Code

#include <time.h>

int main(void) {
  clock_t start_time = clock();

  // code or function to benchmark

  double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
  printf("Done in %f seconds\n", elapsed_time);
}

Einfacher Benchmark von Multithread-C-Code

Wenn Sie Multithreading-Programme benchmarken möchten, müssen Sie sich das zunächst einmal genauer ansehen Uhr:

Beschreibung

Die Funktion clock() gibt eine Annäherung an die vom Programm verwendete Prozessorzeit zurück.

Rückgabewert

Der zurückgegebene Wert ist die CPU-Zeit bisher als clock_t verwendet; Um die Anzahl der verwendeten Sekunden zu erhalten, dividieren Sie durch CLOCKS_PER_SEC. Ist die verwendete Prozessorzeit nicht verfügbar oder deren Wert nicht darstellbar, liefert die Funktion den Wert (clock_t)(-1)

Daher ist es sehr wichtig, Teilen Sie Ihre elapsed_time durch die Anzahl der Threads um die Ausführungszeit Ihrer Funktion zu erhalten:

#include <time.h>
#include <omp.h>

#define THREADS_NB omp_get_max_threads()

#pragma omp parallel for private(i) num_threads(THREADS_NB)
clock_t start_time = clock();

// code or function to benchmark

double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
printf("Done in %f seconds\n", elapsed_time / THREADS_NB); // divide by THREADS_NB!

Beispiel

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <omp.h>

#define N 20000
#define THREADS_NB omp_get_max_threads()

void init_arrays(double *a, double *b) {
  memset(a, 0, sizeof(a));
  memset(b, 0, sizeof(b));
  for (int i = 0; i < N; i++) {
    a[i] += 1.0;
    b[i] += 1.0;
  }
}

double func2(double i, double j) {
  double res = 0.0;

  while (i / j > 0.0) {
    res += i / j;
    i -= 0.1;
    j -= 0.000003;
  }
  return res;
}

double single_thread(double *a, double *b) {
  double res = 0;
  int i, j;
  for (i = 0; i < N; i++) {
    for (j = 0; j < N; j++) {
      if (i == j) continue;
      res += func2(a[i], b[j]);
    }
  }
  return res;
}

double multi_threads(double *a, double *b) {
  double res = 0;
  int i, j;
  #pragma omp parallel for private(j) num_threads(THREADS_NB) reduction(+:res)
  for (i = 0; i < N; i++) {
    for (j = 0; j < N; j++) {
      if (i == j) continue;
      res += func2(a[i], b[j]);
    }
  }
  return res;
}

int main(void) {
  double *a, *b;
  a = (double *)calloc(N, sizeof(double));
  b = (double *)calloc(N, sizeof(double));
  init_arrays(a, b);

  clock_t start_time = clock();
  double res = single_thread(a, b);
  double elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
  printf("Default:  Done with %f in %f sd\n", res, elapsed_time);

  start_time = clock();
  res = multi_threads(a, b);
  elapsed_time = (double)(clock() - start_time) / CLOCKS_PER_SEC;
  printf("With OMP: Done with %f in %f sd\n", res, elapsed_time / THREADS_NB);
}

Kompilieren mit:

gcc -O3 multithread_benchmark.c -fopenmp && time ./a.out

Ausgabe:

Default:  Done with 2199909813.614555 in 4.909633 sd
With OMP: Done with 2199909799.377532 in 1.708831 sd

real    0m6.703s (from time function)

  • Gehen Sie nicht davon aus, dass alle Threads jederzeit alle Kerne voll auslasten können? Sie unterschätzen also die Menge an Echtzeit, wenn es einen Synchronisierungs-Overhead gibt. Wenn Sie Echtzeit wollen, fragen Sie nach Echtzeit (mit clock_gettime) und auf einem inaktiven System testen. Dann kannst du vergleichen gegen die gesamten CPU-Sekunden der CPU-Zeit, die während dieser Menge an Echtzeit verwendet wurde. Oder minimieren Sie den Startaufwand und lassen Sie den Benchmark genug wiederholen, um die Gesamtlaufzeit zu dominieren, und perf stat Ihr gesamtes Programm und übernimmt all dies für Sie, einschließlich Showing task-clock und 3.800 CPUs ausgelastet oder was auch immer.

    – Peter Cordes

    25. März 2021 um 4:11 Uhr

  • Ich musste diese Annahme treffen, weil es keine Möglichkeit gibt, die aktuelle Anzahl aktiver Threads zu kennen, ohne das Programm zu verlangsamen 😉 Es ist eher eine faire Schätzung und es funktioniert, es ist keineswegs genau.

    – Antonin GAVREL

    25. März 2021 um 4:12 Uhr


  • Sie “mussten” nur, wenn Sie darauf bestehen, die Echtzeit zu extrapolieren, anstatt sie direkt über eine etwas weniger tragbare Zeitquelle mit hoher Auflösung für die Wandzeit zu messen. Ich würde das nicht empfehlen; wie ich sagte, Verwenden Sie eine richtige Echtzeituhr, anstatt überhaupt zu extrapolieren. Möglicherweise täuschen Sie sich selbst und verstecken alle seriellen oder weniger parallelen Phasen für Probleme, die sich nicht perfekt in gleichmäßig große Stücke mit gleichmäßigem Arbeitsaufwand aufteilen lassen. (Um dies zu handhaben, gibt es verschiedene OpenMP-Planungsoptionen, z. B. dynamisch vs. statisch.)

    – Peter Cordes

    25. März 2021 um 4:18 Uhr


  • memset(a, 0, sizeof(a)) ist falsch. Du solltest schreiben memset(a, 0, sizeof(*a) * N) und N sollte als Argument übergeben werden, obwohl es für den Compiler schwieriger wird, den Code zu parallelisieren, wenn N ist eine veränderliche Größe.

    – chqrlie

    25. März 2021 um 10:44 Uhr


  • Ihre Herangehensweise an das OMP-Timing ist fragwürdig: Entweder interessieren Sie sich für die Leistung eines einzelnen Ausführungsthreads und können dies erreichen, indem Sie die Codegenerierung nicht parallelisieren, oder Sie möchten die Effizienz des OMP-Codegenerators bewerten und sollten beides melden das Timing der Wanduhr und die tatsächliche Anzahl der angeforderten und verwendeten Threads. Allein durch die Division durch diese Zahl wird eine wichtige Information entfernt. Sie sollten das Timing tatsächlich mit der tatsächlichen Anzahl von Threads multiplizieren und mit dem Single-Thread-Timing vergleichen, um zu sehen, ob OMP effizient oder überhaupt nützlich ist.

    – chqrlie

    25. März 2021 um 10:54 Uhr


Versuchen Sie es in POSIX getrusage. Das relevante Argument ist RUSAGE_SELF und die relevanten Felder sind ru_utime.tv_sec und ru_utime.tv_usec.

Benutzeravatar von Mark Wilkins
Markus Wilkins

Möglicherweise gibt es vorhandene Dienstprogramme, die dabei helfen, aber ich vermute, die meisten werden eine Art Probenahme oder möglicherweise Injektion verwenden. Aber um bestimmte Abschnitte des Codes zeitgesteuert zu bekommen, müssen Sie wahrscheinlich Aufrufe zu einem Timer hinzufügen, wie Sie es in Ihrem Beispiel zeigen. Wenn Sie Windows verwenden, funktioniert der Hochleistungstimer. Ich habe eine ähnliche Frage beantwortet und Beispielcode gezeigt, der dies tun wird. Für Linux gibt es ähnliche Methoden.

1414290cookie-checkWie kann ich C-Code einfach Benchmarken?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy