Spring Bootの起動を速くしてみるぞー

疲れた!

## How Fast is Spring?

というSpring One Platformのセッションのビデオを見て、自分でも試してみたお話。このセッション、すごく面白いのでおすすめ。

springoneplatform.io

## 今日のコード

github.com

↓JDK11でやった。

❯ java --version
openjdk 11.0.1 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)

↓こんな感じで実行できる。ベンチマークとってるので、結構時間かかる。

❯ ./mvnw clean package
❯ (cd benchmarks/; java -jar target/benchmarks.jar)

## これがやりたくて

これがやりたくて、Spring Boot Thin Launcherとか

JMHを勉強してたのだった。

いざ!仕上げ!

## 1. FluxBaseline

↓SpringInitializrでReactive Webだけ選んで、WebMVCスタイルでちょこっと書いただけのプロジェクト。

@SpringBootApplication
@RestController
public class DemoApplication {

  @GetMapping("/")
  public String home() {
    return "Hello";
  }

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

↓Spring Bootのバージョンは2.1.0.RELEASE。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

↓このアプリの起動時間は2.938 ± 0.287 s/opだった。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op

ここから(Daveがセッションで言ってたことを参考にしたりしながら)色々やってみることにする。

## 2. Webとの比較

↓WebFluxじゃなくて、Webだとどうなんだろう?と思ったので比較。今回のサンプルプロジェクトでは、主にTomcatとNettyの起動時間の比較になってしまうのかな?

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case02_Web                               ss   10  3.281 ± 0.342   s/op

Fluxの方がちょっと速いね。

## 3. spring-context-indexer

次は、コンポーネントインデックスを作ってくれるというspring-context-indexerを使ってみた。

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-indexer</artifactId>
            <optional>true</optional>
        </dependency>

↓お?ちょっと遅くなった。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case03_WithContextIndexer                ss   10  3.063 ± 0.102   s/op

↓spring.componentsを見てみたら、1個だけしか入ってなかった。そうか、これはもっと大きなプロジェクトで試してみないと分かんなさそう。

#
#Sun Nov 04 18:42:59 JST 2018
com.example.DemoApplication=org.springframework.stereotype.Component

## 4. Lazy Initialization

Lazy Initしてみた。

@Configuration
public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
      beanFactory.getBeanDefinition(beanName).setLazyInit(true);
    }
  }
}

↓結果はこう。ほんのちょっと速くなったかな。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case04_WithLazyInit                      ss   10  2.844 ± 0.129   s/op

## 5. NoVerify

-noverifyをつけて実行したら

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case05_WithNoVerifyOption                ss   10  2.582 ± 0.060   s/op

ちょっと速くなったー。オプションの意味分かってないから、今度調べる。

## 6. TieredStopAtLevel

-XX:TieredStopAtLevel=1をつけて実行したら

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case06_WithTieredStopAtLevel1Option      ss   10  1.980 ± 0.037   s/op

うぉー。だいぶ速くなったなー。2秒切った。でも、これも意味分かってないから、今度調べる。

## 7. SpringConfigLocationを明示的に指定

-Dspring.config.location=classpath:/application.propertiesをつけて実行。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case07_WithSpringConfigLocationOption    ss   10  3.026 ± 0.139   s/op

あれ、遅くなっちゃった。

## 8. JMXをOFFに

-Dspring.jmx.enabled=falseをつけて実行すると

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case08_WithJmxDisabledOption             ss   10  2.877 ± 0.097   s/op

ちょっとだけ速くなったのかな。

## 9. Logbackを除外

こっから先はライブラリーを外していく、まずはLogback

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
        </dependency>

結果はこうなった。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case09_WithoutLogback                    ss   10  2.904 ± 0.096   s/op

んー。気持ちだけ?

## 10. Jacksonを除外

次はJackson

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-json</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

結果は

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case10_WithoutJackson                    ss   10  2.789 ± 0.093   s/op

ちょっとだけ速くなった。

## 11. HibernateValidatorを除去

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>hibernate-validator</artifactId>
                    <groupId>org.hibernate.validator</groupId>
                </exclusion>
            </exclusions>
        </dependency>

