Java异常有多慢?

时间:2008-11-18 15:31:11

标签: java performance exception-handling

问题:Java中的异常处理实际上是否很慢?

传统智慧以及许多Google搜索结果表明,不应将特殊逻辑用于Java中的正常程序流程。通常有两个原因,

  1. 它真的很慢 - 甚至比常规代码慢一个数量级(给出的原因各不相同),
    1. 它很混乱,因为人们只希望在特殊代码中处理错误。
    2. 这个问题是关于#1。

      作为一个例子,this page将Java异常处理描述为“非常慢”,并将缓慢与创建异常消息字符串联系起来 - “然后将此字符串用于创建抛出的异常对象。不快。“文章Effective Exception Handling in Java说“造成这种情况的原因是由于异常处理的对象创建方面因此导致异常本身变慢”。另一个原因是堆栈跟踪生成减慢了它的速度。

      我的测试(使用Java 1.6.0_07,Java HotSpot 10.0,在32位Linux上),表明异常处理并不比常规代码慢。我尝试在循环中运行一个执行一些代码的方法。在方法结束时,我使用布尔值来指示是返回还是 throw 。这样实际处理是一样的。我尝试以不同的顺序运行方法并平均我的测试时间,认为它可能是JVM升温。在我的所有测试中,投掷至少与返回一样快,如果不是更快(最多快3.1%)。我对我的测试错误的可能性持开放态度,但我没有看到代码示例,测试比较或过去一两年中显示Java中的异常处理的结果慢。

      让我走上这条道路的是我需要使用的API,它将异常作为正常控制逻辑的一部分。我想在他们的使用中纠正它们,但现在我可能无法做到。相反,我是否必须赞美他们的前瞻性思维?

      在论文Efficient Java exception handling in just-in-time compilation中,作者建议单独存在异常处理程序,即使没有抛出异常,也足以阻止JIT编译器正确优化代码,从而减慢它的速度。我还没有测试过这个理论。

18 个答案:

答案 0 :(得分:327)

这取决于如何实施例外。最简单的方法是使用setjmp和longjmp。这意味着CPU的所有寄存器都写入堆栈(已经需要一些时间),并且可能需要创建一些其他数据......所有这些都已经发生在try语句中。 throw语句需要展开堆栈并恢复所有寄存器的值(以及VM中可能的其他值)。所以try和throw同样很慢,而且速度很慢,但是如果没有抛出异常,退出try块在大多数情况下都不会花费任何时间(因为如果方法存在,所有内容都会被放到堆栈中自动清理)。

Sun和其他人认识到,这可能是次优的,当然VM随着时间的推移变得越来越快。还有另一种方法来实现异常,这使得try本身闪电般快速(实际上没有任何事情发生在一般情况下尝试 - 当VM加载类时,所有需要发生的事情已经完成)并且它使得抛出速度不是很慢。我不知道哪个JVM使用这种新的更好的技术......

...但是你是用Java编写的,所以稍后你的代码只在一个特定系统上的一个JVM上运行?既然它可能在任何其他平台或任何其他JVM版本(可能是任何其他供应商)上运行,谁说他们也使用快速实现?快速的比复杂的更复杂,并且在所有系统上都不容易实现。你想保持便携?然后不要依赖快速的例外。

它在try块中的作用也有很大的不同。如果你打开一个try块并且从不在这个try块中调用任何方法,那么try块将是超快的,因为JIT可以实际上像一个简单的goto一样处理throw。如果抛出异常,它既不需要保存堆栈状态也不需要展开堆栈(它只需要跳转到catch处理程序)。但是,这不是你通常做的。通常你打开一个try块,然后调用一个可能抛出异常的方法,对吧?即使你只是在你的方法中使用try块,这将是什么样的方法,不会调用任何其他方法?它只是计算一个数字吗?那么你需要什么例外呢?有更多优雅的方法来规范程序流程。对于除了简单的数学之外的其他任何事情,您将不得不调用外部方法,这已经破坏了本地try块的优势。

请参阅以下测试代码:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

结果:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

try块的减速太小,无法排除混淆因素,例如后台进程。但是拦截块杀死了所有东西并使它慢了66倍!

正如我所说,如果你把try / catch放在同一个方法(method3)中,结果就不会那么糟糕,但这是一个我不会依赖的特殊JIT优化。即使使用这种优化,投掷仍然很慢。所以我不知道你在这里要做什么,但肯定有比使用try / catch / throw更好的方法。

