我编写了一个测试来测量使用线程的C++异常成本。
#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;
}
这是我最初为了好玩而写的C语言版本。
#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;
}
与C版本相比,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
所以我运行了callgrind分析器。
对于cpp-test
,__cxz_throw
被调用了精确地400,000次,自身耗费为8,000,032。
对于c-test
,__longjmp_chk
被调用了精确地400,000次,自身耗费为5,600,000。
cpp-test
的整体成本为4,048,441,756。
c-test
的整体成本为60,417,722。
我猜C++异常处理涉及的成本远不止简单保存跳转点状态并稍后恢复。我无法使用更大的N
进行测试,因为对于C++测试来说,callgrind分析器将永远运行下去。
C++异常处理中涉及的额外成本是什么,使其在这个例子中比setjmp
/longjmp
组合慢多倍?
try {
块时设置setjmp。我不确定但我认为如果你正在使用SJLJ,那么你会得到类似于C程序的结果;SEH和Dwarf2选项使得对于最常见的使用情况(即没有抛出异常)更快。 - M.Msetjmp
/longjmp
完全忽略了这些工作。如果在调用setjmp
和longjmp
之间分配了内存,则在调用longjmp
时可能会泄漏该内存。很可能您还泄漏了其他资源(文件描述符等)。即使不是在测试代码中,在一般情况下也是如此。是的,setjmp
和longjmp
能够工作,但它们通常是一种非常简单的异常处理机制。 - Jonathan Leffler