C+的成本+;异常和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; }
所以我运行了callgrind分析器 对于C+的成本+;异常和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++,c,exception,setjmp,C++,C,Exception,Setjmp,所以我运行了callgrind分析器 对于cpp测试,\uuuucxz\uthrow被精确调用了400000次,自身成本为800032 对于c-test,longjmp\u chk的调用次数正好为400000次,自开销为5600000 cpp测试的全部成本为4048441756 c-test的全部成本为60417722 我猜,除了简单地保存跳点状态和稍后恢复,还有很多事情是用C++异常完成的。我不能用较大的 n>代码>进行测试,因为CalgReD剖析器将永远运行C++测试。 C++异常中的额
cpp测试
,\uuuucxz\uthrow
被精确调用了400000次,自身成本为800032
对于c-test
,longjmp\u chk
的调用次数正好为400000次,自开销为5600000
cpp测试的全部成本为4048441756
c-test
的全部成本为60417722
<>我猜,除了简单地保存跳点状态和稍后恢复,还有很多事情是用C++异常完成的。我不能用较大的<代码> n>代码>进行测试,因为CalgReD剖析器将永远运行C++测试。
C++异常中的额外开销是什么,使得它比 SETJMP LojMP >对的速度慢很多倍?至少在这个例子中是这样的?
<强>这是设计。< /强>
C++异常在本质上被认为是异常的,因此被优化。当不发生异常时,程序被编译为最有效的
您可以通过注释掉测试中的异常来验证这一点
在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中:
C++异常的额外开销是什么?它至少比这个例子中的SETJMP/LIJMP对慢很多倍?
g++实现零成本模型异常,当异常未抛出时,它没有有效的开销*。机器代码的生成就像没有try
/catch
块一样
这种零开销的代价是,当抛出异常时,必须在程序计数器上执行表查找,以确定跳转到执行堆栈展开的适当代码。这将整个try
/catch
块实现放在执行抛出的代码中
额外的费用是查表
*可能会出现一些小的计时巫术,因为PC查找表的存在可能会影响内存布局,这可能会影响CPU缓存未命中。这里有各种groovy信息:您正在测试不应该经常调用的异常的性能。如果你的程序抛出了那么多导致性能问题的异常,那么你就有了一个严重的问题。您应该测试的是使用try/catch块与不使用try/catch块,并查看准备好异常以处理问题的成本。这将向您展示在真正需要时准备好异常的成本。异常(正确完成)与setjmp/longjmp有很大不同。除了例外情况,您实际上可以处理问题并保持程序运行。您需要提供有关您的g++构建的更多信息。在Windows中,g++可以使用3种不同的异常处理机制构建:SetJmp/LongJmp、Win32 SEH或Dwarf2。后两个选项(我相信)是零开销的:如果没有抛出异常,那么就没有运行时惩罚。当您输入try{
块时,SJLJ会浪费时间设置setjmp。我不确定,但我怀疑您会发现,如果您使用SJLJ,那么您会得到与C程序类似的结果;SEH和Dwarf2选项使最常见的用例更快(即,不会抛出任何东西)请注意,异常涉及所有类型的清理工作,而setjmp
/longjmp
完全忽略了这些工作。如果在调用setjmp
和调用longjmp
之间分配内存,则在调用longjmp
时可能泄漏了它。很可能是泄漏了其他资源(文件描述符等)。即使不是在您的测试代码中,在一般情况下。是的,setjmp
和longjmp
工作,但它们通常是一种非常粗糙的异常处理机制。“您额外的成本是查表。”这是对问题的轻描淡写。在热表中查表(在这段代码中,表肯定是热的,因为相同的异常会从同一行一次又一次地抛出)成本很低。一个大成本setjmp
/longjmp
避免的是内存分配;每个抛出的异常都涉及动态内存分配(例如,在g++
上,它调用\ucxa\u allocate\u exception
,以后必须释放该异常),然后抛出
本身涉及到在跳转到处理程序代码之前组装大量堆栈展开信息(在这种情况下,大部分信息都会被丢弃而不被使用)。
$ 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
//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
/*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