抛出C ++ 0x异常的代价

时间:2009-06-19 16:11:47

标签: c++ performance exception

在C ++ 0x中抛出异常有什么性能影响?这个编译器依赖多少?这与询问what is the cost of entering a try block, even if no exception is thrown不同。

我们是否应该将更多的异常用于Java中的一般逻辑处理?

10 个答案:

答案 0 :(得分:36)

#include <iostream>
#include <stdexcept>

struct SpaceWaster {
    SpaceWaster(int l, SpaceWaster *p) : level(l), prev(p) {}
    // we want the destructor to do something
    ~SpaceWaster() { prev = 0; }
    bool checkLevel() { return level == 0; }
    int level;
    SpaceWaster *prev;
};

void thrower(SpaceWaster *current) {
    if (current->checkLevel()) throw std::logic_error("some error message goes here\n");
    SpaceWaster next(current->level - 1, current);
    // typical exception-using code doesn't need error return values
    thrower(&next);
    return;
}

int returner(SpaceWaster *current) {
    if (current->checkLevel()) return -1;
    SpaceWaster next(current->level - 1, current);
    // typical exception-free code requires that return values be handled
    if (returner(&next) == -1) return -1;
    return 0;
}

int main() {
    const int repeats = 1001;
    int returns = 0;
    SpaceWaster first(1000, 0);

    for (int i = 0; i < repeats; ++i) {
        #ifdef THROW
            try {
                thrower(&first);
            } catch (std::exception &e) {
                ++returns;
            }
        #else
            returner(&first);
            ++returns;
        #endif
    }
    #ifdef THROW
        std::cout << returns << " exceptions\n";
    #else
        std::cout << returns << " returns\n";
    #endif
}

米老鼠基准测试结果:

$ make throw -B && time ./throw
g++     throw.cpp   -o throw
1001 returns

real    0m0.547s
user    0m0.421s
sys     0m0.046s

$ make throw CPPFLAGS=-DTHROW -B && time ./throw
g++  -DTHROW   throw.cpp   -o throw
1001 exceptions

real    0m2.047s
user    0m1.905s
sys     0m0.030s

因此,在这种情况下,抛出1000个堆栈级别的异常,而不是正常返回,需要大约1.5ms。这包括进入try块,我相信某些系统在执行时是免费的,而其他系统在每次进入try时都会产生成本,而在其他系统上每次进入包含try的函数时只会产生成本。对于更可能的100个堆栈级别,我将重复次数增加到10k,因为一切都快了10倍。所以例外成本为0.1ms。

对于10 000个堆栈级别,它是18.7秒vs 4.1s,因此异常大约需要14ms。因此,对于这个例子,我们正在研究每级堆栈1.5us的相当一致的开销(每个级别都在破坏一个对象)。

显然,C ++ 0x没有指定异常的性能(或其他任何东西,除了算法和数据结构的大O复杂性)。我不认为它会以一种严重影响许多实现的方式改变异常,无论是积极的还是消极的。

答案 1 :(得分:14)

异常性能非常依赖于编译器。您必须分析您的应用程序以查看它是否有问题。一般来说,它不应该是。

你真的应该对“异常条件”使用异常,而不是一般的逻辑处理。例外是通过代码和错误路径分离正常路径的理想选择。

答案 2 :(得分:10)

我基本上认为问题是错误的。
什么是例外的成本是没有用的,更有用的是相对于备选的例外成本。因此,您需要衡量多少例外费用,并将其与返回的错误代码进行比较&gt;&gt;&gt; AND&lt;&lt;&lt;检查堆栈每个级别的错误代码是否展开。

另请注意,当您控制所有内容时,不应使用例外。在返回错误代码的类中,这可能是一种更好的技术。当您无法确定在运行时如何(或在何种上下文中)使用对象时,应使用异常来在运行时传输控件。

基本上它应该用于将控制转移到更高级别的上下文,其中具有足够上下文的对象将理解如何处理异常情况。

