Why adding a try block makes the program faster?
我使用以下代码来测试try块的速度有多慢。令我惊讶的是,Try块使它更快。为什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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()); } } |
我的机器正在运行64位Windows7和64位JDK7。我得到了以下结果:
1 2 | method1 took 914 ms, result was 2 method1 with try block took 789 ms, result was 2 |
我已经运行了很多次代码,每次我得到几乎相同的结果。
更新:
这是在MacBook Pro,Java 6上运行测试十次的结果。Try-Catch使方法更快,与Windows上的方法相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 |
当您在同一个方法中有多个长时间运行的循环时,您可以在第二个循环上使用不可预知的结果来触发整个方法的优化。避免这种情况的一种方法是;
- 为每个循环提供自己的方法
- 多次运行测试以检查结果是否可重复生产。
- 运行测试2-10秒。
你会看到一些变化,有时结果是不确定的。即变化大于差异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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()); } } |
印刷品
1 2 3 4 5 6 7 8 9 10 | 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 |
从这些结果来看,使用Try/Catch可能会稍微慢一点,但并非总是如此。
在Windows 7、Xeon E54中运行Java 7更新7。
我用卡尺微基准测试了一下,我真的看不出有什么不同。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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()); } } |
结果是:
1 2 3 4 5 6 | 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 ============================= |
如果我交换函数的顺序(使它们以相反的顺序运行),结果是:
1 2 3 4 5 6 | 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 ============================= |
我想说它们基本上是一样的。
我做了一些实验。
首先,我完全确认了op的发现。即使删除第一个循环,或者将异常更改为完全无关的循环,只要不通过重新引发异常来添加分支,try catch也会使代码更快。如果代码真的必须捕获异常(例如,如果使循环从0开始而不是1),那么它的速度会更快。
我的"解释"是,JIT是一种疯狂的优化机器,有时它们的性能比其他一些时候更好,在某些方面,如果没有在JIT级别进行非常具体的研究,您通常无法理解。有许多可能的事情可以改变(例如使用寄存器)。
这是在全球范围内发现的,在一个非常类似的情况下,一个c jit。
在任何情况下,Java都是为尝试捕获而优化的。由于总是有可能出现异常,因此通过添加try catch实际上不会添加太多分支,因此发现第二个循环比第一个循环长并不奇怪。
为了避免JVM和OS可以执行的任何隐藏的优化或缓存,我首先开发了两个种子Java程序EDCOX1,0和EDCOX1,1,它们的区别是使用一个试块。这两个种子程序将用于生成不同的程序,以禁止JVM或OS进行隐藏优化。在每个测试中,将生成并编译一个新的Java程序,并且我重复测试10次。
根据我的实验,没有试块的运行平均需要9779.3ms,而使用试块的运行平均需要9775.9ms:平均运行时间相差3.4ms(或0.035%),这可以看作是噪音。这表明使用一个void try块(void,我的意思是除了空指针异常之外,不存在任何可能的异常)或不影响运行时间。
测试运行在相同的Linux机器(CPU 2492MHz)和Java版本"1.60y24"上。
下面是我基于种子程序生成测试程序的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | for i in `seq 1 10`; do echo"NoTryBlock$i" cp NoTryBlock.java NoTryBlock$i.java find . -name"NoTryBlock$i.java" -print | xargs sed -i"s/NoTryBlock/NoTryBlock$i/g"; javac NoTryBlock$i.java; java NoTryBlock$i rm NoTryBlock$i.* -f; done for i in `seq 1 10`; do echo"TryBlock$i" cp TryBlock.java TryBlock$i.java find . -name"TryBlock$i.java" -print | xargs sed -i"s/TryBlock/TryBlock$i/g"; javac TryBlock$i.java; java TryBlock$i rm TryBlock$i.* -f; done |
下面是种子计划,首先是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import java.util.*; import java.lang.*; public class NoTryBlock { 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, j; long l; NoTryBlock t = new NoTryBlock(); // using a try block l = System.currentTimeMillis(); t.reset(); for (j = 1; j < 10; ++j) { for (i = 1; i < 100000000; i++) { t.method1(i); } } l = System.currentTimeMillis() - l; System.out.println( "method1 with try block took" + l +" ms, result was" + t.getValue()); } } |
第二个是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import java.util.*; import java.lang.*; public class TryBlock { 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, j; long l; TryBlock t = new TryBlock(); // using a try block l = System.currentTimeMillis(); t.reset(); for (j = 1; j < 10; ++j) { 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()); } } |
下面是我的两个种子程序的差异,您可以看到除了类名之外,try块是它们唯一的区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ diff TryBlock.java NoTryBlock.java 4c4 < public class TryBlock { --- > public class NoTryBlock { 27c27 < TryBlock t = new TryBlock(); --- > NoTryBlock t = new NoTryBlock(); 34d33 < try { 36,37d34 < } catch (Exception e) { < } 42c39 < "method1 with try block took" + l +" ms, result was" --- > "method1 without try block took" + l +" ms, result was" |
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | method1 without try block took,9732,ms, result was 2 method1 without try block took,9756,ms, result was 2 method1 without try block took,9845,ms, result was 2 method1 without try block took,9794,ms, result was 2 method1 without try block took,9758,ms, result was 2 method1 without try block took,9733,ms, result was 2 method1 without try block took,9763,ms, result was 2 method1 without try block took,9893,ms, result was 2 method1 without try block took,9761,ms, result was 2 method1 without try block took,9758,ms, result was 2 method1 with try block took,9776,ms, result was 2 method1 with try block took,9751,ms, result was 2 method1 with try block took,9767,ms, result was 2 method1 with try block took,9726,ms, result was 2 method1 with try block took,9779,ms, result was 2 method1 with try block took,9797,ms, result was 2 method1 with try block took,9845,ms, result was 2 method1 with try block took,9784,ms, result was 2 method1 with try block took,9787,ms, result was 2 method1 with try block took,9747,ms, result was 2 |