15

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

请提供独立的代码示例。

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

我需要从以下情况中获得结果:

  1. 没有 try/catch 块时的开销
  2. 有 try/catch 块但不抛出异常时的开销
  3. 抛出异常时的开销
4

8 回答 8

34

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

于 2008-09-04T09:30:34.963 回答
10

作为一个建议:当抛出异常时,不要太在意开销。异常处理实现通常使不快速抛出和缓慢捕获。没关系,因为这些情况非常特殊。

卡尔

于 2008-09-04T07:32:43.937 回答
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 中的类似设置。

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

#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
}
于 2008-09-04T07:38:09.743 回答
3

Kevin Frei在他的演讲“ Windows 上 C++ 异常处理的成本”中谈到了异常处理的性能成本。(在“摘要和结论”下,有一个列表项显示“[异常处理性能成本] 并不总是可衡量的”。)

于 2008-09-05T15:55:23.750 回答
2

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

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

卡尔

于 2008-09-04T14:53:29.197 回答
2

在代码中没有真正好的方法来衡量这一点。您将需要使用分析器。

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

把他们的时间加起来,你就有了开销。在我们的项目中,我们使用了 Intel 的 vTunes,它可以与 Visual C++ 和 gcc 一起使用。

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

于 2008-09-04T07:42:41.307 回答
0

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

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

于 2008-09-04T20:58:26.760 回答
0

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

于 2009-02-17T17:32:04.467 回答