最终价格昂贵


14

对于在退出函数之前必须进行资源清理的代码,这两种执行方法之间在性能上存在重大差异。

  1. 在每个return语句之前清理资源

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. 最终清除资源

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }

我在示例程序中进行了一些基本测试,似乎没有太大的区别。我非常喜欢finally这样做的方法-但我想知道这是否会对大型项目造成任何性能影响。


3
确实不值得发布答案,但是没有。如果您使用的是Java(希望将异常作为默认的错误处理策略),则应使用try / catch / finally-语言针对所使用的模式进行了优化。
乔纳森·里奇

1
@JonathanRich:怎么样?
haylem 2013年

2
简洁地编写代码,以表达您要完成的工作。当您知道自己有问题时,请稍后进行优化-不要回避语言功能,因为您认为自己可能会从最初并不慢的功能中节省几毫秒。
肖恩·麦克索明

@mikeTheLiar-如果您是说我应该写 if(!cond),那是Java我这样做的。在C ++中,这就是我编写布尔值以及其他类型(即)的代码的方式int x; if(!x)。由于java允许我仅将它用于booleans,所以我完全停止了在Java中使用if(cond)if(!cond)
user93353 2013年

1
@ user93353让我难过。我宁愿看到而(someIntValue != 0)不是比较而不是评估布尔值。这对我来说很香,当我在野外看到它时,我会立即对其进行重构。
MikeTheLiar 2013年

Answers:


23

Java异常有多慢?可以看到,缓慢的原因try {} catch {}在于异常本身的状态。

创建异常将从运行时获取整个调用堆栈,这是开销所在。如果你没有创建一个例外,这只是非常轻微的时间的增加。

在此问题给出的示例中,没有任何例外,不要指望创建它们会有任何放慢-没有创建它们。相反,这里是try {} finally {}在finally块中处理资源释放的方法。

因此,要回答这个问题,不,在try {} finally {}不使用异常的结构中没有实际的运行时开销(如所见,这并非闻所未闻)。什么可能是昂贵的是维修时间当一个读取的代码,并认为这不是典型的代码风格和编码器具有左右让他们记住,别的东西发生在后,这个方法return返回到以前的调用之前。


如前所述,维护是实现这两种方法的一个论据。出于记录考虑,经过考虑,我倾向于采用最终的方法。

考虑教别人新的语言结构的维护时间。眼见try {} finally {}不是那种常常可以看到一些在Java代码,因此可以迷惑人。在Java中学习比人们所熟悉的东西更高级的结构需要一定的维护时间。

finally {}始终运行。这就是为什么您应该使用它。当有人忘记在适当的时间包括登出,或者在不正确的时间调用登出,或者在调用之后忘记返回/退出以致被两次调用时,还要考虑调试非最终方法的维护时间。有这么多可能的错误,try {} finally {}使得不可能使用。

当权衡这两个成本时,维护时间会很昂贵 使用该try {} finally {}方法会增加。尽管人们可以了解该try {} finally {}块与另一版本相比有多少毫秒或额外的jvm指令,但人们还必须考虑在调试上花费的时间不是解决资源分配问题的理想方法。

首先编写可维护的代码,最好以防止以后编写错误的方式编写。


1
一位不建议的编辑这样说:“尝试/最终提供有关运行时异常的其他保证。无论您是否喜欢,任何行实际上都可以抛出异常”-我:这不是真的。问题中给出的结构没有任何其他例外,这些例外会减慢代码的性能。与非try / finally块相比,将try / final包裹在块上不会对性能造成任何其他影响。

2
与维护最终的理解相比,维护成本可能更高,那就是必须维护几个清理块。现在至少您知道,如果需要额外的清理,则只有一个地方可以进行清理。
Bart van Ingen Schenau

@BartvanIngenSchenau确实。最后尝试可能是处理它的最佳方法,但这并不是我在代码中看到的结构的共同点-人们在尝试理解最后尝试捕获和一般的异常处理方面有很多麻烦。 -最后还是没有抓住?什么?人们真的不理解。正如您所建议的那样,最好理解语言结构,而不是避免这样做并做不好事情。

