Mais conteúdo relacionado Semelhante a Getting Started GraalVM (再アップロード) (20) Getting Started GraalVM (再アップロード)6. 自己紹介
• Name: Kiyotaka Suzuki
• Twitter: @tamtam180
• Main works
– SquareEnix (約5.5年)
• PlayOnline, FF-XIV, etc.
– SmartNews (約5年)
• Cross-functional Expert Team
• Senior Software Engineer
• 広告事業, 公共事業
• データストア系OSSが好き
• パフォーマンスチューニングが好き
6
8. 用語の説明
• Graal
• GraalVM
• Polyglot
• Truffle
• Substrate VM
• JVMCI, Compiler Interface
• JIT Compiler
• AOT
• HotSpotVM
8
9. 用語の説明
• Graal
• GraalVM
• Polyglot
• Truffle
• Substrate VM
• JVMCI, Compiler Interface
• JIT Compiler
• AOT
• HotSpotVM
9
11. 用語の説明 11
HotSpot VM
Compiler Interface
C1 (Client) Graal
• GraalVM
–多言語用の仮想マシン
C++
← JIT Compiler
C2の代わりにGraalを
JVMCI
Java
JITをJavaで書くためのInterface
12. 用語の説明 12
HotSpot VM
Compiler Interface
C1 (Client) Graal
• Graal
–Javaで書かれたJITコンパイラ
C++
← JIT Compiler
C2の代わりにGraalを
JVMCI
Java
JITをJavaで書くためのInterface
13. 用語の説明 13
HotSpot VM
Graal
• Truffle
–言語実装用フレームワーク
–ASTインタプリタ
C++
JVMCI Java
TruffleJVM Lang
Sulong(LLVM)JS R Ruby Python
C, C++, Rust
15. 用語の説明
• AOT (Ahead of Time)
–事前コンパイルの事だよ
–jaotcとかjava9から入ってるよ
• Substrate VM
–Truffleで書かれたASTインタプリタを
コンパイルしてくれる
15
https://www.oracle.com/technetwork/java/jvmls2015-wimmer-2637907.pdf
20. 開発環境の作り方
• GraalVM CE 1.0.0-RC10
を使います
• OSはUbuntu18.04
20
# ダウンロード
$ wget https://github.com/oracle/graal/releases/download/vm-
1.0.0-rc10/graalvm-ce-1.0.0-rc10-linux-amd64.tar.gz
# 展開
$ tar zxf graalvm-ce-1.0.0-rc10-linux-amd64.tar.gz ¥
-C /usr/lib/jvm
# パスを通す
$ export GRAAL_HOME=/usr/lib/jvm/graalvm-ce-1.0.0-rc10
$ export PATH=$GRAAL_HOME/bin:$PATH
21. 開発環境の作り方 21
$ ls /usr/lib/jvm/graalvm-ce-1.0.0-rc10/bin/
appletviewer javah jps native-image rmiregistry
extcheck javap jrunscript native2ascii schemagen
gu jcmd js node serialver
idlj jconsole jsadebugd npm servertool
jar jdb jstack orbd tnameserv
jarsigner jdeps jstat pack200 unpack200
java jhat jstatd policytool wsgen
java-rmi.cgi jinfo jvisualvm polyglot wsimport
javac jjs keytool rmic xjc
javadoc jmap lli rmid
$ node -v
v10.9.0
$ java -version
openjdk version "1.8.0_192"
OpenJDK Runtime Environment (build 1.8.0_192-
20181024121959.buildslave.jdk8u-src-tar--b12)
GraalVM 1.0.0-rc10 (build 25.192-b12-jvmci-0.53, mixed mode)
23. 開発環境の作り方
• 他の言語もインストールする
23
# 他の言語も使えるようにする
$ gu --catalog list
Downloading: Component catalog
ComponentId Version Component name
----------------------------------------------------------------
python 1.0.0-rc10 Graal.Python
R 1.0.0-rc10 FastR
ruby 1.0.0-rc10 TruffleRuby
# Install Ruby
$ gu install ruby
$ ${GRAAL_HOME}/jre/languages/ruby/lib/truffle/post_install_hook.sh
# Install Python
$ gu install python
$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu]
$ graalpython -V
Graal Python 3.7.0 (GraalVM CE Native 1.0.0-rc10)
27. Sample:Polyglot
• Javaから他の言語を呼び出す
27
import org.graalvm.polyglot.*;
public class Sample {
public static void main(String[] args) {
try (Context context =
Context.newBuilder().allowAllAccess(true).build()) {
System.out.println(
context.getEngine().getLanguages().keySet());
context.eval("ruby", "puts 'hello, ruby’”);
context.eval("python", "print('hello, python')");
context.eval("js", "console.log('hello, JS¥n')");
context.eval("R", "cat('hello, R')");
} catch (PolyglotException e) {
e.printStackTrace(); // こういうの本当は駄目!!
}
}
}
28. Sample:Polyglot
• 実行結果
28
[R, js, python, llvm, ruby]
hello, ruby
hello, python
hello, JS
hello, R
$ javac -cp $GRAAL_HOME/jre/lib/boot/graal-sdk.jar Sample.java
$ java Sample
30. Sample:Polyglot:LLVM
• Javaから他の言語を呼び出す(LLVM)
30
import org.graalvm.polyglot.*;
import java.io.*;
public class Sample2 {
public static void main(String[] args) throws Exception {
File f = new File("hello.bc");
Source source = Source.newBuilder("llvm",
new File("hello.bc")).build();
try (Context context =
Context.newBuilder().allowAllAccess(true).build()) {
context.eval(source).execute();
}
}
}
$ java Sample2
Hello from GraalVM!
31. Sample:Polyglot
• 値を受け取る
31
import org.graalvm.polyglot.*;
public class Sample3 {
public static void main(String[] args) {
try (Context context =
Context.newBuilder().allowNativeAccess(true).build()) {
Value value = context.eval("ruby", "[2,4,8]");
int v = value.getArrayElement(1).asInt();
System.out.println(v); // 4が表示される
} catch (PolyglotException e) {
e.printStackTrace(); // こういうの本当は駄目!!
}
}
}
$ java Sample3
4
32. Sample:Polyglot
• 双方向で実行可能!!
–Node から Java
–Rubyから Python
–などなど..
• サーバをJavaで書いてPluginを
Pythonで書く等が可能
– (JRubyやJPythonで今までも出来たけど)
• ちゃんと使う場合はSourceクラスを
使ったりしてキャッシュ化しましょう
32
https://www.graalvm.org/docs/reference-manual/polyglot/
34. Sample: Nativeイメージを作る
• まずは基本に忠実にHello World
34
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
# コンパイル
$ javac HelloWorld.java
# 普通に実行
$ java HelloWorld
Hello, World!
35. Sample: Nativeイメージを作る
• Native Imageを作る
– ビルドサーバが立ち上がってそこで実行される
– --no-server をつけた方がトラブルが少ない
– -H:Nameで出力ファイル名を指定する(省略可)
35
$ native-image –H:Name=hello --no-server HelloWorld
[helloworld:5620] classlist: 1,487.46 ms
[helloworld:5620] (cap): 988.10 ms
[helloworld:5620] setup: 2,169.08 ms
[helloworld:5620] (typeflow): 3,763.17 ms
[helloworld:5620] (objects): 655.53 ms
[helloworld:5620] (features): 131.40 ms
[helloworld:5620] analysis: 4,616.25 ms
[helloworld:5620] universe: 199.90 ms
[helloworld:5620] (parse): 1,029.82 ms
[helloworld:5620] (inline): 1,138.66 ms
[helloworld:5620] (compile): 5,375.45 ms
[helloworld:5620] compile: 7,758.60 ms
[helloworld:5620] image: 247.74 ms
[helloworld:5620] write: 144.12 ms
[helloworld:5620] [total]: 16,690.46 ms
$ ./hello
Hello, World!
36. Sample: Nativeイメージを作る
• 中身を確認する
– Nativeイメージだけど、libcの依存がある
36
$ file ./hello
./hello: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0,
BuildID[sha1]=32f1dede2061d81be1f5573b20db12b9044212f7, with debug_info, not stripped
$ ldd ./hello
linux-vdso.so.1 (0x00007ffe5696d000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f00c17a5000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f00c159d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f00c11ac000)
/lib64/ld-linux-x86-64.so.2 (0x00007f00c1dc4000)
$ ls -hs ./hello
2.2M ./hello
37. Sample: Nativeイメージを作る
• Libcの依存も無くそう!!
– --staticでsingle binaryが生成できる
37
$ native-image –H:Name=hello --no-server --static HelloWorld
$ file ./hello
./hello: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux),
statically linked, for GNU/Linux 3.2.0,
BuildID[sha1]=db0a71c9360fe7b8a21e1c5cdcedad965643152e, with debug_info, not stripped
$ ldd ./hello
not a dynamic executable
$ ls -hs ./hello
3.4M ./hello
$ ./hello
Hello, World!
39. Sample: Nativeイメージを作る
• フットプリントも小さい
39
$ /usr/bin/time -f "Memory:%M KB time:%E" java HelloWorld
Memory:23380 KB time:0:00.05
$ /usr/bin/time -f "Memory:%M KB time:%E" ./hello
Memory:2484 KB time:0:00.02
Java Native
23,380 KB 2,484KB
41. 注意事項
• Native-imageは制限が多い
– いくつか後述します
– SpringBootのような凝ったライブラリは茨の道です
• 頑張れば実行はできます(これも後述)
– Commonsは素直な実装が多いのでおすすめ
• Native-image化する時の制限です!!
– GraalVMを通常のOpenJDKの代わりに使う分にはC2コ
ンパイラが異なるだけなので制限はありません
41
45. Sample: CLI 45
public class MyCLI {
public static void main(String[] args) throws Exception {
Options options = new Options();
options.addOption("t", false, "display current time");
options.addOption("h", false, "display help");
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse( options, args );
if (cmd.hasOption("h")) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp( "mycli", options );
return;
}
if (cmd.hasOption("t")) {
System.out.println(new Date());
return;
}
}
}
46. Sample: CLI 46
• FatJarをnative build
# FAT JARを作る
$ mvn package
# native-imageを作る
$ native-image --no-server ¥
-jar target/mycli-1.0-SNAPSHOT.jar
# 実行
$ ./mycli-1.0-SNAPSHOT –t
Thu Dec 13 10:16:31 UTC 2018
# ファイルサイズ
$ ls -hs ./mycli-1.0-SNAPSHOT
4.1M ./mycli-1.0-SNAPSHOT
50. Sample: Redis 50
• Mainのコード
public class App {
public static void main( String[] args )
throws Exception {
Jedis jedis = new Jedis("localhost", 6379);
jedis.set("foo", "bar");
String value = jedis.get("foo");
System.out.println("value: " + value);
}
}
51. Sample: Redis 51
• 単純にビルドしても怒られます
[hello-1.0-SNAPSHOT:7248] classlist: 2,885.53 ms
[hello-1.0-SNAPSHOT:7248] (cap): 1,210.36 ms
[hello-1.0-SNAPSHOT:7248] setup: 2,941.58 ms
SLF4J: slf4j-api 1.6.x (or later) is incompatible with this binding.
SLF4J: Your binding is version 1.5.5 or earlier.
SLF4J: Upgrade your binding to version 1.6.x.
[hello-1.0-SNAPSHOT:7248] analysis: 6,413.27 ms
error: Class initialization failed: redis.clients.jedis.HostAndPort
Detailed message:
Error: Class initialization failed: redis.clients.jedis.HostAndPort
Original exception that caused the problem: java.lang.NoSuchMethodError:
org.slf4j.impl.StaticLoggerBinder.getSingleton()Lorg/slf4j/impl/StaticLogger
Binder;
at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
52. Sample: Redis 52
• リフレクションやDynamicProxyの制限
–外部設定でいろいろ指定してあげる
–初期化を遅延させたり
$ native-image --no-server ¥
--allow-incomplete-classpath ¥
--delay-class-initialization-to-
runtime=redis.clients.jedis.HostAndPort ¥
-jar target/hello-1.0-SNAPSHOT.jar
53. Sample: Redis 53
• これでRedisの利用はできます
• このようにLogger, リフレクション, Proxy
を利用しているところは、外から設定を追
加していく必要があります。
$ ./hello-1.0-SNAPSHOT
value: bar
55. Sample: MySQL JDBC Driver 55
• JDBCをNativeImage化するのは辛みがあ
る!!
Class.forName("com.mysql.cj.jdbc.Driver");
String url = “jdbc:mysql://127.0.0.1:3306/mysql?..(省略)";
try (Connection conn = DriverManager.getConnection(url,
"root", "" )) {
try (PreparedStatement pst = conn.prepareStatement("select
help_keyword_id, name from mysql.help_keyword")) {
try (ResultSet rs = pst.executeQuery()) {
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
System.out.printf("%10d -> %s%n", id, name);
}
}
}
}
56. Sample: MySQL JDBC Driver 56
• リフレクションで生成されるクラスを書い
ていく
-H:ReflectionConfigurationFiles=app.json
[
{
"name" : "com.mysql.cj.conf.url.SingleConnectionUrl",
"allDeclaredConstructors" : true,
"allPublicConstructors" : true,
"allDeclaredMethods" : true,
"allPublicMethods" : true
}, /* … */
]
57. Sample: MySQL JDBC Driver 57
• 例えば、接続URLによって5種類の
ConnectionUrlクラスを動的に生成する
https://github.com/mysql/mysql-connector-j/blob/8.0.13/src/main/
core-api/java/com/mysql/cj/conf/ConnectionUrl.java#L199
58. Sample: MySQL JDBC Driver 58
• ひたすら書いていく..
[
{
"name" : "com.mysql.cj.conf.url.SingleConnectionUrl",
},
{
"name" : "com.mysql.cj.conf.url.FailoverConnectionUrl",
},
{
"name" : "com.mysql.cj.conf.url.LoadbalanceConnectionUrl",
},
{
"name" : "com.mysql.cj.conf.url.ReplicationConnectionUrl",
},
]
59. Sample: MySQL JDBC Driver 59
• 時にはクラスを書き換える!!
import com.oracle.svm.core.annotate.*;
@TargetClass(com.mysql.cj.log.LogFactory.class)
final class my_replace_test1 {
@Substitute
public static Log getLogger(String className, String
instanceName) {
//return new com.mysql.cj.log.NullLogger(instanceName);
return new com.mysql.cj.log.StandardLogger(instanceName);
}
}
https://github.com/mysql/mysql-connector-j/blob/8.0.13/src/main/core-impl/java/com/mysql/cj/log/LogFactory.java
60. Sample: MySQL JDBC Driver 60
• Compile時に実装を乗っ取ることができる
– AOPのCompile Time版みたいなイメージ
import com.oracle.svm.core.annotate.*;
@TargetClass(com.mysql.cj.log.LogFactory.class)
final class my_replace_test1 {
@Substitute
public static Log getLogger(String className, String
instanceName) {
//return new com.mysql.cj.log.NullLogger(instanceName);
return new com.mysql.cj.log.StandardLogger(instanceName);
}
}
• jre/lib/svm/builder/svm.jarをリンクする
61. Sample: MySQL JDBC Driver 61
• StandardSocketFactory
• NativeProtocol
• このあたりまで潜ったけど、connection
errorを解決できず.. time over
誰か挑戦してください!!
63. Sample: SpringBoot 63
• こちらを参照ください
– https://github.com/dsyer/spring-boot-micro-apps/
• ちゃんと動いている
• すごい頑張っている..
68. Sample: NodeJS Single Binary 68
• Polyglotでjsを呼び出すサンプル
import org.graalvm.polyglot.*;
public class JSNative {
public static void main(String[] args) {
try (Context context = Context.create("js")) {
context.eval("js", "console.log('hello');");
} catch (PolyglotException e) {
e.printStackTrace();
}
}
}
69. Sample: NodeJS Single Binary 69
• ビルドする
– めっちゃ重い
– メモリ6GBくらい消費する
– CPUもほぼ全部ぶんまわる
$ javac -cp $GRAAL_HOME/jre/lib/boot/graal-sdk.jar
JSNative.java
$ native-image --language:js --tool:truffle --no-server
JSNative
70. Sample: NodeJS Single Binary 70
• できた!!
– サイズはでかい..
$ ./jsnative
hello
$ ls -hs ./jsnative
65M ./jsnative
72. Sample: Ruby Single Binary 72
• 同じ感じで出来ます
$ javac -cp $GRAAL_HOME/jre/lib/boot/graal-sdk.jar
RubyNative.java
$ native-image --language:ruby --tool:truffle --no-server
RubyNative
77. 制限事項: Static Initializer
• CompileTimeに実行される
–動的な処理は避ける
77
public class StaticInitializersSample {
private static final int MyConstInt;
private static final String MyConstString;
static {
MyConstInt = 20181215;
MyConstString = "JJUG CCC";
System.out.println("static init");
}
public static void main(String[] args) {
System.out.println(MyConstInt);
System.out.println(MyConstString);
System.out.println("main print");
}
}
78. 制限事項: Static Initializer
• ビルド
78
$ javac StaticInitializersSample.java
$ native-image StaticInitializersSample
Build on Server(pid: 14906, port: 46301)
[staticinitializerssample:14906] classlist: 251.47 ms
[staticinitializerssample:14906] (cap): 645.03 ms
[staticinitializerssample:14906] setup: 942.31 ms
static init
[staticinitializerssample:14906] (typeflow): 2,296.96 ms
…
コンパイル時に実行される
79. 制限事項: Static Initializer
• 実行
79
$ ./staticinitializerssample
20181215
JJUG CCC
main print
static {} は実行されない!!
→ static initは表示されない
80. 制限事項: Static Initializer
• こういうコードは危険
80
public class MyInit {
private static String defaultEnv = "dev";
static {
String env = System.getenv("MY_ENV");
if (env != null) defaultEnv = env;
}
public static void main(String[] args) {
System.out.println("env=" + defaultEnv);
}
}
$ java MyInit
env=dev
$ MY_ENV=stg java MyInit
env=stg
Java
81. 制限事項: Static Initializer
• こういうコードは危険
81
public class MyInit {
private static String defaultEnv = "dev";
static {
String env = System.getenv("MY_ENV");
if (env != null) defaultEnv = env;
}
public static void main(String[] args) {
System.out.println("env=" + defaultEnv);
}
}
$ MY_ENV=prod native-image --no-server MyInit
$ ./myinit
env=prod
$ MY_ENV=stg ./myinit
env=prod
Native
82. 制限事項: Static Initializer
• 回避策
– --rerun-class-initialization-at-runtime
– カンマ区切りでクラスを渡す
– 実行時にstatic initializerを実行する
82
$ native-image ¥
--rerun-class-initialization-at-runtime=MyInit ¥
--no-server MyInit
$ ./myinit
env=prod
$ MY_ENV=dev ./myinit
env=dev
85. 制限事項: synchronized 85
import java.util.ArrayList;
public class Sync2 {
static String hoge() {
ArrayList<String> v = new ArrayList<>();
v.add("Me"); v.add("You"); v.add("Her");
return v.toString();
}
public static void main(String[] args) {
for (int i = 0; i < 10000000; i++) {
String v = hoge();
}
}
}
real 0m2.099s
user 0m3.578s
sys 0m0.393s
real 0m1.700s
user 0m1.591s
sys 0m0.102s
Java Native
Synchronized無しは
Nativeの方が若干速いけど、
86. 制限事項: synchronized 86
import java.util.Vector;
public class Sync2 {
static String hoge() {
Vector<String> v = new Vector<>();
v.add("Me"); v.add("You"); v.add("Her");
return v.toString();
}
public static void main(String[] args) {
for (int i = 0; i < 10000000; i++) {
String v = hoge();
}
}
}
real 0m2.075s
user 0m3.789s
sys 0m0.238s
real 0m4.004s
user 0m3.901s
sys 0m0.090s
Java Native
Synchronizedがあると、
Nativeはとても遅い!!