答案 1 :(得分:239)

仅供参考,我延长了Mecki所做的实验:

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

前3个和Mecki相同(我的笔记本电脑显然比较慢)。

method4与method3相同,只是它创建了new Integer(1)而不是throw new Exception()

method5与method3类似,只是它创建new Exception()而不抛出它。

method6与method3类似,不同之处在于它抛出预先创建的异常(实例变量)而不是创建新异常。

在Java中,抛出异常的大部分费用是收集堆栈跟踪所花费的时间,这是在创建异常对象时发生的。抛出异常的实际成本虽然很大,但却远远低于创建异常的成本。

答案 2 :(得分:56)

AlekseyShipilëv做了一个very thorough analysis,他在各种条件组合下对Java异常进行了基准测试:

  • 新创建的例外与预先创建的例外
  • 启用堆栈跟踪与禁用
  • 请求的堆栈跟踪vs从未请求
  • 处于最高级别,而不是每个级别的重新训练,而不是链接/包裹在每个级别
  • 各种级别的Java调用堆栈深度
  • 没有内联优化与极端内联与默认设置
  • 读取与未读取的用户定义字段

他还将它们与在各种错误频率级别检查错误代码的性能进行了比较。

结论(从他的帖子中逐字引用)是:

  1. 真正卓越的例外情况非常出色。如果您按照设计使用它们,并且只在常规代码处理的绝大多数非例外情况中传达真正例外情况,那么使用例外是表现胜利。

  2. 异常的性能成本有两个主要组成部分:实例化异常时的堆栈跟踪构造,异常抛出期间堆栈展开

  3. 在异常实例化时,堆栈跟踪构建成本与堆栈深度成比例。那已经很糟糕了,因为地球上的谁知道这个投掷方法的堆栈深度?即使您关闭堆栈跟踪生成和/或缓存异常,您也只能摆脱这部分性能成本。

  4. 堆栈展开成本取决于我们在编译代码中使异常处理程序更接近的程度。仔细构建代码以避免深度异常处理程序查找可能有助于我们更幸运。

  5. 如果我们消除这两种影响,异常的性能成本就是本地分支的性能成本。无论它听起来多么美丽,这并不意味着你应该使用Exceptions作为通常的控件流程,因为在这种情况下你可以优化编译器!你应该只在真正例外的情况下使用它们,其中异常频率摊销可能不幸的筹集成本实际例外。

  6. 乐观的经验法则似乎 10 ^ -4 异常频率非常特殊。当然,这取决于异常本身的重量级,异常处理程序中采取的确切操作等。

  7. 结果是,当没有抛出异常时,您不需要支付费用,因此当异常情况非常罕见时,异常处理比每次使用if更快。完整的帖子非常值得一读。

答案 3 :(得分:39)

遗憾的是,我的回答太长了,无法在此发布。因此,请在此总结一下,并向您推荐http://www.fuwjax.com/how-slow-are-java-exceptions/的详细信息。

这里真正的问题不是“与'永不失败的代码'相比,'故障报告为异常的速度有多慢'?”因为公认的回应可能让你相信。相反,问题应该是“与其他方式报告的故障相比,故障报告为异常的速度有多慢?”通常,报告失败的另外两种方法是使用sentinel值或使用结果包装器。

Sentinel值是尝试在成功的情况下返回一个类,而另一个在失败的情况下返回。您可以将其视为返回异常而不是抛出异常。这需要一个带有成功对象的共享父类,然后进行“instanceof”检查和几次转换以获取成功或失败信息。

事实证明,在存在类型安全风险的情况下,Sentinel值比异常更快,但只有大约2倍。现在,这可能看起来很多,但是2x只涵盖了实现差异的成本。在实践中,因为我们的方法可能失败的因素比本页其他地方的示例代码中的一些算术运算符更有趣。

结果另一方面,包装不会牺牲类型安全性。它们将成功和失败信息包装在一个类中。因此,它们不是“instanceof”而是为成功和失败对象提供“isSuccess()”和getter。但是,结果对象比使用异常大约2倍。事实证明,每次创建一个新的包装器对象比有时抛出异常要贵得多。

最重要的是,异常是指示方法可能失败的语言。除了API之外,没有其他方法可以告诉哪些方法总是(大部分)工作,哪些方法会报告失败。

