c++ c exception setjmp

El costo de las excepciones de C++ y setjmp/longjmp



exception (1)

Esto es por diseño.

Se espera que las excepciones de C ++ sean de naturaleza excepcional y, por lo tanto, estén optimizadas. El programa se compila para ser más eficiente cuando no ocurre una excepción.

Puede verificar esto comentando la excepción de sus pruebas.

En 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

Cía:

/*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

¿Cuál es el costo adicional involucrado en las excepciones de C ++ por lo que es mucho más lento que el par setjmp / longjmp al menos en este ejemplo?

g ++ implementa excepciones del modelo de costo cero, que no tienen una sobrecarga efectiva * cuando no se lanza una excepción. El código de máquina se produce como si no hubiera un bloque try / catch .

El costo de esta sobrecarga cero es que se debe realizar una búsqueda en la tabla en el contador del programa cuando se lanza una excepción, para determinar un salto al código apropiado para realizar el desenrollado de la pila. Esto coloca toda la implementación del bloque try / catch dentro del código que realiza un throw .

Su costo extra es una tabla de búsqueda.

* Es posible que se produzca un vudú de sincronización menor, ya que la presencia de una tabla de búsqueda de PC puede afectar el diseño de la memoria, lo que puede afectar las fallas en la memoria caché de la CPU.

Escribí una prueba para medir el costo de las excepciones de C ++ con subprocesos.

#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; }

Aquí está la versión C que escribí inicialmente por diversión.

#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; }

La versión de C ++ se ejecuta muy lento en comparación con la versión de C.

$ 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

Así que corrí el perfilador de callgrind.

Para cpp-test , __cxz_throw fue llamado exactamente 400,000 veces con un costo propio de 8,000,032.

Para c-test , __longjmp_chk fue llamado exactamente 400,000 veces con un costo propio de 5,600,000.

El costo total de cpp-test es 4,048,441,756.

El costo total de c-test es 60,417,722.

Supongo que se hace mucho más que simplemente guardar el estado del punto de salto y la reanudación posterior con excepciones de C ++. No pude probar con una N más grande porque el generador de perfiles de callgrind se ejecutará para siempre para la prueba de C ++.

¿Cuál es el costo adicional involucrado en las excepciones de C ++ por lo que es mucho más lento que el par setjmp / longjmp al menos en este ejemplo?