【发布时间】:2015-10-07 19:53:54
【问题描述】:
我编写了一个测试来测量线程中 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 进行测试,因为 callgrind 分析器将永远运行以进行 C++ 测试。
至少在这个例子中,C++ 异常导致它比 setjmp/longjmp 对慢很多倍的额外成本是什么?
【问题讨论】:
-
这里有各种时髦的信息:stackoverflow.com/questions/13835817/…
-
您正在测试应该不经常调用的异常的性能。如果你的程序抛出了很多导致性能问题的异常,那么你就有了一个严重的问题。您应该测试的是使用 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有效,但它们通常是一种非常粗糙的异常处理机制。