我主要是想让人们知道,try-finally的替代方法也不是免费的。希望我能再为您增加费用表示赞成。
Bart van Ingen Schenau 2013年

5

/programming/299068/how-slow-are-java-exceptions

该问题的可接受答案表明,将函数调用包装在try catch块中的成本比裸函数调用少5%。实际上,抛出并捕获异常会导致运行时膨胀到裸函数调用的66倍以上。因此,如果您希望异常设计能够定期抛出,那么我会尽量避免使用(在正确配置的性能关键代码中),但是,如果这种例外情况很少见,那么没什么大不了的。


3
“如果例外情况很少见” -那么这里就有重言式。异常意味着稀有,而这正是应该使用异常的方式。切勿成为设计或控制流程的一部分。
Jesse C. Slicer 2013年

1
在实践中,异常并不一定很罕见,这只是偶然的副作用。例如,如果您的UI不好,人们可能比正常流程更经常地导致异常用例流程
Esailija 2013年

2
问题的代码中没有异常。

2
@ JesseC.Slicer在某些语言中,将其用作流控制并不罕见。作为示例,在python中进行迭代时,迭代器在完成后会引发stopiteration异常。同样在OCaml中,他们的标准库似乎非常“抛出高兴”,它抛出了很多不常见的情况(如果您有地图,并尝试查找它抛出的地图中不存在的东西)。
stonemetal

@stonemetal就像Python dict一样,带有
KeyError

4

与其优化,不如考虑您的代码在做什么。

添加finally块是不同的实现-这意味着您将注销每个异常。

具体来说-如果登录引发异常,则第二个函数的行为将与第一个函数不同。

如果登录和注销实际上不是函数行为的一部分,那么我建议该方法应该做一件事和一件事:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

并且应该在已经封装在用于管理登录/注销的上下文中的函数中,因为这些似乎与您的主要代码在本质上有所不同。

您的帖子被标记为Java,我对此并不十分熟悉,但是在.net中,我将为此使用using块:

即:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

登录上下文具有Dispose方法,该方法将注销并清理资源。

编辑:我实际上没有回答这个问题!

我没有可测量的证据,但我不希望这会更昂贵,或者肯定不会足够昂贵,以至于不值得过早优化。

我将保留其余的答案,因为尽管没有回答该问题,但我认为这对OP很有帮助。


1
为了完成您的答案,Java 7引入了一个try-with-resources语句,该语句类似于您的.net using构造,假定所讨论的资源实现了该AutoCloseable接口。
haylem 2013年

我什至会删除此ret变量,该变量只被分配了两次,待一行后检查。做吧if (dosomething() == false) return;
ott-- 2013年

同意,但是我想保留OP代码的提供方式。
迈克尔

@ ott--我假设OP的代码是其用例的简化-例如,他们可能有更多类似的东西ret2 = doSomethingElse(ret1),但这与问题无关,因此将其删除。
伊兹卡塔

if (dosomething() == false) ...是错误的代码。使用更直观if (!dosomething()) ...
Steffen Heil

1

假设:您正在使用C#代码进行开发。

快速的答案是,使用try / finally块不会对性能产生重大影响。当您引发并捕获异常时,会对性能造成影响。

更长的答案是看自己。如果查看生成的基础CIL代码(例如,红门反射器),则可以看到生成的基础CIL指令,并以此方式查看影响。


11
这个问题被标记为java
Joachim Sauer 2013年

好眼力!;)
Michael Shaw

3
同样,方法不能大写,尽管那可能只是好味道。
Erik Reppen 2013年

如果问题是c#,仍然是一个很好的答案:)
PhillyNJ

-2

正如其他人已经指出的那样,Ttese代码将产生不同的结果(如果存在dosomethingelsedootherstuff抛出异常)。

是的,任何函数调用都可能引发异常,至少一个StackOVerflowError!尽管很少有可能正确地处理这些问题(因为任何调用的清除函数都可能有相同的问题,但是在某些情况下(例如,实现锁),甚至正确地处理也是至关重要的...


2
这似乎并没有提供超过前6个答案的实质内容
gnat 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.