15

I am using the following code to test how slow a try block is. To my surprise, the try block makes it faster. Why?

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

My machine is running 64-bit Windows 7 and 64-bit JDK7. I got the following result:

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

And I have run the code many times and every time I got almost the same result.

Update:

Here is the result of running the test ten times on a MacBook Pro, Java 6. Try-catch makes the method faster, same as on 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
Curious
  • 2,783
  • 3
  • 29
  • 45
Wei Liu
  • 555
  • 8
  • 24
  • 1
    This is out of the scope of the question, but you should use `System.nanoTime` to compare the data. Read [System.currentTimeMillis vs System.nanoTime](http://stackoverflow.com/q/351565/1065197). – Luiggi Mendoza Oct 12 '12 at 06:28
  • 2
    @RahulAgrawal I swapped the code and got the same result. – Wei Liu Oct 12 '12 at 06:34
  • 2
    I made many tests around OP's code, and I confirm his finding. – Denys Séguret Oct 12 '12 at 06:37
  • So did you got the difference ? – Rahul Agrawal Oct 12 '12 at 06:38
  • 1
    Yes : adding a try catch, even with a totally irrelevant error or exception, does make the code faster. It does not make it faster if you rethrow the exception in the catch. – Denys Séguret Oct 12 '12 at 06:40
  • 1
    Hello, have a look on http://stackoverflow.com/questions/8423789/benchmarking-inside-java-code which would lead you to the paper from IBM company: http://www.ibm.com/developerworks/java/library/j-benchmark1/index.html it would show you how to perform correct java code benchmarking. – zubrabubra Oct 12 '12 at 07:44
  • @AndrewThompson I corrected the code. – Wei Liu Oct 12 '12 at 08:16

4 Answers4

19

When you have multiple long running loops in the same method, one can trigger the optimisation of the whole method with unpredictable results on the second loop. One way to avoid this is;

  • to give each loop its own method
  • run the tests multiple times to check the result is re-producible
  • run the test for 2 - 10 seconds.

You will see some variation and sometimes the results are inconclusive. i.e. variation is higher than the difference.

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

prints

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

From these results, it might appear that with try/catch is marginally slower, but not always.

Run on Windows 7, Xeon E5450 with Java 7 update 7.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
5

I tried it out with Caliper Microbenchmark and I really couldn't see a difference.

Here's the code:

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

And here is the result:

 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 =============================

If I swap the order of the functions (to get them to run in reverse order) the result is:

 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 =============================

I would say that they are basically the same.

maba
  • 47,113
  • 10
  • 108
  • 118
2

I have made a few experimentations.

To start with, I totally confirm the finding of OP. Even if removing the first loop, or changing the exception to some totally irrelevant one, the try catch, as long as you don't add a branching by rethrowing the exception, does make the code faster. The code if still faster if it really has to catch an exception (if you make the loop start at 0 instead of 1 for example).

My "explanation" is that JIT are wild optimizing machines and that sometimes they perfom better than some other times, in ways you can't generally understand without very specific study at the JIT level. There are many possible things that can change (use of registers for example).

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

In any case, Java is optimized for try-catch. As there is always the possibility of an exception, you don't really add much branching by adding a try-catch, so it's not surprising to not find the second loop longer than the first.

Community
  • 1
  • 1
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
1

To avoid any hidden optimization or cache that could be performed by the JVM and the OS, I first developed two seed java programs, TryBlock and NoTryBlock, where their difference is use a try block or not. These two seed programs will be used to generate different programs to disallow JVM or OS to do hidden optimization. In each test, a new java program will be generated and compiled, and I repeated the test 10 times.

Based on my experiment, running without try block takes 9779.3 ms in average while running with try block takes 9775.9ms: a 3.4ms (or 0.035%) difference in their average running time, which can be viewed as noise. This indicates that using a void try block (by void, I mean other than null-pointer exception there exists no possible exceptions) or not seems not have impact on running time.

The test is running on same linux machine (cpu 2392MHz) and under java version "1.6.0_24".

Below is my script for generating testing programs based on the seed programs:

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

Here are the seed programs, first is the 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());
    }
}

the second is the TryBlock.java, which uses a try-block on the method function call:

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

Below is the diff of my two seed programs, and you can see except the class name, the try block is their only difference:

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

Below is the output:

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
Debosmit Ray
  • 5,228
  • 2
  • 27
  • 43
keelar
  • 5,814
  • 7
  • 40
  • 79