例外比哨兵更安全,比结果对象更快,并且比任何一个都更不令人惊讶。我并不是建议使用try / catch替换if / else,但异常是报告失败的正确方法,即使在业务逻辑中也是如此。

尽管如此,我想指出的是,我遇到的两种最常见的影响性能的方法是创建不必要的对象和嵌套循环。如果您可以在创建异常或不创建异常之间进行选择,请不要创建异常。如果您可以选择有时创建异常或始终创建另一个对象,则创建异常。

答案 4 :(得分:18)

我扩展了@Mecki@incarnate给出的答案,没有堆栈跟踪填充Java。

使用Java 7+,我们可以使用Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace)。但是对于Java6,请参阅my answer for this question

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

使用Java 1.6.0_45输出,在Core i7上,8GB RAM:

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

因此,与抛出异常的方法相比,返回值的方法仍然更快。恕我直言,我们无法使用返回类型设计一个明确的API,无论是成功还是成功。错误流动。抛出异常而没有堆栈跟踪的方法比正常异常快4-5倍。

编辑:NoStackTraceThrowable.java 谢谢@Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}

答案 5 :(得分:7)

我认为第一篇文章是指遍历调用堆栈并创建堆栈跟踪作为昂贵部分的行为,虽然第二篇文章没有说明,但我认为这是对象创建中最昂贵的部分。 John Rose有an article where he describes different techniques for speeding up exceptions。 (预分配和重用异常,没有堆栈跟踪的异常等)

但仍然 - 我认为这应该只被视为必要的邪恶,不得已。 John这样做的原因是模拟JVM中尚未提供的其他语言的功能。你不应该养成使用控制流异常的习惯。特别是出于性能原因!正如你自己在#2中提到的那样,你冒着以这种方式掩盖代码中的严重错误的风险,并且对新程序员来说维护起来会更困难。

Java中的Microbenchmarks非常难以做到(我被告知),尤其是当你进入JIT领域时,所以我真的怀疑使用异常比现实生活中的“回归”更快。例如,我怀疑你的测试中有2到5个堆栈帧?现在假设您的代码将由JBoss部署的JSF组件调用。现在您可能有几页长的堆栈跟踪。

也许您可以发布测试代码?

答案 6 :(得分:7)

前段时间我写了一个类来测试使用两种方法将字符串转换为int的相对性能:(1)调用Integer.parseInt()并捕获异常,或者(2)将字符串与正则表达式匹配并调用parseInt()仅在匹配成功时使用。我以最有效的方式使用正则表达式(即,在循环之前创建Pattern和Matcher对象),并且我没有打印或保存异常中的堆栈跟踪。

对于一万个字符串的列表,如果它们都是有效数字,则parseInt()方法的速度是正则表达式方法的四倍。但如果只有80%的字符串有效,那么正则表达式的速度是parseInt()的两倍。如果20%是有效的,意味着异常被抛出并且有80%的时间被捕获,那么正则表达式的速度大约是parseInt()的20倍。

我对结果感到惊讶,因为正则表达式方法处理两次有效字符串:一次用于匹配,另一次用于parseInt()。但抛出和捕获异常不仅仅是为了弥补这一点。在现实世界中,这种情况不太可能经常发生,但如果确实如此,你肯定不应该使用异常捕获技术。但是,如果您只是验证用户输入或类似的东西,请务必使用parseInt()方法。

答案 7 :(得分:7)

不知道这些主题是否相关,但我曾经想要依靠当前线程的堆栈跟踪实现一个技巧:我想发现方法的名称,它触发了实例化类中的实例化(是的,这个想法很疯狂,我完全放弃了)。所以我发现调用Thread.currentThread().getStackTrace() 非常慢(由于它在内部使用的原生dumpThreads方法)。

因此,Java Throwable相应地具有本机方法fillInStackTrace。我认为前面描述的杀手 - catch块以某种方式触发了这种方法的执行。

但是让我告诉你另一个故事......

在Scala中,一些功能特性使用ControlThrowable在JVM中编译,扩展Throwable并以下列方式覆盖其fillInStackTrace

override def fillInStackTrace(): Throwable = this

所以我调整了上面的测试(周期数减少了10,我的机器有点慢了):

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

所以,结果是:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

您看,method3method4之间的唯一区别是它们会抛出不同类型的异常。是的,method4仍然比method1method2慢,但差异更大可以接受。

