[検証]SpringBoot vs Micronaut 起動速度の比較

[Java Advent Calendar 2019](https://qiita.com/advent-calendar/2019/java) 24日目の記事です。

クリスマス前にも関わらず暇なので:rage:
JVM言語ベースのフレームワークであるSpringBootとMicronautの単純起動速度を比較してみた

Springは知る人ぞ知るフレームワークなので紹介は省きます

##Mirconautとは

- OCI社が開発したマイクロサービス向けフレームワーク
- JVMベース
- 2018年10月に1.0GAリリース

公式サイト
https://micronaut.io/

###特徴

- プログラム言語にJava/Groovy/Kotlinが使える
- Compile Time DI&AOPを採用している
- GraalVM、NativeImageも使える
- CLIが提供されている
- 起動が早い
- メモリフットプリントが小さい

##検証
###環境

- MacBook Pro
<img width="250" alt="スクリーンショット 2019-12-23 13.45.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51062/4dcab65e-2bd4-2c7d-8b59-9e28e376d014.png">

- SpringBoot v2.2.2.RELEASE
- Micronaut 1.2.7

#####実行環境
- AdoptOpenJDK 11.0.4
- AdoptOpenJDK 8.0.232
- GraalVM 19.2.1 
 - JIT
 - AOT(NativeImage)

###方法

- シンプルなREST APIを１つ実装する
- jarにパッケージして起動する（java -jar xxx.jar）
- 各実行環境で10回起動して、その平均値を取る
- SpringBootはTomcatとJettyの両方で起動速度を計測する

###実装
####SpringBoot
```java:
@RestController
public class HelloController {

    @RequestMapping(value="/hello", method=RequestMethod.GET)
    public String hello() {
        return "Hello Spring";
    }
}
```
Tomcat起動

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2019-12-23 14:29:46.247  INFO 73041 --- [           main] com.example.sphelloapp.DemoApplication   : Starting DemoApplication
2019-12-23 14:29:46.250  INFO 73041 --- [           main] com.example.sphelloapp.DemoApplication   : No active profile set, falling back to default profiles: default
2019-12-23 14:29:46.925  INFO 73041 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-12-23 14:29:46.935  INFO 73041 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-12-23 14:29:46.935  INFO 73041 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.29]
2019-12-23 14:29:46.984  INFO 73041 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-12-23 14:29:46.984  INFO 73041 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 695 ms
2019-12-23 14:29:47.105  INFO 73041 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-12-23 14:29:47.214  INFO 73041 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-12-23 14:29:47.216  INFO 73041 --- [           main] com.example.sphelloapp.DemoApplication   : Started DemoApplication in 1.233 seconds (JVM running for 1.556)
```
Jetty起動

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.2.RELEASE)

2019-12-23 14:22:39.036  INFO 72942 --- [           main] c.e.sphelloappjetty.DemoApplication      : Starting DemoApplication
2019-12-23 14:22:39.038  INFO 72942 --- [           main] c.e.sphelloappjetty.DemoApplication      : No active profile set, falling back to default profiles: default
2019-12-23 14:22:39.651  INFO 72942 --- [           main] org.eclipse.jetty.util.log               : Logging initialized @1229ms to org.eclipse.jetty.util.log.Slf4jLog
2019-12-23 14:22:39.713  INFO 72942 --- [           main] o.s.b.w.e.j.JettyServletWebServerFactory : Server initialized with port: 8080
2019-12-23 14:22:39.716  INFO 72942 --- [           main] org.eclipse.jetty.server.Server          : jetty-9.4.24.v20191120; built: 2019-11-20T21:37:49.771Z; git: 363d5f2df3a8a28de40604320230664b9c793c16; jvm 1.8.0_232-20191009173705.graal.jdk8u-src-tar-gz-b07
2019-12-23 14:22:39.738  INFO 72942 --- [           main] o.e.j.s.h.ContextHandler.application     : Initializing Spring embedded WebApplicationContext
2019-12-23 14:22:39.739  INFO 72942 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 664 ms
2019-12-23 14:22:39.815  INFO 72942 --- [           main] org.eclipse.jetty.server.session         : DefaultSessionIdManager workerName=node0
2019-12-23 14:22:39.815  INFO 72942 --- [           main] org.eclipse.jetty.server.session         : No SessionScavenger set, using defaults
2019-12-23 14:22:39.816  INFO 72942 --- [           main] org.eclipse.jetty.server.session         : node0 Scavenging every 660000ms
2019-12-23 14:22:39.821  INFO 72942 --- [           main] o.e.jetty.server.handler.ContextHandler  : Started o.s.b.w.e.j.JettyEmbeddedWebAppContext@45f45fa1{application,/,[file:///private/var/folders/xk/q20_p4gd2xz1sfmtmngwjg380000gp/T/jetty-docbase.146795275059491819.8080/],AVAILABLE}
2019-12-23 14:22:39.822  INFO 72942 --- [           main] org.eclipse.jetty.server.Server          : Started @1400ms
2019-12-23 14:22:39.922  INFO 72942 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-12-23 14:22:40.036  INFO 72942 --- [           main] o.e.j.s.h.ContextHandler.application     : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-12-23 14:22:40.036  INFO 72942 --- [           main] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-12-23 14:22:40.040  INFO 72942 --- [           main] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms
2019-12-23 14:22:40.060  INFO 72942 --- [           main] o.e.jetty.server.AbstractConnector       : Started ServerConnector@37574691{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2019-12-23 14:22:40.062  INFO 72942 --- [           main] o.s.b.web.embedded.jetty.JettyWebServer  : Jetty started on port(s) 8080 (http/1.1) with context path '/'
2019-12-23 14:22:40.064  INFO 72942 --- [           main] c.e.sphelloappjetty.DemoApplication      : Started DemoApplication in 1.299 seconds (JVM running for 1.643)
```
(参考)
jetty起動にする場合の設定(gradle)

