<命題>
GraalVM(AOT)機能を使い、CPU負荷が掛かるJavaプログラムを、ネイティブイメージ化して、JITモードで走らす場合とスループットやCPU利用率を比較してみよう!
<やり方>
- まず、GraalVMをダウンロードしましょう!
ダウンロードサイドはこちら。
→ https://www.oracle.com/jp/java/technologies/downloads/#graalvmjava21
Tips : GraalVM for JDK21かJDK17をDLしてください。
ご自分のOSとCPUに適合したバージョンをDLしてください。
2. 通常のJDKと同様に、パスを通しましょう!
GraalVMに切り替わっている事を確認しましょう。(下記は、GraalVM for JDK17の例)
testuser_jp@testuser_jp-mac ~ % java -version
java version "17.0.8" 2023-07-18 LTS
Java(TM) SE Runtime Environment Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14)
Java HotSpot(TM) 64-Bit Server VM Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14, mixed mode, sharing)
testuser_jp@testuser_jp-mac ~ %
3. 負荷を掛けるJavaプログラムの例です。
任意のデイレクトリーのファイル数とサイズを算出するプログラムです。
これを、JITモードで動かした場合と、ネイティブイメージ化した上で動かした場合を比較します。
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class ListDir {
public static void main(String[] args) throws java.io.IOException {
String root = ".";
if(args.length > 0) {
root = args[0];
}
System.out.println("Walking path: " + Paths.get(root));
long[] size = {0};
long[] count = {0};
try (Stream<Path> paths = Files.walk(Paths.get(root))) {
paths.filter(Files::isRegularFile).forEach((Path p) -> {
File f = p.toFile();
size[0] += f.length();
count[0] += 1;
});
}
System.out.println("Total: " + count[0] + " files, total size = " + size[0] + " bytes");
}
}
4. クラスファイルを作成します。
testuser_jp@testuser_jp-mac graalvm % javac ListDir.java
5. ネイティブイメージ化します。
クラスファイルに対して、”native-image クラスファイル名”とする事で、ネイティブイメージ化が終了します。(数十秒、完了まで掛かります。)
testuser_jp@testuser_jp-mac graalvm % native-image ListDir
==========================================================================
GraalVM Native Image: Generating 'listdir' (executable)...
==========================================================================
[1/8] Initializing... (6.9s @ 0.16GB)
Java version: 17.0.8+9-LTS, vendor version: Oracle GraalVM 17.0.8+9.1
Graal compiler: optimization level: 2, target machine: armv8-a, PGO: off
C compiler: cc (apple, arm64, 15.0.0)
Garbage collector: Serial GC (max heap size: 80% of RAM)
[2/8] Performing analysis... [***] (3.5s @ 0.23GB)
1,872 (59.79%) of 3,131 types reachable
1,744 (46.86%) of 3,722 fields reachable
7,831 (36.27%) of 21,588 methods reachable
642 types, 0 fields, and 288 methods registered for reflection
49 types, 33 fields, and 48 methods registered for JNI access
4 native libraries: -framework Foundation, dl, pthread, z
[3/8] Building universe... (0.6s @ 0.28GB)
[4/8] Parsing methods... [*] (0.7s @ 0.21GB)
[5/8] Inlining methods... [***] (0.3s @ 0.29GB)
[6/8] Compiling methods... [***] (8.1s @ 0.43GB)
[7/8] Layouting methods... [*] (0.4s @ 0.55GB)
[8/8] Creating image... [*] (1.0s @ 0.28GB)
2.57MB (41.23%) for code area: 3,567 compilation units
3.45MB (55.36%) for image heap: 49,270 objects and 1 resources
217.73kB ( 3.41%) for other data
6.24MB in total
--------------------------------------------------------------------------------
Top 10 origins of code area: Top 10 object types in image heap:
1.35MB java.base 511.54kB byte[] for code metadata
1.05MB svm.jar (Native Image) 417.76kB byte[] for java.lang.String
66.98kB com.oracle.svm.svm_enterprise 328.76kB java.lang.String
27.29kB org.graalvm.nativeimage.base 293.92kB java.lang.Class
22.11kB org.graalvm.sdk 255.48kB byte[] for general heap data
16.06kB jdk.internal.vm.ci 149.91kB java.util.HashMap$Node
13.77kB jdk.internal.vm.compiler 111.71kB char[]
4.62kB ListDir 79.03kB java.lang.Object[]
1.76kB jdk.proxy3 73.13kB c.o.s.c.h.DynamicHubCompanion
1.72kB jdk.proxy1 70.82kB byte[] for reflection metadata
1.48kB for 2 more packages 452.61kB for 507 more object types
--------------------------------------------------------------------------------
Recommendations:
PGO: Use Profile-Guided Optimizations ('--pgo') for improved throughput.
HEAP: Set max heap for improved and more predictable memory usage.
CPU: Enable more CPU features with '-march=native' for improved performance.
QBM: Use the quick build mode ('-Ob') to speed up builds during development.
BRPT: Try out the new build reports with '-H:+BuildReport'.
--------------------------------------------------------------------------------
0.8s (3.6% of total time) in 71 GCs | Peak RSS: 1.07GB | CPU load: 4.39
--------------------------------------------------------------------------------
Produced artifacts:
/Users/testuser_jp/Desktop/code/graalvm/listdir (executable)
================================================================================
Finished generating 'listdir' in 21.7s.
6. JITモードで動かした場合と、ネイティブイメージで動かした場合のスループットとCPU使用率を比較します。
testuser_jp@testuser_jp-mac graalvm % time java ListDir
Walking path: .
Total: 9072 files, total size = 5529902980 bytes
java ListDir 0.29s user 0.22s system 153% cpu 0.333 total
testuser_jp@testuser_jp-mac graalvm % time ./ListDir
Walking path: .
Total: 9072 files, total size = 5529902980 bytes
./ListDir 0.02s user 0.18s system 94% cpu 0.219 total
testuser_jp@testuser_jp-mac graalvm %
<結論>
CPU利用率は、38%以上の効率化。
スループットは、34%以上の向上。
西川 (yojiro.nishikawa@oracle.com)
本件も含め、Javaに関する事なら、何でもお気軽にご相談ください!