如何分析异常处理的性能开销

问题描述 投票:0回答:8

在 C++ 中衡量异常处理开销/性能的最佳方法是什么?

请提供独立的代码示例。

我的目标是 Microsoft Visual C++ 2008 和 gcc。

我需要从以下案例中得到结果:

  1. 没有 try/catch 块时的开销
  2. 存在 try/catch 块但不抛出异常时的开销
  3. 抛出异常时的开销
c++ exception gcc visual-c++ profiling
8个回答
34
投票

《C++ 性能技术报告草案》 的第 5.4 节完全致力于异常的开销。


10
投票
建议:当抛出异常时,不要太在意开销。异常处理实现通常会使抛出速度不快而捕获速度慢。没关系,因为这些情况都是例外。

卡尔


8
投票
这是我想出的测量代码。你发现它有什么问题吗?

目前可在 Linux 和 Windows 上运行,编译方式为:

g++ exception_handling.cpp -o exception_handling [ -O2 ]

或例如

Visual C++ Express

要获取基本情况(“从语言中完全删除异常支持”),请使用:

g++ exception_handling.cpp -o exception_handling [ -O2 ] -fno-exceptions -DNO_EXCEPTIONS

或 MSVC 中的类似设置。

一些初步结果

这里。由于机器负载不同,它们可能都是做作的,但它们确实给出了一些关于相对异常处理开销的想法。 (执行摘要:当没有抛出异常时,没有或很少,当实际抛出异常时,则很大。)

#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\n"); #else printf("case1\tcase2\tcase3\n"); #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("\n"); } return result; // optimizer is happy - we produce a result }
    


2
投票
没有真正好的方法可以在代码中测量这一点。您需要使用分析器。

这不会直接显示您在异常处理上花费了多少时间,但通过一些研究,您会发现哪些运行时方法处理异常(例如,对于 VC++.NET,它是 __cxx_exc[...]) .

将他们的时间加起来,你就得到了开销。在我们的项目中,我们使用了来自 Intel 的 vTunes,它可以与 Visual C++ 和 gcc 配合使用。

编辑:好吧,如果您只需要一个可能有效的通用号码。以为您有一个实际的应用程序来分析,您不能只关闭异常。


2
投票
关于异常处理性能的另一个注意事项:简单的测试不考虑缓存。 try 代码和 catch 代码都非常小,以至于所有内容都适合指令和数据缓存。但编译器可能会尝试将 catch 代码移至远离 try 代码的位置,这会减少正常保留在缓存中的代码量,从而提高性能。

如果将异常处理与传统的 C 风格返回值检查进行比较,则还应该考虑到这种缓存效果(在讨论中通常会忽略这个问题)。

卡尔


0
投票
答案难道不取决于抛出后必须进行什么清理吗?如果引发异常,导致整个对象负载超出堆栈范围,那么这将增加开销。

换句话说,我不确定第三个问题是否有独立于代码细节的答案。


0
投票
此处

显示了有关 g++ 如何处理异常的完整详细信息。它将其描述为适用于 Itanium 架构,但所使用的一般技术是相同的。它不会告诉您确切的时间开销,但是您可以了解粗略的代码开销是多少。

© www.soinside.com 2019 - 2024. All rights reserved.