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

Posted

技术标签:

【中文标题】为啥添加 try 块会使程序更快?【英文标题】:Why adding a try block makes the program faster?为什么添加 try 块会使程序更快? 【发布时间】:2012-10-02 22:25:02 【问题描述】:

我正在使用以下代码来测试 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());
    

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

method1 took 914 ms, result was 2
method1 with try block took 789 ms, result was 2

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

更新:

这是在 MacBook Pro、Java 6 上运行测试十次的结果。Try-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

【问题讨论】:

这超出了问题的范围,但是您应该使用System.nanoTime来比较数据。阅读System.currentTimeMillis vs System.nanoTime。 @RahulAgrawal 我交换了代码并得到了相同的结果。 我围绕 OP 的代码做了很多测试,我证实了他的发现。 是的:添加一个try catch,即使是完全不相关的错误或异常,确实可以使代码更快。如果你在 catch 中重新抛出异常,它并不会让它更快。 您好,请查看***.com/questions/8423789/…,它将引导您访问 IBM 公司的论文:ibm.com/developerworks/java/library/j-benchmark1/index.html,它将向您展示如何执行正确的 java 代码基准测试。 【参考方案1】:

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

为每个循环提供自己的方法 多次运行测试以检查结果是否可重现 运行测试 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());
    

打印

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 E5450 和 Java 7 更新 7 上运行。

【讨论】:

在我的机器上,testWithoutTryCatch() 总是从第三次开始变得更快。 Intel(R) Core(TM) i5-3450, PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 58 Stepping 9, GenuineIntel +1,表示“变化大于差异”,这说明了一切【参考方案2】:

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

结果如下:

0% 场景vm=java, trial=0, benchmark=WithoutTryCatch 8,23 ns; σ=0,03 ns @ 3 次试验 50% 场景vm=java, trial=0, benchmark=WithTryCatch 8,13 ns; σ=0,03 ns @ 3 次试验 基准 ns 线性运行时 没有TryCatch 8,23 =============================== With TryCatch 8,13 ==============================

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

0% 场景vm=java, trial=0, benchmark=WithTryCatch 8,21 ns; σ=0,05 ns @ 3 次试验 50% 场景vm=java, trial=0, benchmark=WithoutTryCatch 8,14 ns; σ=0,03 ns @ 3 次试验 基准 ns 线性运行时 With TryCatch 8,21 =============================== 没有TryCatch 8,14 ==============================

我会说它们基本上是一样的。

【讨论】:

【参考方案3】:

我做了一些实验。

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

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

This is globally what was found in a very similar case with a C# JIT.

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

【讨论】:

【参考方案4】:

为了避免 JVM 和操作系统可能执行的任何隐藏优化或缓存,我首先开发了两个种子 java 程序,TryBlockNoTryBlock,它们的区别在于是否使用 try 块。这两个种子程序将用于生成不同的程序以禁止 JVM 或 OS 进行隐藏优化。每次测试都会生成并编译一个新的java程序,我重复测试10次。

根据我的实验,不使用 try 块运行平均需要 9779.3 毫秒,而使用 try 块运行需要 9775.9 毫秒:它们的平均运行时间相差 3.4 毫秒(或 0.035%),这可以被视为噪音。这表明使用 void try 块(我所说的 void,我的意思是除了空指针异常之外不存在任何可能的异常)或不使用似乎对运行时间没有影响。

测试在同一台linux机器(cpu 2392MHz)和java版本“1.6.0_24”下运行。

下面是我基于种子程序生成测试程序的脚本:

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

这里是种子程序,首先是NoTryBlock.java

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

第二个是TryBlock.java,在方法函数调用上使用了try-block:

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 块是它们唯一的区别:

$ 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 "

下面是输出:

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

【讨论】:

以上是关于为啥添加 try 块会使程序更快?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 subView 中添加 UILabel 会使其像素化

为啥添加导航控制器会使我的数组消失?

为啥 Java switch on contiguous ints 似乎在添加案例时运行得更快?

在 Boost Graph Library 中,为啥添加边会使边迭代器(和其他问题)无效?

为啥在 FROM 子句中再添加一个 INNER JOIN 会使我的 SQL 查询如此缓慢?

R - 为啥向数据表添加 1 列几乎会使使用的峰值内存增加一倍?