代码之家  ›  专栏  ›  技术社区  ›  Kai Huppmann

例外情况有多贵[重复]

  •  22
  • Kai Huppmann  · 技术社区  · 16 年前

    你知道java中抛出和处理异常的代价有多高吗?

    我们团队就异常的实际成本进行了几次讨论。一些人尽可能多地避免它们,一些人说使用异常造成的性能损失被高估了。

    private void doSomething()
    {
        try
        {
          doSomethingElse();
        }
        catch(DidNotWorkException e)
        {
           log("A Message");
        }
        goOn();
    }
    private void doSomethingElse()
    {
       if(isSoAndSo())
       {
          throw new DidNotWorkException();
       }
       goOnAgain();
    }
    

    与此相比,其性能如何

    private void doSomething()
    {
        doSomethingElse();
        goOn();
    }
    private void doSomethingElse()
    {
       if(isSoAndSo())
       {
          log("A Message");
          return;
       }
       goOnAgain();
    }
    

    你有真实的经验/测量吗?

    10 回复  |  直到 16 年前
        1
  •  12
  •   Eric Wilson    13 年前

    例外情况并非免费。..所以它们很贵:-)

    Effective Java

    • 第39项仅在特殊情况下使用例外。
    • 第40项使用可恢复条件的例外情况

    作者发现,在他使用特定VM和OS组合的机器上,异常导致他的测试用例的代码调优速度慢了70倍。

        2
  •  12
  •   Bill the Lizard    13 年前

    filling in the stack trace .

    如果预先创建异常并重用它,JIT可能会将其优化为“ a machine level goto ."

    尽管如此,除非你的问题中的代码处于一个非常紧密的循环中,否则差异将可以忽略不计。

        3
  •  9
  •   Community CDub    8 年前

    关于异常的缓慢部分是构建堆栈跟踪(在构造函数中 java.lang.Throwable ),这取决于堆叠深度。投掷本身并不缓慢。

    使用异常来表示故障。因此,性能影响可以忽略不计,堆栈跟踪有助于确定故障原因。

    如果你需要控制流的异常(不推荐),并且分析显示异常是瓶颈,那么创建一个覆盖的Exception子类 fillInStackTrace() 一个空的实现。或者(或另外)只实例化一个异常,将其存储在字段中,并始终抛出相同的实例。

    下面通过在微基准测试中添加一个简单的方法来演示没有堆栈跟踪的异常( albeit flawed )在 accepted answer :

    public class DidNotWorkException extends Exception {
      public Throwable fillInStackTrace() {
          return this;
      }
    }
    

    在中使用JVM运行它 -server 模式(Windows 7上的版本1.6.0_24)导致:

    Exception:99ms
    Boolean:12ms
    
    Exception:92ms
    Boolean:11ms
    

        4
  •  6
  •   Henry B    16 年前

    我没有费心去阅读Exception,但用你的一些修改后的代码做了一个非常快速的测试,我得出的结论是,Exception情况比布尔情况慢得多。

    我得到了以下结果:

    Exception:20891ms
    Boolean:62ms
    

    public class Test {
        public static void main(String args[]) {
                Test t = new Test();
                t.testException();
                t.testBoolean();
        }
        public void testException() {
                long start = System.currentTimeMillis();
                for(long i = 0; i <= 10000000L; ++i)
                        doSomethingException();
                System.out.println("Exception:" + (System.currentTimeMillis()-start) + "ms");
        }
        public void testBoolean() {
                long start = System.currentTimeMillis();
                for(long i = 0; i <= 10000000L; ++i)
                        doSomething();
                System.out.println("Boolean:" + (System.currentTimeMillis()-start) + "ms");
        }
    
        private void doSomethingException() {
            try {
              doSomethingElseException();
            } catch(DidNotWorkException e) {
               //Msg
            }
        }
        private void doSomethingElseException() throws DidNotWorkException {
           if(!isSoAndSo()) {
              throw new DidNotWorkException();
           }
        }
        private void doSomething() {
            if(!doSomethingElse())
                ;//Msg
        }
        private boolean doSomethingElse() {
           if(!isSoAndSo())
              return false;
           return true;
        }
        private boolean isSoAndSo() { return false; }
        public class DidNotWorkException extends Exception {}
    }
    

    我愚蠢地没有很好地阅读我的代码,之前有一个错误(多么尴尬),如果有人能对这段代码进行三重检查,我会非常感激的,以防我老了。

    我的规格是:

    • 在1.5.0_16上编译和运行
    • 前两副图中
    • WinXP SP3
    • 英特尔迅驰双核T7200(2.00Ghz,977Mhz)
    • 2.00 GB内存

    在我看来,你应该注意到,非异常方法不会在doSomethingElse中给出日志错误,而是返回一个布尔值,这样调用代码就可以处理失败。如果存在多个可能失败的区域,则可能需要在其中记录错误或抛出异常。

        5
  •  4
  •   Thorbjørn Ravn Andersen    16 年前

    对于您列出的代码片段,我个人要求原作者彻底记录他为什么在这里使用异常抛出,因为这不是对以后维护它至关重要的“最小意外之路”。

    例外是一个非常非常有用的工具,但只应在必要时使用:)

        6
  •  3
  •   Frederik Gheysels    16 年前

    我没有实际的测量值,但抛出异常的代价更高。

    好的,这是一个关于的链接。NET框架,但我认为这同样适用于Java:

    exceptions & performance

    也就是说,在以下情况下,你应该毫不犹豫地使用它们 也就是说:不要将它们用于流量控制,而是在发生异常情况时使用它们;你没想到会发生的事情。

        7
  •  3
  •   Mohit Chakraborty    16 年前


    在您给出的示例中,看起来失败并不是意外或灾难性的,因此该方法应该真正返回一个bool来表示其成功状态,而不是使用异常,从而使它们成为常规控制流的一部分。

        8
  •  3
  •   Kai Huppmann    16 年前

    感谢您的所有回复。

    我最终采纳了Thorbjrn的建议,编写了一个小测试程序,亲自测量性能。结果是: 两种变体之间的差异(在性能方面)。

    尽管我没有问代码美学或其他问题,即异常的意图是什么等,但你们大多数人也谈到了这个话题。但事实上,事情并不总是那么清楚。..在所考虑的情况下,代码诞生于很久以前,当时抛出异常的情况似乎是异常的。如今,库的使用方式不同,不同应用程序的行为和使用方式也发生了变化,测试覆盖率不是很好,但代码仍然能完成它的工作,只是有点太慢了(这就是我要求性能的原因!!)。在这种情况下,我认为,从a改为B应该有一个很好的理由,在我看来,这不能是“这不是例外的原因!”。

    事实证明,日志记录(“消息”)是(与其他发生的事情相比) 非常 很贵,所以我想,我会把它扔掉的。

    编辑 :

    测试代码与原始帖子中的代码完全相同,由一个方法调用 testPerfomance() 在一个被包围的环中 System.currentTimeMillis() -调用以获取执行时间。..但是:

    我现在查看了测试代码,忽略了其他所有内容(日志语句),循环次数比以前多了100倍,结果发现,使用原始帖子中的B而不是a时,100万次调用节省了4.7秒。正如罗恩所说 fillStackTrace 这是最昂贵的部分(+1),如果你覆盖它(在你不需要它的情况下,就像我一样),你可以节省几乎相同的时间(4.5秒)。总的来说,在我的情况下,差异仍然几乎为零,因为代码每小时被调用1000次,测量结果显示我可以在这段时间内节省4.5毫秒。..

    所以,我上面的第一部分回答有点误导,但我所说的平衡重构的成本效益仍然是正确的。

        9
  •  0
  •   Neil Coffey    16 年前

    我认为你问这个问题的角度有点错误。例外情况旨在用于发出信号 只有在例外情形下 ,作为a 程序流机制

    例外情况通常被设计为在预期用途中表现良好。如果它们的使用方式使其成为瓶颈,那么最重要的是,这可能表明它们只是被用于“错误的事情”——也就是说,你实际上拥有的是一个程序 设计 问题。

        10
  •  0
  •   Jason Plank Maksim Kondratyuk    13 年前

    如果没有,如果 DoSomething() 方法必须做 工作量(对其他方法的大量调用等)?

    1.

    try
    {
       DoSomething();
    }
    catch (...)
    {
       ...
    }
    

    2.

    DoSomething();