Clojureで書いたプログラムを速く起動する。

[この記事](https://qiita.com/ayato_p/items/56595ece48837438d97f)を参考にしました。

Clojureは数ある言語の中でも起動が遅い。そのため、実行時間が短いプログラムでは、Clojureの優れた実行速度が生かせない場合がある。

そんなことにもんもんとして、速くならないものかと実験してみた結果を記録しておく。
結論としては、GraalでNativeビルドが一番速い。
また、今回は実験していないが参考記事で説明されているDripもNativeビルドほどではないが速い。


#環境等
AWS EC2 t2.microを使用した。

```shell-session
ubuntu@ip-172-31-37-182:~$ uname -a
Linux ip-172-31-37-182 4.15.0-1021-aws #21-Ubuntu SMP Tue Aug 28 10:23:07 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
```
起動時間を計測することが目的なので、"Hello World!!"を出力する簡単なプログラムで実験することにした。

```clojure:hello_world.clj
(ns hello_world)
(println "Hello World!!")
```

#まずは率直に
つまり、なにも手を加えずにそのままClojureのスクリプトを実行した場合。

```shell-session
ubuntu@ip-172-31-37-182:~$ du clojure-1.9.0.jar -h
3.6M	clojure-1.9.0.jar
ubuntu@ip-172-31-37-182:~$ time java -cp .:./clojure-1.9.0.jar:./core.specs.alpha-0.2.44.jar:./spec.alpha-0.2.176.jar clojure.main hello_world.clj 
Hello World!!

real	0m2.215s
user	0m2.071s
sys	0m0.132s

ubuntu@ip-172-31-37-182:~$ time for i in `seq 1 10`;do java -cp .:./clojure-1.9.0.jar:./core.specs.alpha-0.2.44.jar:./spec.alpha-0.2.176.jar clojure.main hello_world.clj ;doneHello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!

real	0m22.109s
user	0m20.780s
sys	0m1.220s

```
見てわかるように、簡単なスクリプトの実行に2秒以上もかかっている。
#Clojureのカスタムビルドを使って
##elide-meta
配布されているClojure.jarファイルはelida-metaが有効になっていない。なので、elida-metaを有効にしてビルドしたClojure.jarファイルを使用した。（Direct-linkingは配布されているClojure.jarファイルですでに有効になっている。)

