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?