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

sea*_*ley 14 c++ performance exception

在C++ 0x中抛出异常有什么性能影响?这个编译器依赖多少?这与询问输入try块的成本是不一样的,即使没有抛出异常.

我们是否应该期望更多地使用异常来处理Java中的常规逻辑处理?

Ste*_*sop 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
}
Run Code Online (Sandbox Code Playgroud)

米老鼠基准测试结果:

$ 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 我不认为(没有双关语意)测试实现比说出你对某些事情的想法更有价值.如果你的想法是根据事实来判断的,那么它们就会以不同的方式同样有效.这个将是"gcc和msvc,x86未经优化的构建"的事实上的答案.但是正如你想象的那样,有更多的编译器,还有更多的构建配置:)无论如何,这个研究答案来自我的+1 (2认同)

Bri*_*eal 14

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

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

  • +1仅在特殊情况下使用例外 (3认同)
  • 来自我的+1.使用普通逻辑的例外可以导致"异常意外",这只是一个工作,只是为了找出事物被捕获的位置.特别是对于多态代码,当您甚至不知道ListenerRegistry的哪个实现是您的调用者时,不能运行它并查看您在当前配置中获得的堆栈跟踪.如果异常仅用于预期不可恢复的情况,那么大多数级别的代码甚至都不会考虑尝试从它们中恢复:-) (2认同)

Mar*_*ork 10

我基本上认为问题是错误的.
什么是例外的成本是没有用的,更有用的是相对于备选的例外成本.因此,您需要测量多少异常成本并将其与返回错误代码进行比较>>>和<<<检查堆栈每个级别的错误代码展开.

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

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

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

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

答案始终取决于(如果您需要,请同时使用简介并使用速记).

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

  • 特别是,"返回者"在*success*情况下可能会变慢,这取决于编译器选择发出分支的方式.你经常听不到"例外!哎呀!冰冷的慢!" 围绕他们如何引入额外的分支(与基于异常的错误处理相比)进入正常,成功的操作的情况. (2认同)

Ear*_*rlz 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){
Run Code Online (Sandbox Code Playgroud)

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

那对我来说很贵

  • 哎哟.这是一个很大的影响. (2认同)

jal*_*alf 6

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

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