为什么添加try块会使程序更快?

Wei*_*Liu 13 java performance exception try-catch

我使用以下代码来测试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!");
        }
    }

    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());

        // using a try block
        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method1(i);
            } catch (Exception e) {

            }
        }

        l = System.currentTimeMillis() - l;
        System.out.println("method1 with try block took " + l + " ms, result was "
                + t.getValue());
    }
}
Run Code Online (Sandbox Code Playgroud)

我的机器运行的是64位Windows 7和64位JDK7.我得到了以下结果:

method1 took 914 ms, result was 2
method1 with try block took 789 ms, result was 2
Run Code Online (Sandbox Code Playgroud)

我已经多次运行代码,每次得到几乎相同的结果.

更新:

以下是在MacBook Pro,Java 6上运行测试十次的结果.Tre-catch使得该方法更快,与在Windows上相同.

method1 took 895 ms, result was 2
method1 with try block took 783 ms, result was 2
--------------------------------------------------
method1 took 943 ms, result was 2
method1 with try block took 803 ms, result was 2
--------------------------------------------------
method1 took 867 ms, result was 2
method1 with try block took 745 ms, result was 2
--------------------------------------------------
method1 took 856 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 862 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 859 ms, result was 2
method1 with try block took 765 ms, result was 2
--------------------------------------------------
method1 took 937 ms, result was 2
method1 with try block took 767 ms, result was 2
--------------------------------------------------
method1 took 861 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 858 ms, result was 2
method1 with try block took 744 ms, result was 2
--------------------------------------------------
method1 took 858 ms, result was 2
method1 with try block took 749 ms, result was 2
Run Code Online (Sandbox Code Playgroud)

Pet*_*rey 19

当您在同一方法中有多个长时间运行循环时,可以在第二个循环上触发整个方法的优化,并产生不可预测的结果.避免这种情况的一种方法是:

  • 给每个循环自己的方法
  • 多次运行测试以检查结果是否可重新生成
  • 运行测试2-10秒.

你会看到一些变化,有时结果是不确定的.即变异高于差异.

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!");
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        for (int i = 0; i < 5; i++) {
            testWithTryCatch(t);
            testWithoutTryCatch(t);
        }
    }

    private static void testWithoutTryCatch(Test t) {
        t.reset();
        long l = System.currentTimeMillis();
        for (int j = 0; j < 10; j++)
            for (int i = 1; i <= 100000000; i++)
                t.method1(i);

        l = System.currentTimeMillis() - l;
        System.out.println("without try/catch method1 took " + l + " ms, result was " + t.getValue());
    }

    private static void testWithTryCatch(Test t) {
        t.reset();
        long l = System.currentTimeMillis();
        for (int j = 0; j < 10; j++)
            for (int i = 1; i <= 100000000; i++)
                try {
                    t.method1(i);
                } catch (Exception ignored) {
                }

        l = System.currentTimeMillis() - l;
        System.out.println("with try/catch method1 took " + l + " ms, result was " + t.getValue());
    }
}
Run Code Online (Sandbox Code Playgroud)

版画

with try/catch method1 took 9723 ms, result was 2
without try/catch method1 took 9456 ms, result was 2
with try/catch method1 took 9672 ms, result was 2
without try/catch method1 took 9476 ms, result was 2
with try/catch method1 took 8375 ms, result was 2
without try/catch method1 took 8233 ms, result was 2
with try/catch method1 took 8337 ms, result was 2
without try/catch method1 took 8227 ms, result was 2
with try/catch method1 took 8163 ms, result was 2
without try/catch method1 took 8565 ms, result was 2
Run Code Online (Sandbox Code Playgroud)

从这些结果来看,似乎使用try/catch稍微慢一些,但并非总是如此.

在带有Java 7更新7的Windows 7,Xeon E5450上运行.

  • +1,因为"变化高于差异",这就说明了一切 (3认同)

mab*_*aba 5

我用Caliper Microbenchmark试了一下,我真的看不出有什么区别.

这是代码:

public class TryCatchBenchmark extends SimpleBenchmark {

    private int value;

    public void setUp() {
        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!");
        }
    }

    public void timeWithoutTryCatch(int reps) {
        for (int i = 1; i < reps; i++) {
            this.method1(i);
        }
    }

    public void timeWithTryCatch(int reps) {
        for (int i = 1; i < reps; i++) {
            try {
                this.method1(i);
            } catch (Exception ignore) {
            }
        }
    }

    public static void main(String[] args) {
        new Runner().run(TryCatchBenchmark.class.getName());
    }
}
Run Code Online (Sandbox Code Playgroud)

这是结果:

 0% Scenario{vm=java, trial=0, benchmark=WithoutTryCatch} 8,23 ns; ?=0,03 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=WithTryCatch} 8,13 ns; ?=0,03 ns @ 3 trials

      benchmark   ns linear runtime
WithoutTryCatch 8,23 ==============================
   WithTryCatch 8,13 =============================

如果我交换函数的顺序(让它们以相反的顺序运行),结果是:

 0% Scenario{vm=java, trial=0, benchmark=WithTryCatch} 8,21 ns; ?=0,05 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=WithoutTryCatch} 8,14 ns; ?=0,03 ns @ 3 trials

      benchmark   ns linear runtime
   WithTryCatch 8,21 ==============================
WithoutTryCatch 8,14 =============================

我会说它们基本相同.


Den*_*ret 2

我做了一些实验。

首先,我完全证实了OP的发现。即使删除第一个循环,或将异常更改为一些完全不相关的异常,只要不通过重新抛出异常来添加分支,try catch 确实会使代码更快。如果代码确实必须捕获异常(例如,如果使循环从 0 而不是 1 开始),那么代码仍然会更快。

我的“解释”是,JIT 是疯狂的优化机器,有时它们的性能比其他时候更好,如果没有在 JIT 级别进行非常具体的研究,您通常无法理解。有许多可能的事情可以改变(例如寄存器的使用)。

这是在全球范围内与 C# JIT 非常相似的情况中发现的情况。

无论如何,Java 针对 try-catch 进行了优化。由于总是存在出现异常的可能性,因此您实际上并没有通过添加 try-catch 来添加太多分支,因此发现第二个循环比第一个循环长也就不足为奇了。