c++异常和setjmp/longjmp的代价
The Cost of C++ Exceptions and setjmp/longjmp
我写了一个测试来测量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("errorn");
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
被调用40万次,自成本为8,000,032。
对于c-test
, __longjmp_chk
被调用正好40万次,自成本为560万。
cpp-test
的总成本为4,048,441,756。
c-test
的总成本为60,417,722。
我猜c++异常比简单地保存跳转点的状态和稍后恢复更重要。我不能用更大的N
进行测试,因为callgrind分析器将永远运行c++测试。
c++异常所涉及的额外成本是什么,至少在这个例子中,它比setjmp
/longjmp
对慢很多倍?
这是通过设计。
c++异常在本质上应该是exception ,并因此进行了优化。当不发生异常时,程序将被编译为最有效的。
您可以通过注释掉测试中的异常来验证这一点。
在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: /*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
至少在这个例子中,c++异常比setjmp/longjmp对慢很多倍所涉及的额外成本是什么?
g++实现了零成本模型异常,当异常没有被抛出时,它没有有效的开销*。如果没有try
/catch
块,则生成机器码。
这个零开销的代价是,当抛出异常时,必须在程序计数器上执行表查找,以确定跳转到执行堆栈展开的适当代码。这将整个try
/catch
块实现置于执行throw
的代码中。
您的额外成本是查找表。
*可能会出现一些小的定时问题,因为PC查找表的存在可能会影响内存布局,这可能会影响CPU缓存丢失
相关文章:
- [longjmp/setjmp]c++ 相同的代码窗口有异常 Linux 没有错误并且运行良好
- 为什么 setjmp/longjmp 的这种用法是未定义的行为?
- longjmp应该还原堆栈吗
- setjmp/longjmp 在发布和调试中的不同行为
- 运行时特性测试、setjmp、longjmp和信号掩码
- C++ 和 C 库使用 longjmp
- 在C++中向邻接矩阵的边添加代价
- 缓存未命中的代价是多少
- 如何在二叉树中得到最大路径代价
- 不一致的警告:变量可能会被“longjmp”或“vfork”破坏
- 对于stl容器来说,end()可能是一个代价高昂的操作
- 通过尝试块进行 longjmp 是否安全
- 在c++和Python之间切换控制的代价
- 算法的代价
- 原子递减是否比递增代价更高?
- 通过指针访问数据成员的代价
- 通过值传递与通过引用或指针传递的性能代价
- 以有状态策略类为代价解耦宿主类和策略类,并且不遵循Effective c++的第26条
- 插入和替换代价不均匀的Levenshtein距离:
- c++异常和setjmp/longjmp的代价