答案 8 :(得分:6)

我已经使用JVM 1.5进行了一些性能测试,并且使用异常至少慢了2倍。平均而言:一个简单的小方法的执行时间超过三倍(3x),但有例外。一个非常小的循环必须捕获异常,自我时间增加了2倍。

我在生产代码和微基准测试中看到了相似的数字。

异常应该 NOT 用于经常调用的任何内容。每秒抛出数以千计的异常将导致巨大的瓶颈。

例如,使用“Integer.ParseInt(...)”查找非常大的文本文件中的所有错误值 - 非常糟糕。 (我已经在生产代码中看到了这个实用方法 kill 性能)

使用异常来报告用户GUI表单上的错误值,从性能角度来看可能不是那么糟糕。

无论它是否是一个好的设计实践,我都会遵循以下规则:如果错误是正常的/预期的,那么使用返回值。如果不正常,请使用例外。例如:读取用户输入,错误值是正常的 - 使用错误代码。将值传递给内部实用程序函数时,应通过调用代码来过滤错误值 - 使用异常。

答案 9 :(得分:3)

即使抛出异常并不慢,但为正常的程序流抛出异常仍然是个坏主意。使用这种方式它类似于GOTO ......

我想这并没有真正回答这个问题。我想象在早期的Java版本中(&lt; 1.4),抛出异常的“常规”智慧是正确的。创建异常需要VM创建整个堆栈跟踪。从那以后,在VM中发生了很多变化,以加快速度,这可能是一个已经改进的领域。

答案 10 :(得分:3)

HotSpot能够为系统生成的异常删除异常代码,只要它全部内联即可。但是,显式创建的异常和其他未删除的异常会花费大量时间来创建堆栈跟踪。覆盖fillInStackTrace以了解这会如​​何影响效果。

答案 11 :(得分:3)

Java和C#中的异常性能还有很多不足之处。

作为程序员,这迫使我们按照规则生活“异常应该不经常造成”,仅仅是出于实际的性能原因。

然而,作为计算机科学家,我们应该反对这个有问题的国家。创作函数的人通常不知道调用它的频率,或者更有可能成功或失败。只有来电者才有此信息。试图避免异常导致API不清楚,在某些情况下我们只有干净但缓慢的异常版本,而在其他情况下,我们有快速但笨重的返回值错误,而在其他情况下,我们最终都会。库实现者可能必须编写和维护两个版本的API,并且调用者必须决定在每种情况下使用哪两个版本。

这有点乱。如果异常具有更好的性能,我们可以避免使用这些笨重的习语,并使用异常,因为它们是用作...作为结构化错误返回工具。

我真的希望看到使用更接近返回值的技术实现异常机制,因此我们可以使性能更接近返回值..因为这是我们在性能敏感代码中恢复的内容。

这是一个代码示例,它将异常性能与错误返回值性能进行比较。

公共类TestIt {

int value;


public int getValue() {
    return value;
}

public void reset() {
    value = 0;
}

public boolean baseline_null(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        return shouldfail;
    } else {
        return baseline_null(shouldfail,recurse_depth-1);
    }
}

public boolean retval_error(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            return false;
        } else {
            return true;
        }
    } else {
        boolean nested_error = retval_error(shouldfail,recurse_depth-1);
        if (nested_error) {
            return true;
        } else {
            return false;
        }
    }
}

public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            throw new Exception();
        }
    } else {
        exception_error(shouldfail,recurse_depth-1);
    }

}

public static void main(String[] args) {
    int i;
    long l;
    TestIt t = new TestIt();
    int failures;

    int ITERATION_COUNT = 100000000;


    // (0) baseline null workload
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                t.baseline_null(shoulderror,recurse_depth);
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }


    // (1) retval_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                if (!t.retval_error(shoulderror,recurse_depth)) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }

    // (2) exception_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                try {
                    t.exception_error(shoulderror,recurse_depth);
                } catch (Exception e) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);              
        }
    }
}

}

以下是结果:

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

检查和传播返回值确实会增加基线空调用的成本,并且该成本与调用深度成比例。在调用链深度为8时,错误返回值检查版本比没有检查返回值的基线版本慢约27%。

相比之下,异常性能不是调用深度的函数,而是异常频率的函数。然而,作为例外频率增加的降级更加剧烈。错误频率仅为25%,代码运行速度慢24小时。在错误频率为100%时,异常版本几乎慢100倍。

