衡量C ++中异常处理开销/性能的最佳方法是什么?
请提供独立的代码示例。
我的目标是Microsoft Visual C ++ 2008和gcc。
我需要从以下情况获得结果:
没有try / catch块时开销
有try / catch块但未引发异常时的开销
引发异常时的开销
有关C ++性能的技术报告草稿中的5.4节完全涉及异常的开销。
作为建议:抛出异常时,不要为开销过多地打扰。异常处理实现通常不会使快速抛出和捕获缓慢。没关系,因为这些案例很特殊。
卡尔
这是我想出的测量代码。您看到任何问题吗?
到目前为止,可在Linux和Windows上运行,并使用以下命令进行编译:
1
| g++ exception_handling.cpp -o exception_handling [ -O2 ] |
或例如Visual C ++ Express。
要获取基本情况("从语言中完全删除了例外支持"),请使用:
1
| g++ exception_handling.cpp -o exception_handling [ -O2 ] -fno-exceptions -DNO_EXCEPTIONS |
或MSVC中的类似设置。
这里有一些初步结果。由于机器负载的变化,它们可能都是骗人的,但是它们确实提供了有关相对异常处理开销的一些信息。 (执行摘要:没有抛出异常时什么都不是,实际上抛出异常时很大。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
| #include <stdio.h>
// Timer code
#if defined(__linux__)
#include <sys/time.h>
#include <time.h>
double time()
{
timeval tv;
gettimeofday(&tv, 0);
return 1.0 * tv.tv_sec + 0.000001 * tv.tv_usec;
}
#elif defined(_WIN32)
#include <windows.h>
double get_performance_frequency()
{
unsigned _int64 frequency;
QueryPerformanceFrequency((LARGE_INTEGER*) &frequency); // just assume it works
return double(frequency);
}
double performance_frequency = get_performance_frequency();
double time()
{
unsigned _int64 counter;
QueryPerformanceCounter((LARGE_INTEGER*) &counter);
return double(counter) / performance_frequency;
}
#else
# error time() not implemented for your platform
#endif
// How many times to repeat the whole test
const int repeats = 10;
// How many times to iterate one case
const int times = 1000000;
// Trick optimizer to not remove code
int result = 0;
// Case 1. No exception thrown nor handled.
void do_something()
{
++result;
}
void case1()
{
do_something();
}
// Case 2. No exception thrown, but handler installed
#ifndef NO_EXCEPTIONS
void do_something_else()
{
--result;
}
void case2()
{
try
{
do_something();
}
catch (int exception)
{
do_something_else();
}
}
// Case 3. Exception thrown and caught
void do_something_and_throw()
{
throw ++result;
}
void case3()
{
try
{
do_something_and_throw();
}
catch (int exception)
{
result = exception;
}
}
#endif // !NO_EXCEPTIONS
void (*tests[])() =
{
case1,
#ifndef NO_EXCEPTIONS
case2,
case3
#endif // !NO_EXCEPTIONS
};
int main()
{
#ifdef NO_EXCEPTIONS
printf("case0
");
#else
printf("case1\tcase2\tcase3
");
#endif
for (int repeat = 0; repeat < repeats; ++repeat)
{
for (int test = 0; test < sizeof(tests)/sizeof(tests[0]); ++test)
{
double start = time();
for (int i = 0; i < times; ++i)
tests[test]();
double end = time();
printf("%f\t", (end - start) * 1000000.0 / times);
}
printf("
");
}
return result; // optimizer is happy - we produce a result
} |
凯文·弗雷(Kevin Frei)在他的演讲" Windows上C ++异常处理的代价"中谈到了异常处理的性能代价。 (在"摘要和结论"下,有一个列表项表示" [异常处理性能成本并非总是可衡量的"。)
关于异常处理性能的另一个注意事项:简单测试不考虑缓存。尝试代码和捕获代码都很小,以至于所有内容都适合指令和数据缓存。但是编译器可能会尝试将捕获代码移离try代码,从而减少正常保存在缓存中的代码量,从而提高性能。
如果将异常处理与传统的C风格的返回值检查进行比较,则还应考虑这种缓存效果(在讨论中通常会忽略此问题)。
卡尔
没有真正好的方法可以在代码中进行测量。您将需要使用分析器。
这不会直接向您显示花费多少时间进行异常处理,但是通过一点点研究,您会发现哪些运行时方法可以处理异常(例如,对于VC ++。NET,它是__cxx_exc [...])。
加倍他们的时间,你就有开销。在我们的项目中,我们使用了Intel的vTunes,它可以与Visual C ++和gcc一起使用。
编辑:好吧,如果您只需要一个可能有效的通用编号。以为您有一个实际的应用程序可以分析您无法关闭异常的情况。
这里显示了有关g ++如何处理异常的完整详细信息。它描述它是用于Itanium体系结构,但是所使用的通用技术是相同的。它不会告诉您确切的时间开销,但是您可以了解大致的代码开销。
答案是否取决于抛出后必须进行的清理?如果抛出一个异常导致整个对象的负载超出堆栈范围,那么这将增加开销。
换句话说,我不确定第三个问题的答案是否独立于代码的细节。