Remo opened SPR-16359 and commented

Spring incurs high computational costs during startup to wire beans together and perform AOP. With newer technologies like docker or serverless/faas architectures there is a trend to move more and more work into the compile-step where possible. On the one side it allows to catch issues early. On the other side it allows to perform more optimizations like

  • tree-shaking (with something like proguard) to remove unnecessary parts
  • AoT compilation with Java 9+
  • Jlink with Java9+
  • and in general do the wiring only once at compile-time instead of each time at runtime.
  • exclude AutoConfigurations not necessary for a particular application

having such a thing in place would drastically improve startup performance, but can also would make the application more lightweight, saving space and memory.

Other projects in the area is, for example, https://github.com/google/dagger.

Doing something in this area seems in particular important for serverless/faas architectures (https://github.com/spring-cloud/spring-cloud-function/issues/132). Without a small footprint and in particular very fast cold start performance, Spring will not be applicable in such areas.


Affects: 5.0.2

3 votes, 6 watchers

Comment From: spring-projects-issues

Dave Syer commented

All of the suggestions above are excellent ideas, but unfortunately, I do not hold out much hope for any of them improving startup times in Spring apps.

  • "Tree-shaking" could reduce the disk and network usage, but the size of the classpath is not correlated with JVM startup time. There is a correlation with the number of loaded classes, but that would not change if all you do is remove unused classes[1]. The best you could achieve would be using less bandwidth and disk IO downloading and moving your app from build to runtime environment. That might be effective for some platforms. Might also be worth looking at lighter weight JAR formats, like the Thin Launcher[2].
  • AOT and JLink are pretty disappointing for non-trivial apps (i.e. not Hello World with no dependencies) [3]. Java 8 and Spring Boot 1.5.9 runs faster than Java 9 with AOT and Spring Boot 2.0.0.
  • Wiring at compile time only saves a tiny amount of time. Reflection is fast and Spring optimizes it a lot already. The "stripped down" samples in [1] show this for some minimal Spring Boot apps - they only start up faster because in the end they have fewer features, and therefore load fewer classes.
  • The benchmarks in [1] also show that Spring Boot auto configuration is not in of itself slow. It adds more features, and more features load more classes, which in turn slows down startup. Excluding configuration files that are not used at runtime would save no time at all, and excluding ones that are used by default would speed up the app, but also remove features.

[1] https://github.com/dsyer/spring-boot-startup-bench/blob/master/static/README.adoc [2] https://github.com/dsyer/spring-boot-thin-launcher [3] https://github.com/dsyer/spring-boot-java-9

Comment From: spring-projects-issues

Remo commented

thank your for the pointers.

Should Spring (or an OSS project) decide to provide more compile-time support, I kind of would see it as an enabler for other people to do more work in the area, like Proguard, AoC, Jlink, etc. One optimization may not make too much of a difference, but all of them combined, one, two years down the road, could give substrantial gains. frontend frameworks like Angular are already there, and there it looks quite impressive (e.g. https://blog.nrwl.io/angular-is-aot-worth-it-8fa02eaf64d4, already a bit outdated, so things aöready got faster/smaller again in the meantime). On the Java side, such as in AoC, there is clearly a lot of work to be done. Not sure whether with the current (too?) early release, people already loose interest again because of that. It will take some time to get to the point where it will be useful.

the slow performance of the aspectj-related code was one of the major t hings I noticed as well as in the article you referenced (https://github.com/dsyer/spring-boot-startup-bench/blob/master/static/README.adoc), not just CPU, also in terms of memory allocation (that gets cleaned up again with gc). That is one major thing that would be entirely eliminiated by this ticket here.

yes, the writing at compile time would save a bit of reflection, true. Maybe not too much (hard to judge, currently for sure not the main contributor). But it could also allow proguard, AoC, etc. to do its work much better by having a simpler code base to work with and optimize.

There is a correlation with the number of loaded classes, but that would not change if all you do is remove unused classes[

as far as I understand a correlation between classpath size and performance will show up when AoC comes into play. It is not supposed to be worthwhile to just AoC everything.

(i will update to Spring Boot 2 the next few days and will look a bit more closely)

Comment From: spring-projects-issues

Remo commented

maybe worth checking out: http://micronaut.io/.

Basically Spring Boot (at least all the basics), but everything done at compile time with a focus on fast startup and small footprint. But I suspect most the effort could have been avoided by just providing that compile-time functionality in Spring instead of creating something totally from scratch.

Comment From: spring-projects-issues

Dave Syer commented

I agree with the second part of your analysis, at least - I suspect the effort could have been applied to Spring with as good or better results. I'm pretty sure Micronaut is not doing "everything at compile time" though - it still has to use some reflection to instantiate the components it is going to use. But they have shown us that there are some optimizations we can do in Spring, and we have been starting to think about banishing annotations completely, which would probably go one level beyond what you are asking for (e.g. see https://github.com/spring-projects/spring-fu and the functional bean registration API that it builds on in Spring 5, https://spring.io/blog/2017/03/01/spring-tips-programmatic-bean-registration-in-spring-framework-5).

Comment From: spring-projects-issues

Remo commented

I did not analyze micronaut yet, but I think to the most degree the rely on annotation processors. I think that is quite a good approach (for the reasons mentioned above).  Myself I also experimented with it in https://github.com/crnk-project/crnk-framework/tree/master/crnk-examples/dagger-vertx-example. In that case based on dagger, it will generate Java sources that wire the application together. In my example I afterwards remove everything not necessary with proguard. Everything together then gets quite small/compact. I assume Micronaut works about the same. From my perspective doing optimizations in this area are something that could be very well sold as a Spring 6, meaning fast, small footprint, no waiting, ready for serverless, etc.

Comment From: remmeier

maybe more followups in the area:

  • micronaut now basically did what you are looking for: https://github.com/micronaut-projects/micronaut-spring, at lest a good start
  • https://devclass.com/2019/03/08/red-hat-brews-up-quarkus-to-pour-java-into-smaller-containers/ does some work with native code & Quark

still think theire is tremendous potential in the area or also the danger for getting obsolete by competitors. we ran quite large number of spring boot micro services, but the footprint and startup costs are too large compared to experiments with other frameworks.

Comment From: dsyer

Please can you share some code with the comparisons and analysis that you did? We publish benchmarks on Spring, and are finding lots of optimizations. The other frameworks might be as amazing as they claim but I haven't seen any evidence yet that I could reproduce.

Comment From: davidkiss

See here for performance benchmark results: https://www.techempower.com/benchmarks/ and source code for both the micronaut and spring code used: https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java.

Comment From: dsyer

That's a data point I guess. But the techempower benchmarks don't really compare "footprint" and "startup costs", which is what is claimed as a benefit of compile-time weaving. Sadly, these benchmarks are also not perceived as a good comparison of real application performance. The specs for the individual apps are not really representative of real application workloads.

The evidence that we have is pointing to likely marginal gains from changing to a compile time approach, and we have already improved some metrics in Spring Boot 2.2/Spring 5.2 by large factors (e.g. apps that start 15x faster, and apps that start in <10MB heap). This issue is still open, and we are continuing to discuss the topic with open minds, but personally I'm not convinced it's going to be worth it.

Comment From: remmeier

"The evidence that we have is pointing to likely marginal gains from changing to a compile time approach"

If it is just about replacing some reflection calls, then yes true. Reflection can be rather fast. spring-aop is an area where I would see benefits. Some performance issues on startup also come from popular third-party like Hibernate. But still real and main benefit for opening up this ticket was also to provide an entry point for further optimizations as mentioned in the description. compile-time injection would play well together with tree shaking and aot/graal. graal already brings benefits on its own. tree shaking is rather hard/cumbercomse to achieve for a spring boot application due to all the reflection/aop/discovery. Would be amazed if there are not some substantial results when bringing all three together. In other languages/frameworks like Angular does benefits are well established.

Comment From: cmyker

Just wanted to highlight one area where functionality mentioned above gets increasingly important - serverless. Even a milisecond delay of a cold start time is important there, since it piles up into a big number... We can certainly discuss that not only spring has issues there, but absence of JIT would be a performance penalty... But it is matter of fact that trend is moving there, and most people are willing to neglect JIT issues in favour of saving costs by converting certain services to responding on demand. This is getting increasingly important since more and more organisations split there infrastructure into smaller domains served by certain microservices that might not be feasible to run all the time or setup complex autoscaling functionality. At this very moment spring boot is almost useless in environment like AWS Lambda, and we, as organisation, prefer to develop such a services running on Lambda in anything else but spring boot, which is a pity and every day piling up loss for Pivotal/VMware...

Comment From: jhoeller

This ticket has generally been superseded by Spring AOT (which performs a lot of scanning and introspection at build time and transforms common application configuration accordingly), along with the GraalVM Native Image support in Spring Framework 6. And for the startup time problem, there is also CRaC and AppCDS now in Spring Framework 6.1.

For concrete improvement requests around our AOT efforts, please create dedicated tickets for it.