elide-metaとDirect-linking、そしてAOTの説明はここを読んでください。[Ahead-of-time Compilation and Class Generation](https://clojure.org/reference/compilation)

```shell-session
ubuntu@ip-172-31-37-182:~$ du ./clojure-clojure-1.9.0/target/clojure-1.9.0.jar  -h
3.5M	./clojure-clojure-1.9.0/target/clojure-1.9.0.jar
time java -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar:./core.specs.alpha-0.2.44.jar:./spec.alpha-0.2.176.jar clojure.main hello_world.clj 
Hello World!!

real	0m2.084s
user	0m1.960s
sys	0m0.116s
ubuntu@ip-172-31-37-182:~$ time for i in `seq 1 10`;do java -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar:./core.specs.alpha-0.2.44.jar:./spec.alpha-0.2.176.jar clojure.main hello_world.clj ;done
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!

real	0m21.348s
user	0m20.000s
sys	0m1.251s
```

100ミリ秒以上速くなっている。
##非圧縮JAR
Clojure.jarを含め多くのJARファイルは圧縮されている。JARファイルの圧縮を無効化することで、わずかではあるが高速化が期待できる。
[Java Drag Race Tuning](https://www.slideshare.net/nekop/20130830-java-dragracetuning)

```shell-session
ubuntu@ip-172-31-37-182:~$ du ./clojure-clojure-1.9.0/target/clojure-1.9.0.jar  -h
7.0M	./clojure-clojure-1.9.0/target/clojure-1.9.0.jar
ubuntu@ip-172-31-37-182:~$ time java -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar:./core.specs.alpha-0.2.44.jar:./spec.alpha-0.2.176.jar clojure.main hello_world.clj 
Hello World!!

real	0m1.998s
user	0m1.859s
sys	0m0.128s
ubuntu@ip-172-31-37-182:~$ time for i in `seq 1 10`;do java -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar:./core.specs.alpha-0.2.44.jar:./spec.alpha-0.2.176.jar clojure.main hello_world.clj ;done
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!

real	0m20.768s
user	0m19.382s
sys	0m1.270s
```
elide-metaのみの場合と比べ100ミリ秒近く速くなっている。

#AOT
Clojureは実行中にソースコードをJVMバイトコードにコンパイルする。短命なプログラムだと、実行時のコンパイル時間が支配的になる。
AOT(事前にコンパイル)することで、実行時のコンパイル時間を省略できる。
AOTを行うためにはMainクラスが必要となるのでプログラムに手を加える。

```clojure:hello_world.clj
(ns hello_world (:gen-class))
(defn -main [] (println "Hello World!!"))
```
##率直に
AOTを行う以外はなにも手を加えずに。

```shell-session
ubuntu@ip-172-31-37-182:~$ java -cp .:./clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.2.44.jar:./ -Dclojure.compile.path=./build  clojure.lang.Compile hello_world
Compiling hello_world to ./build
ubuntu@ip-172-31-37-182:~$ time java -cp .:./clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.2.44.jar:./build hello_world
Hello World!!

real	0m1.845s
user	0m1.721s
sys	0m0.112s
ubuntu@ip-172-31-37-182:~$ time for i in `seq 1 10`;do java -cp .:./clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.2.44.jar:./build hello_world;done
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!

real	0m17.371s
user	0m16.107s
sys	0m1.148s
```
AOTをするとしないとでは、500ミリ秒近くの差が出る。
##カスタムビルド
elide-metaかつ非圧縮JARで実行。

```
ubuntu@ip-172-31-37-182:~$ java -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.2.44.jar:./ -Dclojure.compile.path=./build -Dclojure.compiler.elide-meta='[:doc :file :line :defe :added]' -Dclojure.compiler.direct-linking=true clojure.lang.Compile hello_world
Compiling hello_world to ./build
ubuntu@ip-172-31-37-182:~$ time java -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.2.44.jar:./build hello_world
Hello World!!

real	0m1.510s
user	0m1.355s
sys	0m0.144s
ubuntu@ip-172-31-37-182:~$ time for i in `seq 1 10`;do java -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.2.44.jar:./build hello_world;done
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!

real	0m15.136s
user	0m13.929s
sys	0m1.093s

```
カスタムビルドを使えば、さらに300ミリ秒ほど速くなる。

##おまけ-Xverify:none
バイトコード検証機能を無効化して高速化する。
Clojureのコンパイル結果は基本的に信頼できるはずなので大丈夫だと思う。

```
ubuntu@ip-172-31-37-182:~$ time java -Xverify:none -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.2.44.jar:./build hello_world
Hello World!!

real	0m1.325s
user	0m1.230s
sys	0m0.084s
ubuntu@ip-172-31-37-182:~$ time for i in `seq 1 10`;do java -Xverify:none -cp .:./clojure-clojure-1.9.0/target/clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.2.44.jar:./build hello_world;done
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!
Hello World!!

real	0m13.313s
user	0m12.201s
sys	0m1.007s

```
なにも手を加えない場合と比べ、1秒近く高速に実行できるようになっている。

#Graalでnative-imageを作ってみる。
Graalを使えばJVMバイトコードからネイティブバイナリイメージが作れる。

```shell_session
java -cp .:./clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.1.10.jar:./ -Dclojure.compile.path=./build -Dclojure.compiler.elide-meta='[:doc :file :line :defe :added]' -Dclojure.compiler.direct-linking=true clojure.lang.Compile hello_world
ubuntu@ip-172-31-41-103:~$ time native-image -cp .:./clojure-1.9.0.jar::./spec.alpha-0.2.176.jar:./core.specs.alpha-0.1.10.jar:./build hello_world
Build on Server(pid: 1618, port: 42213)
[hello_world:1618]    classlist:   2,345.92 ms
[hello_world:1618]        (cap):   1,696.97 ms
[hello_world:1618]        setup:   2,352.80 ms
[hello_world:1618]   (typeflow):  13,554.26 ms
[hello_world:1618]    (objects):   3,682.15 ms
[hello_world:1618]   (features):     112.22 ms
[hello_world:1618]     analysis:  17,597.45 ms
[hello_world:1618]     universe:     532.43 ms
[hello_world:1618]      (parse):   6,225.57 ms
[hello_world:1618]     (inline):   5,108.59 ms
[hello_world:1618]    (compile):  19,305.11 ms
[hello_world:1618]      compile:  31,355.72 ms
[hello_world:1618]        image:   2,277.31 ms
[hello_world:1618]        write:     366.47 ms
[hello_world:1618]      [total]:  56,918.67 ms

real	0m57.585s
user	0m0.179s
sys	0m0.036s
ubuntu@ip-172-31-41-103:~$ time ./hello_world
Hello World!!

real	0m0.004s
user	0m0.000s
sys	0m0.004s
```
JVMを起動する必要がなく、またClojureの巨大なクラスファイル群を読み込む必要がなくなったためか。恐ろしく速くなった。
#まとめ
Clojureでも競プロが不利にならない未来が訪れて欲しい。