如果抛出异常会导致性能下降,我能否使用STL?

8
例如,我正在编写一个多线程的时间关键应用程序,实时处理和流式传输音频。音频中的中断是完全不可接受的。这是否意味着当抛出异常时,我不能使用STL,因为可能会导致减速?

音频中出现的中断是完全不可接受的。你的代码必须引入一定的处理延迟,这将不可避免地具有某些可变性。你必须有一个输出缓冲区来平滑这些变化,对吧?那么,你认为异常会导致太大的减速呢? - Raedwald
7个回答

28

一般来说,STL容器自身抛出的唯一异常是std::bad_alloc,如果new失败的话。唯一的其他情况是用户代码(例如构造函数、赋值、拷贝构造函数)抛出异常。如果您的用户代码从不抛出异常,则只需防范new抛出异常,这通常是必须要做的。

还可能抛出异常的内容有: - at()函数可能会在越界时抛出std::out_of_range异常。无论如何,这都是一个严重的程序错误。

其次,异常并不总是很慢。如果在音频处理过程中发生异常,那么这可能是由于严重错误引起的,您肯定需要处理。错误处理代码可能比传输异常到catch点的异常处理代码显着昂贵。


8
如果STL容器抛出异常,你可能面临比速度慢得多的更大问题 :)

7

之前的回答没有清楚地说明,因此:

C++ 中会发生异常

无论使用 STL 还是不使用,都无法消除 RAII 代码,该代码将释放您分配的对象资源。

例如:

void doSomething()
{
    MyString str ;
    doSomethingElse() ;
}

在上述代码中,编译器将生成释放MyString资源的代码(即调用MyString析构函数),无论期间发生了什么,包括如果doSomethingElse抛出异常或者在函数范围结束之前使用“return”。
如果您对此有问题,那么您应该调整您的思维方式,或者尝试使用C。
异常应该是例外情况
通常,当发生异常时(仅当发生异常时),会产生性能损失。
但是,异常应该只在以下情况下发送:
- 您需要处理异常事件(即某种错误)。 - 在非常特殊的情况下(例如,在堆栈中进行复杂搜索或在线程优雅中断之前解开堆栈)。
关键词在这里是“exceptional”,这很好,因为我们正在讨论“exception”(看模式?)。
在您的情况下,如果发生异常,很可能是因为程序出现了严重错误,即使没有异常,程序也会崩溃。在这种情况下,您的问题不是处理性能损失,而是处理错误的优雅方式,或者最坏情况下,优雅地终止程序(包括弹出“抱歉”消息框、将未保存的数据保存到临时文件以供以后恢复等)。这意味着(除非在非常特殊的情况下),不要将异常用作“返回数据”。当发生非常严重的情况时,才抛出异常。仅在知道如何处理异常时才捕获异常。避免使用 try/catch(除非您知道如何处理异常)。
关于 STL 呢?
既然我们知道:
- 您仍然想使用 C++。 - 您的目的不是每秒钟抛出一千个异常,只是为了好玩。
我们应该讨论 STL:
STL 通常会验证您是否正在使用错误的方法。如果您确实这样做了,它会抛出异常。但是,在 C++ 中,通常不会为您不使用的东西付费。其中一个例子就是访问 vector 数据。
如果你知道不会越界,那么应该使用运算符[]。
如果你知道不会验证边界,那么应该使用方法at()。
示例A:
typedef std::vector<std::string> Vector ;

void outputAllData(const Vector & aString)
{
   for(Vector::size_type i = 0, iMax = aString.size() ; i != iMax ; ++i)
    {
       std::cout << i << " : " << aString[i] << std::endl ;
    }
}

例子 B:

typedef std::vector<std::string> Vector ;

void outputSomeData(const Vector & aString, Vector::size_type iIndex)
{
   std::cout << iIndex << " : " << aString.at(iIndex) << std::endl ;
}

例子A“信任”程序员,不会浪费时间进行验证(因此,如果出现错误,那时很少有异常抛出的机会...这通常意味着错误/异常/崩溃通常会发生在之后,这对调试没有帮助并会让更多数据被破坏)。
例子B要求向量验证索引是否正确,如果不正确则抛出异常。
选择权在你手中。

我认为顺便说一下,你应该始终默认使用at(),然后再将at优化为[]。很多时候代码并不是性能关键,最好保险起见。 - Elazar Leibovich
@Elazar Leibovich:默认情况下我使用[],但我想你是对的。如果只是为了语法糖,没有理由不使用at() - paercebal
1
我使用[],因为它在调试构建中仍会抛出异常。 - paulm

3

不要因为性能问题而害怕异常。

在C++的早期版本中,启用异常处理可能会使某些编译器变得非常缓慢。

如今,在你选择启用或禁用异常处理时,对于性能并没有什么影响。

通常情况下,STL不会抛出异常,除非内存耗尽,因此这对于你的应用程序类型也不应该是一个问题。

(现在别使用带有GC的语言了.....)


3

有几点需要注意:

  • 你的应用程序是多线程的。如果一个线程(可能是GUI线程)因为异常而变慢,它不应该影响实时线程的性能。

  • 异常是为了特殊情况而设计的。如果在你的实时线程中抛出异常,很可能意味着你无法继续播放音频。如果你发现无论什么原因都在这些线程中不断处理异常,那么重新设计以避免首先出现异常。

我建议你接受STL及其异常(除非STL本身证明太慢 - 但请记住:先测量,后优化),并在你的应用程序中采用异常处理来处理自己的“特殊情况”(例如音频硬件故障等)。


1

我很难想到STL的哪些部分指定了它们可以引发异常。在我的经验中,大多数错误处理都是通过返回代码或作为STL使用的先决条件来处理的。 传递给STL的对象肯定会引发异常,例如复制构造函数,但无论是否使用STL,这都将是一个问题。 其他人提到了诸如std::vector::at()之类的函数,但通常可以执行检查或使用替代方法来确保不会抛出任何异常。

当然,STL的特定实现可以对您使用STL进行“检查”,通常用于调试构建,我认为它只会引发断言,但也许有些会引发异常。

如果没有try/catch存在,我相信除非您自己的类引发异常,否则不会产生任何/最小的性能损失。

在Visual Studio上,您可以完全禁用使用C++异常,请参见项目属性-> C/C++-> 代码生成-> 启用C++异常。我认为这在大多数C++平台上都可用。


0

你说话的口气好像异常情况是不可避免的。只要不做可能会导致异常的事情——修复你的错误,验证你的输入。


编写自己的内存分配器。 - Steve Jessop
@steve使用放置new new (std::nothrow) 更简单。无需再次编写内存分配器,这不是他的错... - Elazar Leibovich
@Elazar:问题是你是否可以在不抛出异常的情况下使用STL。假设标准容器是“STL”的一部分,并且您想对它们进行可能分配内存的操作,则必须编写一个分配器以用作该容器的模板参数,因为默认分配器可能会抛出异常。是的,它可以只使用nothrow new,如果失败,我想它就必须中止。无论您使用什么,都必须编写内存分配器。话虽如此,如果异常的性能成本不可接受,那么内存分配的成本也是如此。 - Steve Jessop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接