Try catch should be placed outside the circulation!

Today's article is about whether # try catch should be placed outside the circulation or inside the circulation. We will answer this question from the aspects of performance and business scenario analysis.

Many people have some misunderstandings about try catch. For example, we often equate it with "low performance", but we lack the most basic understanding of the essence of try catch. Therefore, we will also explore the essence of try catch in this article.

 

 

performance evaluation

Without much to say, let's start today's test directly. In this article, we still use JMH (JAVA micro benchmark harness) officially provided by Oracle to test.

First in POM Add JMH framework to the XML file. The configuration is as follows:

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
   <groupId>org.openjdk.jmh</groupId>
   <artifactId>jmh-core</artifactId>
   <version>{version}</version>
</dependency>

The complete test code is as follows:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

/**
 * try - catch performance testing
 */
@BenchmarkMode(Mode.AverageTime) //Test completion time
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //Preheat , 1 , round for , 1s each time
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) //Test , 5 , rounds for , 3s each time
@Fork(1) //fork # 1 thread
@State(Scope.Benchmark)
@Threads(100)
public class TryCatchPerformanceTest {
    private static final int forSize = 1000; //Number of cycles
    public static void main(String[] args) throws RunnerException {
        //Start benchmark
        Options opt = new OptionsBuilder()
                .include(TryCatchPerformanceTest.class.getSimpleName()) //Test class to import
                .build();
        new Runner(opt).run(); //Perform test
    }

    @Benchmark
    public int innerForeach() {
        int count = 0;
        for (int i = 0; i < forSize; i++) {
            try {
                if (i == forSize) {
                    throw new Exception("new Exception");
                }
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return count;
    }

    @Benchmark
    public int outerForeach() {
        int count = 0;
        try {
            for (int i = 0; i < forSize; i++) {
                if (i == forSize) {
                    throw new Exception("new Exception");
                }
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }
}

The test results of the above codes are:

 

It can be seen from the above results that the average execution time of a single program is:

  • The average execution time of try catch in the loop is 635 nanoseconds ± 75 nanoseconds, that is, the upper and lower error of 635 nanoseconds is 75 nanoseconds;
  • The average execution time of try catch outside the loop is 630 nanoseconds, with an up-down error of 38 nanoseconds.

In other words, in the case of no exception, excluding the error value, we come to the conclusion that the performance of try catch is the same and there is almost no difference whether in or out of the {for} loop.

 

The essence of try catch

To understand the performance of try catch, we must start with its bytecode. Only in this way can I know what the essence of try catch is and how it is executed.

At this point, we write the simplest try catch Code:

public class AppTest {
    public static void main(String[] args) {
        try {
            int count = 0;
            throw new Exception("new Exception");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Then, after generating bytecode with javac , use the command of , javap -c AppTest , to view the bytecode file:

➜ javap -c AppTest 
warning: Binary file AppTest contain com.example.AppTest
Compiled from "AppTest.java"
public class com.example.AppTest {
  public com.example.AppTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: new           #2                  // class java/lang/Exception
       5: dup
       6: ldc           #3                  // String new Exception
       8: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
      11: athrow
      12: astore_1
      13: aload_1
      14: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      17: return
    Exception table:
       from    to  target type
           0    12    12   Class java/lang/Exception
}

From the bytecode above, we can see an exception table:

Exception table:
       from    to  target type
          0    12    12   Class java/lang/Exception

Parameter Description:

  • from: indicates the start address of the try catch;
  • to: end address of try catch;
  • target: indicates the start bit of exception handling;
  • type: indicates the name of the exception class.

As can be seen from the bytecode instruction, when the code runs with an error, it will first judge whether the error data is within the range of "from" to ". If so, it will be executed from the" target "flag bit. If there is no error, it will be directly" goto "to return. In other words, if the code does not make mistakes, the performance is almost unaffected, and the execution logic of normal code is the same.

 

Business analysis

Although the performance of try catch in vivo or in vitro is similar, the business meanings of their codes are completely different, such as the following codes:

public class AppTest {
    public static void main(String[] args) {
        System.out.println("Execution results in the loop:" + innerForeach());
        System.out.println("Execution results outside the loop:" + outerForeach());
    }
    
    //Method 1
    public static int innerForeach() {
        int count = 0;
        for (int i = 0; i < 6; i++) {
            try {
                if (i == 3) {
                    throw new Exception("new Exception");
                }
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return count;
    }

    //Method 2
    public static int outerForeach() {
        int count = 0;
        try {
            for (int i = 0; i < 6; i++) {
                if (i == 3) {
                    throw new Exception("new Exception");
                }
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }
}

The results of the above procedures are:

java.lang.Exception: new Exception

at com.example.AppTest.innerForeach(AppTest.java:15)

at com.example.AppTest.main(AppTest.java:5)

java.lang.Exception: new Exception

at com.example.AppTest.outerForeach(AppTest.java:31)

at com.example.AppTest.main(AppTest.java:6)

Execution result in the loop: 5

Execution result out of loop: 3

It can be seen that the try catch in the loop body can continue to execute the loop after an exception occurs; The try catch outside the loop will terminate the loop after an exception occurs.

Therefore, when we decide whether the} try catch should be placed inside or outside the loop, it does not depend on the performance (because the performance is almost the same), but on the specific business scenario.

For example, we need to process a batch of data, and no matter which data in this group has a problem, it can not affect the normal execution of other groups. At this time, we can put try catch in the loop body; When we need to calculate the total value of a group of data, as long as there is a group of data error, we need to terminate the execution and throw an exception. At this time, we need to put the try catch outside the loop body for execution.

 

summary

In this paper, we tested the performance of try catch in vivo and in vitro, and found that their performance is almost the same when they are circulated many times. Then, through bytecode analysis, we found that only when an exception occurs, we can compare the exception table for exception handling, while under normal circumstances, we can ignore the execution of try catch. The performance of try catch is different from that of try catch in vivo, but we should consider the actual performance of try catch in vivo.

Posted by maniac1aw on Wed, 11 May 2022 07:05:29 +0300