2015-07-18 11 views
14

Ich schrieb einen Test, um die Kosten von C++ - Ausnahmen mit Threads zu messen.Die Kosten von C++ - Ausnahmen und setjmp/longjmp

#include <cstdlib> 
#include <iostream> 
#include <vector> 
#include <thread> 

static const int N = 100000; 

static void doSomething(int& n) 
{ 
    --n; 
    throw 1; 
} 

static void throwManyManyTimes() 
{ 
    int n = N; 
    while (n) 
    { 
     try 
     { 
      doSomething(n); 
     } 
     catch (int n) 
     { 
      switch (n) 
      { 
      case 1: 
       continue; 
      default: 
       std::cout << "error" << std::endl; 
       std::exit(EXIT_FAILURE); 
      } 
     } 
    } 
} 

int main(void) 
{ 
    int nCPUs = std::thread::hardware_concurrency(); 
    std::vector<std::thread> threads(nCPUs); 
    for (int i = 0; i < nCPUs; ++i) 
    { 
     threads[i] = std::thread(throwManyManyTimes); 
    } 
    for (int i = 0; i < nCPUs; ++i) 
    { 
     threads[i].join(); 
    } 
    return EXIT_SUCCESS; 
} 

Hier ist die C-Version, die ich ursprünglich zum Spaß schrieb.

#include <stdio.h> 
#include <stdlib.h> 
#include <setjmp.h> 
#include <glib.h> 

#define N 100000 

static GPrivate jumpBuffer; 

static void doSomething(volatile int *pn) 
{ 
    jmp_buf *pjb = g_private_get(&jumpBuffer); 

    --*pn; 
    longjmp(*pjb, 1); 
} 

static void *throwManyManyTimes(void *p) 
{ 
    jmp_buf jb; 
    volatile int n = N; 

    (void)p; 
    g_private_set(&jumpBuffer, &jb); 
    while (n) 
    { 
     switch (setjmp(jb)) 
     { 
     case 0: 
      doSomething(&n); 
     case 1: 
      continue; 
     default: 
      printf("error\n"); 
      exit(EXIT_FAILURE); 
     } 
    } 
    return NULL; 
} 

int main(void) 
{ 
    int nCPUs = g_get_num_processors(); 
    GThread *threads[nCPUs]; 
    int i; 

    for (i = 0; i < nCPUs; ++i) 
    { 
     threads[i] = g_thread_new(NULL, throwManyManyTimes, NULL); 
    } 
    for (i = 0; i < nCPUs; ++i) 
    { 
     g_thread_join(threads[i]); 
    } 
    return EXIT_SUCCESS; 
} 

Die C++ Version läuft sehr langsam im Vergleich zu der C-Version.

$ g++ -O3 -g -std=c++11 test.cpp -o cpp-test -pthread 
$ gcc -O3 -g -std=c89 test.c -o c-test `pkg-config glib-2.0 --cflags --libs` 
$ time ./cpp-test 

real 0m1.089s 
user 0m2.345s 
sys  0m1.637s 
$ time ./c-test 

real 0m0.024s 
user 0m0.067s 
sys  0m0.000s 

So lief ich den Callgrind Profiler.

Für cpp-test, __cxz_throw wurde genau 400.000 mal mit Selbstkosten von 8.000.032 aufgerufen.

Für c-test, __longjmp_chk wurde genau 400.000 mal mit Selbstkosten von 5.600.000 aufgerufen. Die gesamten Kosten von cpp-test sind 4.048.441.756.

Die gesamten Kosten von c-test sind 60.417.722.

Ich denke, etwas viel mehr als nur den Zustand des Sprungpunktes zu speichern und später wieder aufzunehmen, wird mit C++ - Ausnahmen gemacht. Ich konnte nicht mit größeren N testen, weil der Callgrind Profiler für den C++ Test für immer ausgeführt wird.

Was sind die zusätzlichen Kosten für C++ - Ausnahmen, die es in diesem Beispiel viel langsamer machen als das setjmp/longjmp Paar?

+0

Können Sie den resultierenden Maschinencode vergleichen? –

+0

@KerrekSB Entschuldigung, die Disassemblierung für beide ist zu kompliziert für mich, um etwas Sinnvolles zu bekommen, ich kann die ganze ASM irgendwo einfügen, wenn du sie brauchst. – xiver77

+0

Alle Arten von groovigen Informationen hier: http://stackoverflow.com/questions/13835817/are-exceptions-inc-really-slow – user4581301

Antwort

16

Dies ist von Entwurf.

C++ - Ausnahmen werden erwartet, Ausnahme in der Natur und sind so optimiert. Das Programm wird kompiliert, um am effizientesten zu sein, wenn keine Ausnahme auftritt.

Sie können dies überprüfen, indem Sie die Ausnahme aus Ihren Tests auskommentieren.

In C++:

//throw 1; 

$ g++ -O3 -g -std=c++11 test.cpp -o cpp-test -pthread 

$ time ./cpp-test 

real 0m0.003s 
user 0m0.004s 
sys  0m0.000s 

In C:

/*longjmp(*pjb, 1);*/ 

$ gcc -O3 -g -std=c89 test.c -o c-test `pkg-config glib-2.0 --cflags --libs` 

$ time ./c-test 

real 0m0.008s 
user 0m0.012s 
sys  0m0.004s 

Was die zusätzlichen Kosten in C++ Ausnahmen beteiligt ist es oft langsamer als die setjmp/longjmp Paar zumindest in dieser Herstellung Beispiel?

g ++ implementiert Zero-Cost-Modell Ausnahmen, die keinen wirksamen Kopf haben *, wenn eine Ausnahme ist nicht geworfen. Maschinencode wird erzeugt, als ob es keinen try/catch Block gäbe.

Die Kosten dieses Nullüberkopf ist, dass eine Tabellensuche auf den Programmzähler durchgeführt werden müssen, wenn eine Ausnahme geworfen wird, zum Durchführen einer Stapel Abwickeln eines Sprung zu dem entsprechenden Code zu bestimmen. Dies setzt die gesamte Blockimplementierung in den Code ein, der eine throw ausführt.

Ihre zusätzlichen Kosten sind eine Tabellensuche.

* Es kann zu geringfügigen Voodoo-Timing kommen, da das Vorhandensein einer PC-Nachschlagetabelle das Speicherlayout beeinflussen kann, was CPU-Cache-Misses verursachen kann.