0

Is there an efficiency difference between:-

public boolean canDivisionBeDone(int iA, int iB){

  try{
      float a = iA/iB;
    }catch(Exception e){
    return false;
 }

return true;
}

and

public boolean canDivisionBeDone(int iA, int iB){

 if(iB == 0){
     return false;
 }else{
         float a = iA/iB;
 }

return true;
}

If yes, why?

Sreekanth Karumanaghat
  • 3,383
  • 6
  • 44
  • 72
  • 3
    `try-catch` decrease readability. `return iB==0?false:true;` – Hadi J Apr 16 '18 at 05:13
  • @HadiJ I was asking about performance efficiency. In terms of CPU consumption, time taken, memory etc. – Sreekanth Karumanaghat Apr 16 '18 at 05:15
  • The example is not very good for comparing performance. Anyway, depending on the need to use it and you can not leave it behind its performance. – Hadi J Apr 16 '18 at 05:19
  • 4
    @HadiJ Unnecessary ternary operators decrease readability, too. :-) `return iB != 0`. Though I suspect this is just a snippet, and the OP is actually doing something with `a` after they calculate it. – yshavit Apr 16 '18 at 05:19
  • https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java – lexicore Apr 16 '18 at 05:20
  • Also, OP: note that you're performing integer arithmetic there, not floating-point. To Hadi's point, right now you should focus on correctness, not micro-optimizations. To answer your question, I'm not 100% sure which would be faster, but I imagine the non-catch approach would be. In the non-0 case the work is the same, and in the 0 case it's less. – yshavit Apr 16 '18 at 05:24
  • @yshavit suppose this code is inside a loop that does 10000 iterations, is micro optimization important? – Sreekanth Karumanaghat Apr 16 '18 at 05:27
  • @SreekanthKarumanaghat it all depends on the number of exception you expect on those 10k iteration. Creating a stack trace and catching it is expensive (you can test that yourself easily). So if you expect more than a few **exception** (the term is explicit by the way), this will be a problem. Of course, I don't think using an `Exception` as the "correct" execution of the method a good idea. – AxelH Apr 16 '18 at 05:33
  • @AxelH Why is this a duplicate? The question you linked to is about the case when exception is not thrown. In this case the exception may be thrown. – lexicore Apr 16 '18 at 06:36
  • @Andreas I disagree it is a duplicate. In this case, an exception may be thrown. – lexicore Apr 16 '18 at 06:37
  • I think it does @lexicore. The question itself answer the question. "_We know that it is expensive to catch exceptions. But, is it also expensive to use a try-catch block in Java even if an exception is never thrown?_" And in the comment section, we can also find that "_throwing the exception is not expensive; instantiating the Exception object is what takes most of the time_". This is not a perfect duplicate but it certainly answer the problem. You can use [How slow are Java exceptions?](https://stackoverflow.com/q/299068/4391450) but I it's getting old and the exception probably change – AxelH Apr 16 '18 at 06:43
  • 1
    So I keep thinking either the previous link or [Is it expensive to use try-catch blocks even if an exception is never thrown?](https://stackoverflow.com/q/16451777/4391450) is a duplicate. – AxelH Apr 16 '18 at 07:23

3 Answers3

1

using try has no expenses by itself, but if the using block creates too much exceptions, you should try to review your code.

Creating an exception in Java is a very slow operation. Expect that throwing an exception will cost you around 1-5 microseconds. Nearly all this time is spent on filling in the exception thread stack. The deeper the stack trace is, the more time it will take to populate it.

for more details read here

Hadi J
  • 16,989
  • 4
  • 36
  • 62
Hamid Ghasemi
  • 880
  • 3
  • 13
  • 32
1

From the coding point of view I would definitely prefer a conditional (a == 0 ? 0 : (a/b)), not exception handling. This is actually not an exceptional situation so exception should not be used for control flow here.

Concerning the efficiency, I wrote a micro-benchmark to test this:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {

    private int a = 10;
    private int b = (int) Math.floor(Math.random());

    @Benchmark
    public float conditional() {
        if (b == 0) {
            return 0;
        } else {
            return a / b;
        }
    }

    @Benchmark
    public float exceptional() {
        try {
            return a / b;
        } catch (ArithmeticException aex) {
            return 0;
        }
    }
}

Results:

Benchmark                Mode  Cnt  Score   Error  Units
MyBenchmark.conditional  avgt  200  7.346 ± 0.326  ns/op
MyBenchmark.exceptional  avgt  200  8.166 ± 0.448  ns/op

As I am quite new to JMH, I am not sure my benchmark is correct. But taking results at the face value, the "exceptional" approach is somewhat (~10%) slower. To be honest, I've expected much greater difference.

lexicore
  • 42,748
  • 17
  • 132
  • 221
  • So as I described in a comment, if it is inside a loop, it does make a difference. – Sreekanth Karumanaghat Apr 16 '18 at 06:45
  • 1
    Your expression `(int) Math.floor(Math.random());` is better written as `new Random().nextInt(2)`. Since `b` is fixed during the benchmark, you'd get no branch mispredictions, which isn't very realistic. Using `int[] b = new Random().ints(1000, 0, 100).mapToInt(n -> n – maaartinus Apr 16 '18 at 10:33
  • 1
    @SreekanthKarumanaghat If these numbers are right (I actually think there branch mispredicitions point is important, so the real cost would be higher), that 10% difference is still only about 0.8 _nano_ seconds. So in a loop of 10,000 iterations, it'll add about 0.0082 milliseconds total. It makes a difference, but an extremely tiny one. That's why people warn against premature optimization: there's a good chance you'll be saving 10%, or heck even 90%, on something that really doesn't matter. – yshavit Apr 16 '18 at 11:09
  • (Also, re the benchmark: isn't `Math.floor(Math.random())` just 0? You start with a number 0 ≤ r < 1, and then round down.) – yshavit Apr 16 '18 at 11:15
  • @maaartinus No, `Math.floor(Math.random())` is not equivalent to `new Random().nextInt(2)`. `Math.floor(Math.random())` always gives 0. – lexicore Apr 16 '18 at 14:16
  • @yshavit I am not 100% sure if the benchmark is OK. Could you please comment/answer in [the question I have asked about this benchmark](https://stackoverflow.com/questions/49850445/simple-micro-benchmark-with-jmh)? – lexicore Apr 16 '18 at 14:18
  • @yshavit Yes, of course `Math.floor(Math.random())` is `0`. It is just a not-so-obvious-way to write `0`. My thoghts were that if I'd simply write `b = 0`, the compiler would probably optimize it all away. So I wanted to make the value of `b` quasi-unclear in the compile time. I am not sure if this is the right approach, either. – lexicore Apr 16 '18 at 14:20
  • @lexicore Oh shoot, I misread your comment to maaartinus. I thought you were saying it _is_ equivalent to `nextInt(2)`. Apologies. :) – yshavit Apr 16 '18 at 15:11
0

Exceptions are for exceptional situations that you want to propagate back to the caller. Don't rely on exceptions during normal operation.

I would just write your code as:

public boolean canDivisionBeDone(int iA, int iB) {
    return iB != 0;
}

But to answer your question: try-catch-finally is implemented in Java using off-line exception tables, and as such, when no exception is thrown, is zero-overhead.

Here's how the bytecode of your two functions looks like:

  public boolean canDivisionBeDoneTryCatch(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: idiv
       3: i2f
       4: fstore_3
       5: goto          11
       8: astore_3
       9: iconst_0
      10: ireturn
      11: iconst_1
      12: ireturn
    Exception table:
       from    to  target type
           0     5     8   Class java/lang/Exception

  public boolean canDivisionBeDoneIf(int, int);
    Code:
       0: iload_2
       1: ifne          6
       4: iconst_0
       5: ireturn
       6: iload_1
       7: iload_2
       8: idiv
       9: i2f
      10: fstore_3
      11: iconst_1
      12: ireturn

As you can see the happy path is almost identical.

However, throwing an exception is expensive.

So yes, I would expect the exception version to be slightly slower, depending on the ratio of iB == 0 situations.

When in doubt, benchmark it.

rustyx
  • 80,671
  • 25
  • 200
  • 267