这告诉我,在我们的异常实现中可能正在做出错误的权衡。通过避免代价高昂的跟踪行走,或者通过直接将它们转换为编译器支持的返回值检查,异常可能会更快。在他们这样做之前,当我们希望我们的代码快速运行时,我们就会避免使用它们。

答案 12 :(得分:2)

比较一下让我们说Integer.parseInt到下面的方法,它只是在不可解析数据的情况下返回默认值而不是抛出异常:

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

只要将两种方法应用于“有效”数据,它们都将以大致相同的速率工作(即使Integer.parseInt设法处理更复杂的数据)。但是,只要您尝试解析无效数据(例如,解析“abc”1.000.000次),性能差异就应该是必不可少的。

答案 13 :(得分:1)

使用附加的代码,在 JDK 15 上,@Mecki 测试用例得到完全不同的结果。这基本上是在 5 个循环中运行代码,第一个循环稍微短一些,以便给 VM 一些时间来预热。

结果:

class Warrior(base_human):
    def __init__(self, name, health, magic, speed):
        super().__init__(health, magic, speed)
        self.name = name
Loop 1 10000 cycles
method1 took 1 ms, result was 2
method2 took 0 ms, result was 2
method3 took 22 ms, result was 2
method4 took 22 ms, result was 2
method5 took 24 ms, result was 2
Loop 2 10000000 cycles
method1 took 39 ms, result was 2
method2 took 39 ms, result was 2
method3 took 1558 ms, result was 2
method4 took 1640 ms, result was 2
method5 took 1717 ms, result was 2
Loop 3 10000000 cycles
method1 took 49 ms, result was 2
method2 took 48 ms, result was 2
method3 took 126 ms, result was 2
method4 took 88 ms, result was 2
method5 took 87 ms, result was 2
Loop 4 10000000 cycles
method1 took 34 ms, result was 2
method2 took 34 ms, result was 2
method3 took 33 ms, result was 2
method4 took 98 ms, result was 2
method5 took 58 ms, result was 2
Loop 5 10000000 cycles
method1 took 34 ms, result was 2
method2 took 33 ms, result was 2
method3 took 33 ms, result was 2
method4 took 48 ms, result was 2
method5 took 49 ms, result was 2

答案 14 :(得分:1)

关于异常表现的好帖子是:

https://shipilev.net/blog/2014/exceptional-performance/

实例化和重用现有的,使用堆栈跟踪和没有等等:

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

取决于堆栈跟踪的深度:

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

有关其他详细信息(包括来自JIT的x64汇编程序),请阅读原始博文。

这意味着Hibernate / Spring / etc-EE-shit很慢,因为异常(xD)和重写应用程序控制流程远离异常(将其替换为continure / break并返回{{1像方法调用C中的标志)可以提高应用程序10x-100x的性能,具体取决于你抛出它们的频率))

答案 15 :(得分:0)

我改变了@Mecki上面的答案,让method1返回一个布尔值并检查调用方法,因为你不能只用一个替换异常。两次运行后,method1仍然是方法2的最快或最快。

以下是代码的快照:

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

和结果:

运行1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

运行2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2

答案 16 :(得分:-3)

我对异常速度与以编程方式检查数据的看法。

许多类都有String to value converter(扫描器/解析器),受人尊敬的知名库;)

通常有表格

class Sample < ActiveRecord::Base
  def appearance=(value)
    write_attribute(:appearance, value.gsub(/\./, ':'))
  end
end

异常名称只是示例,通常是未选中的(运行时),所以抛出声明只是我的图片

有时存在第二种形式:

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

永不投掷

当第二个不可用时(或者程序员阅读的文档太少而且只使用第一个),用正则表达式编写这样的代码。正则表达很酷,政治正确等等:

public static Example Parse(String input, Example defaultValue)

使用此代码程序员不需要例外。但是正常表达式的成本非常高,总是与有时候的小成本相比。

我几乎总是在这样的环境中使用

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

没有分析堆栈跟踪等,我相信你的讲座速度非常快。

不要害怕例外

答案 17 :(得分:-5)

为什么异常会比正常回报慢?

只要不将堆栈跟踪打印到终端,将其保存到文件或类似文件中,catch-block就不会比其他代码块做更多的工作了。所以,我无法想象为什么“抛出新的my_cool_error()”应该那么慢。

好问题,我期待有关此主题的更多信息!