鉴于这种用法原则,我们看到异常将用于在堆栈帧中传输多个级别的控制。现在考虑编写所需的额外代码,以便将错误代码传递回同一个调用堆栈。考虑当错误代码来自多个不同方向并尝试协调所有不同类型的错误代码时添加的额外复杂性。

鉴于此,您可以看到异常如何能够极大地简化代码流,并且您可以看到代码流的复杂性。然后问题变成天气异常比需要在每个堆栈帧执行的复杂错误条件测试更昂贵。

答案总是取决于(如果你需要的话,同时使用简介并使用速记)。

但如果速度不是唯一的成本 可维护性是可以衡量的成本。使用此成本度量异常总是获胜,因为它们最终使代码的控制流只需要完成的任务而不是任务和错误控制。

答案 3 :(得分:8)

我曾经创建了一个x86仿真库,并使用了例外中断等。馊主意。即使我没有抛出任何异常,它也会影响我的主循环。像这样的东西是我的主循环

try{
    CheckInterrupts();
    *(uint32_t*)&op_cache=ReadDword(cCS,eip);
    (this->*Opcodes[op_cache[0]])();
    //operate on the this class with the opcode functions in this class
    eip=(uint16_t)eip+1;

}
//eventually, handle these and do CpuInts...
catch(CpuInt_excp err){
    err.code&=0x00FF;
    switch(err.code){

在try块中包含该代码的开销使得异常函数成为CPU时间前5位用户中的2位。

那对我来说很贵

答案 4 :(得分:6)

C ++ 0x中的异常应该比C ++ 03更快或更慢。这意味着他们的表现完全取决于实施。 Windows使用完全不同的数据结构来实现32位与64位以及Itanium与x86之间的异常处理。 Linux并不能保证只使用一个实现。这取决于。有几种流行的方法可以实现异常处理,所有这些方法都有优点和缺点。

因此它不依赖于语言(c ++ 03 vs 0x),而是依赖于编译器,运行时库,操作系统和CPU架构。

答案 5 :(得分:4)

我认为C ++ 0x不会像C ++异常那样增加或改变任何东西。如需一般建议,请查看here

答案 6 :(得分:3)

可以想象他们会有与C ++ 03相同的性能,这是“非常慢”!不,由于try-catch-throw构造,在任何语言中都应该在异常情况下使用异常。如果在java中使用throw for program flow control,那么你做错了什么。

答案 7 :(得分:0)

想象一下,铃声响起,电脑停止接受任何输入三秒钟,然后有人将用户踢到头部。

这是例外的成本。如果它可以防止数据丢失或机器着火,那么这是值得的。否则,它可能不是。

编辑:因为这得到了一个downvote(加上一个upvote,所以+8对我来说!),我将用更少的幽默和更多的信息澄清上述内容:异常,至少在C ++领域,需要RTTI和编译器以及可能是操作系统的魔力,这使得它们的表现成为一个巨大的不确定黑洞。 (你甚至不能保证它们会被激活,但是这些情况发生在其他更严重的事件中,比如内存不足或用户只是杀死进程或者机器实际着火。)所以如果你使用它们,那么应该是因为你想要从一种可能导致可怕的事情发生的情况中优雅地恢复,但是恢复不能有任何期望的运行(无论你的具体应用是什么)。

因此,如果您要使用异常,则不能对性能影响做任何假设。

答案 8 :(得分:0)

@Steve Jessop已发布了事实上的答案,但我仍然认为还有一件事:尝试大多数产品经常使用的不同优化级别,例如“ -O2”。

答案 9 :(得分:-1)

异常处理通常是一项昂贵的功能,因为抛出/捕获意味着要执行额外的代码以确保堆栈展开和捕获条件评估。

据我从一些读物中了解到,对于Visual C ++,例如,代码中嵌入了一些非常复杂的结构和逻辑来确保这一点。由于大多数函数可能会调用可能抛出异常的其他函数,因此即使在这些情况下也可能存在堆栈展开的一些开销。

但是,在考虑异常开销之前,最好在任何优化操作之前测量代码中异常使用的影响。避免异常过度应该可以防止异常处理带来的重大开销。