結果はこう

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case11_WithoutHibernateValidator         ss   10  2.857 ± 0.084   s/op

まぁこれも気持ちだけ。ライブラリー除去はこれくらい。

## 12. AppCDS

Oracle JDKの商用機能としてあったAppCDS(Application Class Data Sharing)という機能が、OpenJDK 10から使えるようになってる。共有アーカイブに情報をダンプしておいて、それを使うので起動が速くなるらしい。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case12_WithAppCds                        ss   10  2.957 ± 0.079   s/op

んー。速くなってないなぁ。って、CDS自体をぼーっと調べてたら、みけさんの記事を見つけて

mike-neck.hatenadiary.com

おー!そういうことかーってなった。SpringBootのFatJARだと中のライブラリーがCDSの対象になってないのか。

## 13. Flux with Thin Launcher

あぁ、ベンチマークの名前Explodedのままだった・・・。最初はThin Launcherを使わずに、単純にFatJARを解凍して使おうと思ったんだけど、それだと結局CDSが使えなかったので、Thin Launcherで実行することにしたからExplodedって言う名前になってるす。

まずは、CDSを使う前に、単にThin Launcherで固めたJARで実行してみる。

        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.boot.experimental</groupId>
                        <artifactId>spring-boot-thin-layout</artifactId>
                        <version>1.0.15.RELEASE</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>

Thin Launcherで固めてはいるけど、起動時の処理を少しでも減らしたいので、Thin Launcherの起動クラスは使わずに、クラスパスだけ生成して、Mainクラスを指定して実行した。結果はこう。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case13_Exploded                          ss   10  2.476 ± 0.091   s/op

お。ちょっと速いね。

## 14. Thin Launcher + CDS

てことで、それにCDSを適用してみる。ExplodedWithAppCdsって名前になってるけど、実際はThinWithAppCdsです。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case14_ExplodedWithAppCds                ss   10  1.535 ± 0.036   s/op

うぉー。めっちゃ速くなったー!

## 15. 全部入り

最後に全部入りで実行してみた。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case15_AllApplied                        ss   10  0.801 ± 0.037   s/op

おー。1秒切ったー!(∩´∀`)∩ワーイ

遅くなってしまったspring-context-indexerSpringConfigLocationも入れてるから、それを外しといたらもうちょい速くなるんかな?(力尽きた)

## もう一歩先

Daveのセッションでは、Functional Bean Definitionsとか、SpringBootを使わずにSpringで色々やったりしててもっと速くなってた。あんまり良くわかってない。またそのうち!

## TODO

  • -noverifyの意味を調べる
  • -XX:TieredStopAtLevel=1の意味を調べる

## 最後に

結果一覧

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case02_Web                               ss   10  3.281 ± 0.342   s/op
MyBenchmark.case03_WithContextIndexer                ss   10  3.063 ± 0.102   s/op
MyBenchmark.case04_WithLazyInit                      ss   10  2.844 ± 0.129   s/op
MyBenchmark.case05_WithNoVerifyOption                ss   10  2.582 ± 0.060   s/op
MyBenchmark.case06_WithTieredStopAtLevel1Option      ss   10  1.980 ± 0.037   s/op
MyBenchmark.case07_WithSpringConfigLocationOption    ss   10  3.026 ± 0.139   s/op
MyBenchmark.case08_WithJmxDisabledOption             ss   10  2.877 ± 0.097   s/op
MyBenchmark.case09_WithoutLogback                    ss   10  2.904 ± 0.096   s/op
MyBenchmark.case10_WithoutJackson                    ss   10  2.789 ± 0.093   s/op
MyBenchmark.case11_WithoutHibernateValidator         ss   10  2.857 ± 0.084   s/op
MyBenchmark.case12_WithAppCds                        ss   10  2.957 ± 0.079   s/op
MyBenchmark.case13_Exploded                          ss   10  2.476 ± 0.091   s/op
MyBenchmark.case14_ExplodedWithAppCds                ss   10  1.535 ± 0.036   s/op
MyBenchmark.case15_AllApplied                        ss   10  0.801 ± 0.037   s/op

色々勉強になったなー。というかもっと勉強しなきゃなーと思ったなー。