|
| 1 | +# Binary Tree Benchmark |
| 2 | + |
| 3 | +This demo shows how to run a Java Microbenchmark Harness (JMH) benchmark as a native executable. |
| 4 | + |
| 5 | +To build a native executable version of this benchmark you need to run the [Tracing Agent](https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/) to supply the reflection configuration to the `native-image` builder. |
| 6 | +This has already been done for you to save time and the generated configuration can be found in _src/main/resources/META-INF/native-image/_. |
| 7 | + |
| 8 | +> **Note:** To generate the configuration yourself, ensure that the JMH `fork` parameter is set to `0`, which can be performed from the command line using the option `-f 0`. It can also be achieved within the code by using the `@Fork` annotation. |
| 9 | +
|
| 10 | +## Important Notes on Using JMH with GraalVM Native Image |
| 11 | + |
| 12 | +To make a JMH benchmark run as a native image, you can not fork the benchmark process in the same way as JMH does when running on the JVM. |
| 13 | +When running on HotSpot, JMH will fork a new JVM for each benchmark to ensure there is no interference in the measurements for each benchmark. |
| 14 | +This forking process is not possible with GraalVM Native Image and you should consider the following guidance when building JMH benchmarks that are meant to be run as native executables: |
| 15 | +* You should only include a single benchmark in each native executable. |
| 16 | +* You need to annotate the benchmark with `@Fork(0)` to ensure that the benchmark is not forked. |
| 17 | +* If you want to profile the benchmark to generate an optimized version of it, you should, obviously, ignore the benchmark results whilst profiling. |
| 18 | + |
| 19 | +## Preparation |
| 20 | + |
| 21 | +1. Download and install the GraalVM JDK using [SDKMAN!](https://sdkman.io/). For other installation options, visit the [Downloads page](https://www.graalvm.org/downloads/). |
| 22 | + ```bash |
| 23 | + sdk install java 21.0.5-graal |
| 24 | + ``` |
| 25 | + |
| 26 | +2. Download or clone the repository and navigate into the _/native-image/benchmark/jmh/binary-tree_ directory: |
| 27 | + ```bash |
| 28 | + git clone https://github.com/graalvm/graalvm-demos |
| 29 | + ``` |
| 30 | + ```bash |
| 31 | + cd graalvm-demos/native-image/benchmark/jmh/binary-tree |
| 32 | + ``` |
| 33 | + |
| 34 | +## Build and Run the Benchmark on HotSpot |
| 35 | + |
| 36 | +To build and run the benchmark on HotSpot, run the following Maven command: |
| 37 | +```shell |
| 38 | +./mvnw clean package exec:exec |
| 39 | +``` |
| 40 | + |
| 41 | +Note that within the _pom.xml_ file there are instructions to explicitly turn off the Graal JIT compiler using the option `-XX:-UseJVMCICompiler`. |
| 42 | +This means that benchmark will run using the C2 JIT compiler. |
| 43 | + |
| 44 | +The application runs the benchmark and displays the results to the terminal. |
| 45 | +**The final result is the most significant.** |
| 46 | +You should see the result similar to this: |
| 47 | +```shell |
| 48 | +Benchmark (binaryTreesN) Mode Cnt Score Error Units |
| 49 | +BinaryTrees.bench 14 thrpt 6 348.923 ± 21.343 ops/s |
| 50 | +``` |
| 51 | + |
| 52 | +## Build and Run the Benchmark from a Native Executable |
| 53 | + |
| 54 | +Now build a native executable using Native Image. |
| 55 | +This demo uses Oracle GraalVM Native Image, however, if you are using GraalVM Community, you may see lower figures for throughput. |
| 56 | + |
| 57 | +1. Build a native executable, run the following command: |
| 58 | + ```shell |
| 59 | + ./mvnw package -Pnative |
| 60 | + ``` |
| 61 | +2. Run the benchmark from a native executable: |
| 62 | + ```shell |
| 63 | + ./target/benchmark-binary-tree |
| 64 | + ``` |
| 65 | + You should see similar results: |
| 66 | + ```shell |
| 67 | + Benchmark (binaryTreesN) Mode Cnt Score Error Units |
| 68 | + BinaryTrees.bench 14 thrpt 6 282.335 ± 5.644 ops/s |
| 69 | + ``` |
| 70 | + |
| 71 | +## Optimize the Benchmark for Throughput |
| 72 | + |
| 73 | +You can improve the performance of this benchmark by applying [Profile-Guided Optimization (PGO)](https://www.graalvm.org/reference-manual/native-image/optimizations-and-performance/PGO/). |
| 74 | + |
| 75 | +> PGO is available with Oracle GraalVM Native Image. |
| 76 | + |
| 77 | +First, you will need to build an instrumented version of this native benchmark that contains extra code to trace the execution of the program and to profile it. |
| 78 | +Therefore, it will run slower than the previous version. |
| 79 | +After execution finishes, a profile file, _default.iprof_, is generated in the root directory. |
| 80 | +This file contains profiling information about the application and will be used to build a more efficient native executable. |
| 81 | + |
| 82 | +1. To build the instrumented version of the native executable, run the following command: |
| 83 | + ```shell |
| 84 | + ./mvnw package -Pinstrumented |
| 85 | + ``` |
| 86 | + |
| 87 | +2. Then run it to generate the profile file: |
| 88 | + ```shell |
| 89 | + ./target/benchmark-binary-tree-instr |
| 90 | + ``` |
| 91 | + |
| 92 | +3. Now that you have the profiles, build and run the optimized version of this native benchmark: |
| 93 | + ```shell |
| 94 | + ./mvnw package -Poptimised |
| 95 | + ``` |
| 96 | + ```shell |
| 97 | + ./target/benchmark-binary-tree-opt |
| 98 | + ``` |
| 99 | + You should see similar results: |
| 100 | + ```shell |
| 101 | + Benchmark (binaryTreesN) Mode Cnt Score Error Units |
| 102 | + BinaryTrees.bench 14 thrpt 6 311.630 ± 3.630 ops/s |
| 103 | + ``` |
| 104 | + |
| 105 | +## Your Mileage May Vary |
| 106 | + |
| 107 | +The results you see will vary depending on the hardware you are running on. |
| 108 | +The results above are from a 2019 MacBook Pro, i7, 32 GB RAM running on Oracle GraalVM for JDK 21.0.5. |
0 commit comments