```gradle:build.gradle
dependencies {
    compile('org.springframework.boot:spring-boot-starter-web') {
        exclude module: 'spring-boot-starter-tomcat'
    }
    compile('org.springframework.boot:spring-boot-starter-jetty')
}
```
#####NativeImage起動
Springはまだ正式にはNativeImageに対応していない(2020年2Qに対応予定）
実験版がgithubにあるので、これで検証する(TomcatのみでJettyはない)
https://github.com/spring-projects-experimental/spring-graal-native

手順
1. GitHubの[spring-graal-native](https://github.com/spring-projects-experimental/spring-graal-native)をclone
2. `./build-feature.sh`を実行
3. `spring-graal-native-samples/springmvc-tomcat`に移動
4. `./compile.sh`を実行 (1〜2分)
<img width="412" alt="スクリーンショット 2019-12-23 15.30.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51062/993ee7cd-769a-cb4c-4616-53be8ff7d040.png">
5. `target`フォルダに移動し、`springmvc-tomcat`が生成されていることを確認
6. `./springmvc-tomcat`で実行

これでspring側の準備は完了

####Micronaut
```java:HelloController.java
@Controller
public class HelloController{

    @Get(value="/hello", produces = MediaType.TEXT_PLAIN)
    public String index(){
        return "Hello Micronaut";
    }
}
```
起動

```
14:37:49.383 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 985ms. Server Running: http://localhost:8080
```
#####NativeImage起動
Micronautは標準でNativeImageに対応しているが、NativeImage専用のプロジェクトを作ると楽できる
※前提としてMicronautCLIがインストールされていること
　[Micronautインストール手順はここを参照](https://qiita.com/tshk_mtsys/items/ce218211969a59b78783)
手順
1. `$mn create-app hello-graal --features graal-native-image`
2. `HelloController.java`を実装
3. `./gradlew build`を実行
4. `$native-image --no-server -cp build/libs/hello-graal-0.1-all.jar`を実行　（1〜2分）
5. `./hello-graal`を実行

これでMicronaut側の準備は完了

##結果
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51062/618d37cc-986b-84e9-fdbc-ff76c608d3e5.png)
<img width="498" alt="スクリーンショット 2019-12-23 16.15.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51062/b4f7f50a-54a4-e9e5-4e0a-5edad2a30944.png">

###考察

- JDK(11, 8, GraalVM JIT)では差はほぼない。
- フレームワーク同士では約300msの差はあるが、これを大きいと捉えるか小さいと捉えるかは個人の考えやケースによる。筆者の場合は特に気にならない差である。
- GraalVM AOT(NativeImage)は圧倒的に早い。
- JVM上で動作させることの利点を捨ててまでNativeImageにする恩恵があるかと言われれば疑問も残る。
　(JVMも1秒前後で起動するからいいんじゃないか）
- ただし、今回は超シンプルなアプリだった結果であるため、たくさんクラスロードがあるようなアプリだと起動時間においてはAOTの恩恵をさらに感じるかもしれない。

##最後に
今回は単純なアプリでの検証のため、実際の複雑なアプリでは異なる結果になるかもしれないので、あくまで参考として見ていただけると幸いです。
機会があればhelidonとかQuarkusといったフレームワークも検証してみます。