How Fast is Spring?

Dave Syer, 2018

Agenda

  • How much work is Spring doing, compared to other parts of your app?
  • Tools for measurement
  • Comparison between various classpath and packaging choices
  • Speeding things up
  • The future

Baseline Micro App

pubchart?oid=1652003601&format=image

Heap Memory and Garbage Collection

heap-size-2.1.x

Garbage Collection

flame_20
flame_21

Spring Boot 2.0

Spring Boot 2.1

How Fast is Spring?

TL;DR How do I make my app go faster?

  • Classpath exclusions from Spring Boot web starters
  • Use the spring-context-indexer
  • (Don’t use actuators if you can afford not to)
  • Use Spring Boot 2.1 (or 2.2) and Spring 5.1
  • Use explicit spring.config.location
  • (Switch off JMX: spring.jmx.enabled=false)
  • Make bean definitions lazy by default
  • Unpack the fat jar and run with an explicit classpath
  • Run the JVM with -noverify. Also consider -XX:TieredStopAtLevel=1
  • Import autoconfigurations individually
  • Use functional bean definitions
  • (Build a native image)

Summary

  • Spring was designed ab initio to be lightweight
  • The engineering team cares deeply about performance
  • There are many optional features
  • Packaging: exploded jar with application main is always best
  • Server: there is no measureable difference between Tomcat, Jetty and Undertow
  • Netty is a bit faster on startup - you won’t notice in a large app
  • The more features you use, the more classes are loaded
  • Functional bean definitions are interesting
  • Spring Boot, HTTP: starts in <1sec and uses <10MB heap

Tools

Benchmarks: Application Startup

Static benchmarks from https://github.com/dsyer/spring-boot-startup-bench. New JVM (separate process) per application startup, explicit classpath (not fat jar).

class         method  sample  beans    classes  heap    memory  median  mean  range
MainBenchmark   main   demo  121.000  5643.000  13.360  76.726  0.935  0.948  0.017
MainBenchmark   main   jdbc  156.000  5794.000  14.137  78.331  1.008  1.017  0.012
MainBenchmark   main   actr  222.000  6182.000  15.569  83.695  1.140  1.155  0.020
StripBenchmark  strip  slim  103.000  5465.000  13.815  76.140  0.875  0.897  0.041
StripBenchmark  strip  thin  62.000   5247.000  12.810  73.781  0.810  0.827  0.023
StripBenchmark  strip  lite  30.000   5012.000  11.432  70.928  0.715  0.727  0.012
StripBenchmark  strip  func  26.000   4967.000  11.351  70.516  0.686  0.701  0.026

Number of Classes vs. Startup Time

pubchart?oid=1452854557&format=image

WebFlux and Micro Apps

class        method sample  beans   classes   heap    memory  median  mean  range
MainBenchmark  main  demo  93.000   4365.000  8.024  49.564  0.766  0.773  0.011
MainBenchmark  main  jlog  80.000   3598.000  6.141  43.006  0.667  0.679  0.019
MiniBenchmark  boot  jlog  28.000   3336.000  7.082  41.949  0.588  0.597  0.014
MiniBenchmark  mini  jlog  27.000   3059.000  5.487  38.953  0.534  0.545  0.018
MiniBenchmark  micro jlog  2.000    2176.000  4.608  32.886  0.336  0.345  0.013

JVM Tweaks

  • Command line: -noverify -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom
  • Open J9: CDS -Xquickstart -Xshareclasses -Xscmx128m
  • Java 10+: CDS, AOT (GraalVM)
  • Explicit classpath (exploded archive)
pubchart?oid=1689271723&format=image

Classpath Exclusions

YMMV, but consider excluding:

  • Jackson (spring-boot-starter-json)
  • Logback (spring-boot-starter-logging)
  • Hibernate Validator (org.hibernate.validator:hibernate-validator)
  • Actuators (spring-boot-starter-actuator)

Lazy Beans

  • Spring Data @EnableJpaRepositories(bootstrapMode=BootstrapMode.LAZY) or spring.data.jpa.repositories.bootstrap-mode=lazy
  • Look carefully at custom beans with expensive @PostConstruct
  • Profiling with AspectJ
  • Lazy subsystems, E.g. see Lazy Actuator
  • Spring Boot 2.2: spring.main.lazy-initialization=true
  • 10-30% faster for micro apps. Generally better for larger apps.

Manual Configuration

Choose your own autoconfigurations: "a la carte" instead of "all you can eat".

@SpringBootConfiguration
@ImportAutoConfiguration({
    WebFluxAutoConfiguration.class,
    ReactiveWebServerFactoryAutoConfiguration.class,
    ErrorWebFluxAutoConfiguration.class,
    HttpHandlerAutoConfiguration.class,
    ConfigurationPropertiesAutoConfiguration.class,
    PropertyPlaceholderAutoConfiguration.class
})
@RestController
public class DemoApplication {

...

}

PetClinic Benchmarks

pubchart?oid=2146002665&format=image
  • Blue: out of the box Spring Boot.
  • Yellow: manual autoconfiguration.
  • Red: fully functional via Spring Init

Functional Bean Definitions

Before:

@Configuration
public class SampleConfiguration {

    @Bean
    public Foo foo() {
        return new Foo();
    }

    @Bean
    public Bar bar(Foo foo) {
        return new Bar(foo);
    }

}

Functional Bean Definitions

After:

public class SampleConfiguration
        implements ApplicationContextInitializer<GenericApplicationContext> {

    public Foo foo() {
        return new Foo();
    }

    public Bar bar(Foo foo) {
        return new Bar(foo);
    }

    @Override
    public void initialize(GenericApplicationContext context) {
        context.registerBean(SampleConfiguration.class);
        context.registerBean(Foo.class,
                () -> context.getBean(SampleConfiguration.class).foo());
        context.registerBean(Bar.class, () -> context.getBean(SampleConfiguration.class)
                .bar(context.getBean(Foo.class)));
    }

}

Examples

CPU Constrained Environments

container cpus  startup(ms)
===========================
riff       4       2817
scf        4        664
riff       2       4614
scf        2        653
riff       1      16782
scf        1       2121
scf:n      1       1091
  • riff is the image built by pack using the riff builder
  • scf same app built with WebFlux and functional beans
  • scf:n also switch off background preinit

Ahead of Time Compilation (AOT)

Native images can be very efficient (see Graal VM).

$ native-image -H:Name=target/bunc ... com.example.func.BuncApplication
$ ./target/bunc

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

...
Aug 07, 2018 11:25:13 AM org.springframework.boot.web.embedded.netty.NettyWebServer start
INFO: Netty started on port(s): 8080
Aug 07, 2018 11:25:13 AM org.springframework.boot.StartupInfoLogger logStarted
INFO: Started application in 0.036 seconds (JVM running for 0.04)
Benchmark app started
Started HttpServer: 40ms

Downside: you lose all of JVM benefits (debugging, manageability, dynamic compilation, optimization, garbage collection).

Many issues have been solved. Still work in progress. Native image feature is not part of GraalVM 19.0.0 release.

/