quarkus icon indicating copy to clipboard operation
quarkus copied to clipboard

Significant increase of Quarkus compiled binary after upgrade from 3.6.7 to 3.7.1

Open gsmet opened this issue 1 year ago • 16 comments

Discussed in https://github.com/quarkusio/quarkus/discussions/38572

Originally posted by vincejv February 3, 2024 Any one noticed a significant increase in the binaries compiled with 3.7.1 compared to 3.6.7? I'm using few Quarkus libraries on top of the framework, mostly just rest client related, and I have observed the docker image published in docker hub increased by 10mb+ (https://hub.docker.com/r/vincejv/qbittorrent-exporter/tags) 0.0.18 (37mb) vs 0.0.17 (28mb).

I thought removing support for older java version would actually reduce the binaries as it will remove legacy code?

Source code: https://github.com/vincejv/qbittorrent-exporter

gsmet avatar Feb 06 '24 10:02 gsmet

Reproducer

git clone https://github.com/vincejv/qbittorrent-exporter
cd qbittorrent-exporter
git worktree add ../qbittorrent-exporter-3.6
./gradlew build -Dquarkus.package.type=native
cd ../qbittorrent-exporter-3.6
sed -i 's/3\.7\.1/3.6.7/g' gradle.properties
./gradlew build -Dquarkus.package.type=native -D

Build output

3.6.7

Warning: The option '-H:ReflectionConfigurationResources=META-INF/native-image/io.micrometer/micrometer-core/reflect-config.json' is experimental and must be enabled via '-H:+UnlockExperimentalVMOptions' in the future.
Warning: The option '-H:ReflectionConfigurationResources=META-INF/native-image/io.netty/netty-transport/reflection-config.json' is experimental and must be enabled via '-H:+UnlockExperimentalVMOptions' in the future.
Warning: Please re-evaluate whether any experimental option is required, and either remove or unlock it. The build output lists all active experimental options, including where they come from and possible alternatives. If you think an experimental option should be considered as stable, please file an issue.
========================================================================================================================
GraalVM Native Image: Generating 'qbittorrent-exporter-unspecified-runner' (executable)...
========================================================================================================================
For detailed information and explanations on the build output, visit:
https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md
------------------------------------------------------------------------------------------------------------------------
[1/8] Initializing...                                                                                    (1.9s @ 0.26GB)
 Java version: 21.0.1+12-LTS, vendor version: Mandrel-23.1.1.0-Final
 Graal compiler: optimization level: 2, target machine: compatibility
 C compiler: gcc (redhat, x86_64, 13.2.1)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
 3 user-specific feature(s):
 - com.oracle.svm.thirdparty.gson.GsonFeature
 - io.quarkus.runner.Feature: Auto-generated class by Quarkus from the existing extensions
 - io.quarkus.runtime.graal.DisableLoggingFeature: Disables INFO logging during the analysis phase
------------------------------------------------------------------------------------------------------------------------
 4 experimental option(s) unlocked:
 - '-H:+AllowFoldMethods' (origin(s): command line)
 - '-H:BuildOutputJSONFile' (origin(s): command line)
 - '-H:-UseServiceLoaderFeature' (origin(s): command line)
 - '-H:ReflectionConfigurationResources' (origin(s): 'META-INF/native-image/io.micrometer/micrometer-core/native-image.properties' in 'file:///home/zakkak/code/tmp/qbittorrent-exporter/build/qbittorrent-exporter-unspecified-native-image-source-jar/lib/io.micrometer.micrometer-core-1.11.5.jar', 'META-INF/native-image/io.netty/netty-transport/native-image.properties' in 'file:///home/zakkak/code/tmp/qbittorrent-exporter/build/qbittorrent-exporter-unspecified-native-image-source-jar/lib/io.netty.netty-transport-4.1.100.Final.jar')
------------------------------------------------------------------------------------------------------------------------
Build resources:
 - 17.47GB of memory (28.2% of 61.94GB system memory, determined at start)
 - 32 thread(s) (100.0% of 32 available processor(s), determined at start)
[2/8] Performing analysis...  [*****]                                                                    (6.4s @ 1.37GB)
   12,164 reachable types   (86.5% of   14,059 total)
   17,966 reachable fields  (59.3% of   30,287 total)
   60,966 reachable methods (57.9% of  105,323 total)
    3,762 types,   345 fields, and 3,934 methods registered for reflection
       61 types,    59 fields, and    55 methods registered for JNI access
        4 native libraries: dl, pthread, rt, z
[3/8] Building universe...                                                                               (1.6s @ 1.48GB)
[4/8] Parsing methods...      [*]                                                                        (0.8s @ 1.58GB)
[5/8] Inlining methods...     [***]                                                                      (0.4s @ 1.52GB)
[6/8] Compiling methods...    [**]                                                                       (4.3s @ 2.01GB)
[7/8] Layouting methods...    [**]                                                                       (2.3s @ 1.56GB)
[8/8] Creating image...       [**]                                                                       (2.7s @ 2.07GB)
  24.84MB (46.91%) for code area:    38,892 compilation units
  27.71MB (52.34%) for image heap:  319,952 objects and 83 resources
 406.17kB ( 0.75%) for other data
  52.95MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 origins of code area:                                Top 10 object types in image heap:
  12.69MB java.base                                            7.87MB byte[] for code metadata
   1.35MB java.net.http                                        4.17MB byte[] for java.lang.String
   1.35MB svm.jar (Native Image)                               2.98MB java.lang.Class
 997.90kB modified-io.vertx.vertx-core-4.4.6.jar               2.89MB java.lang.String
 981.48kB qbittorrent-exporter-unspecified-runner.jar          1.03MB byte[] for general heap data
 575.14kB org.yaml.snakeyaml-2.2.jar                           1.02MB com.oracle.svm.core.hub.DynamicHubCompanion
 498.01kB io.netty.netty-buffer-4.1.100.Final.jar            737.07kB byte[] for reflection metadata
 483.75kB io.netty.netty-common-4.1.100.Final.jar            571.48kB java.lang.String[]
 418.46kB io.netty.netty-codec-http-4.1.100.Final.jar        442.42kB c.o.svm.core.hub.DynamicHub$ReflectionMetadata
 380.80kB io.netty.netty-codec-http2-4.1.100.Final.jar       427.66kB java.lang.Object[]
   4.94MB for 85 more packages                                 5.62MB for 2994 more object types
------------------------------------------------------------------------------------------------------------------------
Recommendations:
 HEAP: Set max heap for improved and more predictable memory usage.
------------------------------------------------------------------------------------------------------------------------
                       1.5s (7.2% of total time) in 118 GCs | Peak RSS: 3.30GB | CPU load: 15.27
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /home/zakkak/code/tmp/qbittorrent-exporter/build/qbittorrent-exporter-unspecified-native-image-source-jar/qbittorrent-exporter-unspecified-runner (executable)
 /home/zakkak/code/tmp/qbittorrent-exporter/build/qbittorrent-exporter-unspecified-native-image-source-jar/qbittorrent-exporter-unspecified-runner-build-output-stats.json (build_info)
========================================================================================================================
Finished generating 'qbittorrent-exporter-unspecified-runner' in 20.8s.

3.7.1

Your application is setting the 'quarkus.locales' configuration key to include 'all'. All JDK locales, languages, currencies, etc. will be included, inflating the size of the executable.
========================================================================================================================
GraalVM Native Image: Generating 'qbittorrent-exporter-unspecified-runner' (executable)...
========================================================================================================================
For detailed information and explanations on the build output, visit:
https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md
------------------------------------------------------------------------------------------------------------------------
[1/8] Initializing...                                                                                    (1.9s @ 0.27GB)
 Java version: 21.0.1+12-LTS, vendor version: Mandrel-23.1.1.0-Final
 Graal compiler: optimization level: 2, target machine: compatibility
 C compiler: gcc (redhat, x86_64, 13.2.1)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
 3 user-specific feature(s):
 - com.oracle.svm.thirdparty.gson.GsonFeature
 - io.quarkus.runner.Feature: Auto-generated class by Quarkus from the existing extensions
 - io.quarkus.runtime.graal.DisableLoggingFeature: Disables INFO logging during the analysis phase
------------------------------------------------------------------------------------------------------------------------
 4 experimental option(s) unlocked:
 - '-H:+IncludeAllLocales' (origin(s): command line)
 - '-H:+AllowFoldMethods' (origin(s): command line)
 - '-H:BuildOutputJSONFile' (origin(s): command line)
 - '-H:-UseServiceLoaderFeature' (origin(s): command line)
------------------------------------------------------------------------------------------------------------------------
Build resources:
 - 18.13GB of memory (29.3% of 61.94GB system memory, determined at start)
 - 32 thread(s) (100.0% of 32 available processor(s), determined at start)
[2/8] Performing analysis...  [*****]                                                                    (6.9s @ 1.50GB)
   13,994 reachable types   (88.1% of   15,893 total)
   18,053 reachable fields  (59.4% of   30,383 total)
   64,721 reachable methods (58.3% of  111,023 total)
   27,459 types,   345 fields, and 5,737 methods registered for reflection
       61 types,    59 fields, and    55 methods registered for JNI access
        4 native libraries: dl, pthread, rt, z
[3/8] Building universe...                                                                               (1.9s @ 1.62GB)
[4/8] Parsing methods...      [*]                                                                        (0.9s @ 1.91GB)
[5/8] Inlining methods...     [***]                                                                      (0.4s @ 1.28GB)
[6/8] Compiling methods...    [***]                                                                      (5.2s @ 1.91GB)
[7/8] Layouting methods...    [**]                                                                       (2.2s @ 1.45GB)
[8/8] Creating image...       [**]                                                                       (3.9s @ 2.21GB)
  25.38MB (34.34%) for code area:    40,769 compilation units
  48.12MB (65.12%) for image heap:  616,599 objects and 21,975 resources
 405.39kB ( 0.54%) for other data
  73.89MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 origins of code area:                                Top 10 object types in image heap:
  12.70MB java.base                                            9.45MB byte[] for java.lang.String
   1.71MB svm.jar (Native Image)                               8.00MB byte[] for code metadata
   1.35MB java.net.http                                        6.27MB byte[] for general heap data
   1.07MB qbittorrent-exporter-unspecified-runner.jar          5.58MB java.lang.String
1018.87kB modified-io.vertx.vertx-core-4.5.1.jar               3.49MB java.lang.Class
 575.06kB org.yaml.snakeyaml-2.2.jar                           2.24MB java.util.concurrent.ConcurrentHashMap$Node
 500.26kB io.netty.netty-buffer-4.1.106.Final.jar              2.09MB java.lang.Object[]
 490.29kB io.netty.netty-common-4.1.106.Final.jar              1.17MB com.oracle.svm.core.hub.DynamicHubCompanion
 420.50kB io.netty.netty-codec-http-4.1.106.Final.jar        860.11kB byte[] for reflection metadata
 382.09kB io.netty.netty-codec-http2-4.1.106.Final.jar       858.40kB org.graalvm.collections.Pair
   4.95MB for 84 more packages                                 8.14MB for 2999 more object types
------------------------------------------------------------------------------------------------------------------------
Recommendations:
 HEAP: Set max heap for improved and more predictable memory usage.
------------------------------------------------------------------------------------------------------------------------
                       2.2s (8.0% of total time) in 119 GCs | Peak RSS: 4.19GB | CPU load: 13.80
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /home/zakkak/code/tmp/qbittorrent-exporter/build/qbittorrent-exporter-unspecified-native-image-source-jar/qbittorrent-exporter-unspecified-runner (executable)
 /home/zakkak/code/tmp/qbittorrent-exporter/build/qbittorrent-exporter-unspecified-native-image-source-jar/qbittorrent-exporter-unspecified-runner-build-output-stats.json (build_info)
========================================================================================================================
Finished generating 'qbittorrent-exporter-unspecified-runner' in 27.8s.

Observations

  1. The binary size increases from 52.95MB to 73.89MB (3.6.7 vs 3.7.1)
  2. The build process in 3.7.1 mentions that:
Your application is setting the 'quarkus.locales' configuration key to include 'all'. All JDK locales, languages, currencies, etc. will be included, inflating the size of the executable.

which is known to increase the binary size.

Test

Changing quarkus.locales to en-US in src/main/resources/application.yaml and rebuilding results in a binary size equal to 53.14MB.

quarkus.locales=all support was added in 3.7 by https://github.com/quarkusio/quarkus/pull/37106, prior to that setting the locales to all probably did nothing (it resulted in -H:IncludeLocales=all being passed to native-image which AFAIK will silently fail to detect the all locale and will thus do nothing.)

cc @Karm

zakkak avatar Feb 06 '24 14:02 zakkak

The binary size increases from 52.95MB to 73.89MB (3.6.7 vs 3.7.1)

This is pretty bad if you ask me :)

geoand avatar Feb 07 '24 07:02 geoand

The binary size increases from 52.95MB to 73.89MB (3.6.7 vs 3.7.1)

This is pretty bad if you ask me :)

@geoand this is not the default behavior though. This is only happening because the user is requesting all locales to be included and it's documented in https://github.com/Karm/quarkus/blob/c025a1b29c02b6a4b8b773d6041c9e50119147a0/docs/src/main/asciidoc/validation.adoc?plain=1#L400-L401

zakkak avatar Feb 07 '24 07:02 zakkak

Oh, then I misread the investigation.

Thanks for the clarification!

geoand avatar Feb 07 '24 08:02 geoand

I think we can close this issue, thanks @zakkak .

gsmet avatar Feb 07 '24 16:02 gsmet

Is this actually closed? I'm still seeing large native binary sizes (52.7MB) when:

Quarkus 3.7.1

quarkus create cd ./code-with-quarkus quarkus build --native

application.properties: quarkus.locales=en-US

[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Running Quarkus native-image plugin on GRAALVM 23.1 JDK 21.0.1+12-jvmci-23.1-b19

adamhenderson avatar Feb 08 '24 14:02 adamhenderson

52 MB for a basic application looks like a lot.

gsmet avatar Feb 08 '24 15:02 gsmet

I think we need some further investigation.

50+ MB for a mostly empty application with only RESTEasy Reactive around is too much IMHO. I think we were a lot better before.

... digging ...

The same app created with Quarkus 2.16 is 40 MB. I also tested 2.13 with the same result.

I also did an experiment with Quarkus 1.13.3.Final and RESTEasy Classic (not RESTEasy Reactive at first as I wasn't sure it was already there) and I ended up with a 29 MB binary. Switch to RESTEasy Reactive again with 1.13 and got 31 MB.

So we basically have our default REST app going like that:

Version Size in MB
1.13 with RESTEasy Classic 29
1.13 with RESTEasy Reactive 31
2.16 with RESTEasy Reactive 40
3.7 with RESTEasy Reactive 52

This is not looking good.

/cc @maxandersen @geoand @cescoffier @zakkak

gsmet avatar Feb 08 '24 15:02 gsmet

Do you also have numbers for 3.2?

geoand avatar Feb 08 '24 16:02 geoand

If I am not mistaken

2.16 is using GraalVM/Mandrel 22.3 3.2 is using GraalVM/Mandrel 23.0 3.7 is using GraalVM/Mandrel 23.1

In each of these updates we know that GraalVM/Mandrel increased the resulting binary size:

  1. https://quarkus.io/blog/mandrel-23-0-image-size-increase/
  2. https://quarkus.io/blog/mandrel-23-1-image-size-increase/

It's also worth keeping in mind that if the runtime performance is not impacted by these changes, while they bring some improvements in other aspects (like stability, debugging, etc.), the binary size shouldn't matter that much (at least in the common case).

zakkak avatar Feb 09 '24 09:02 zakkak

Here are some new data points obtained with:

  • the Quarkus default app obtained by using quarkus create app (but making sure we use RESTEasy Reactive as we changed the default at some point, except for the first number which is basically the baseline I still had in mind...)
  • normalized with a container build (not sure why but I obtain 52 for 3.7 when building with a local GraalVM)
Version Size in MB
1.13 with RESTEasy Classic 29
1.13 with RESTEasy Reactive 31
2.0 with RESTEasy Reactive 39
2.1 with RESTEasy Reactive 40
2.16 with RESTEasy Reactive 40
3.0 with RESTEasy Reactive 42
3.1 with RESTEasy Reactive 42
3.2 with RESTEasy Reactive 44
3.3 with RESTEasy Reactive 44
3.4 with RESTEasy Reactive 44
3.5 with RESTEasy Reactive 47
3.6 with RESTEasy Reactive 47
3.7 with RESTEasy Reactive 48

gsmet avatar Feb 09 '24 09:02 gsmet

It's also worth keeping in mind that if the runtime performance is not impacted by these changes, while they bring some improvements in other aspects (like stability, debugging, etc.), the binary size shouldn't matter that much (at least in the common case).

I dunno, I can't say I'm excited about a 65% increase in binary size for a very simple app.

gsmet avatar Feb 09 '24 09:02 gsmet

@gsmet maybe you can add the graalVM version you used for each entry.

The reason is that without large changes (so 3.x) we have a bump of +2 Mb between 3.1 and 3.2 and +# Mb between 3.4 and 3.5...

cescoffier avatar Feb 09 '24 12:02 cescoffier

a picture shows it better .. image

so its not a massive jump from 3.6 to 3.7 here at least? its more 3.4 to 3.5?

maxandersen avatar Feb 15 '24 10:02 maxandersen

similar issue in https://github.com/quarkusio/quarkus/issues/38683 but more affecting time.

maxandersen avatar Feb 15 '24 10:02 maxandersen

@gsmet maybe you can add the graalVM version you used for each entry.

Assuming that @gsmet used the default builder image of each version the table with the Mandrel/GraalVM version is:

Version Mandrel/GraalVM Version Size in MB
1.13 with RESTEasy Classic GraalVM CE 21.0 - Java 11 29
1.13 with RESTEasy Reactive GraalVM CE 21.0 - Java 11 31
2.0 with RESTEasy Reactive GraalVM CE 21.1 - Java 11 39
2.1 with RESTEasy Reactive GraalVM CE 21.0 - Java 11 40
2.16 with RESTEasy Reactive Mandrel 22.3 - Java 17 40
3.0 with RESTEasy Reactive Mandrel 22.3 - Java 17 42
3.1 with RESTEasy Reactive Mandrel 22.3 - Java 17 42
3.2 with RESTEasy Reactive Mandrel 23.0 - Java 17 44
3.3 with RESTEasy Reactive Mandrel 23.0 - Java 17 44
3.4 with RESTEasy Reactive Mandrel 23.0 - Java 17 44
3.5 with RESTEasy Reactive Mandrel 23.1 - Java 21 47
3.6 with RESTEasy Reactive Mandrel 23.1 - Java 21 47
3.7 with RESTEasy Reactive Mandrel 23.1 - Java 21 48

so its not a massive jump from 3.6 to 3.7 here at least? its more 3.4 to 3.5?

Correct, we should either move this discussion in a different issue or edit the title (since we are now discussing a different thing than the issue reported originally).

Now regarding the increases between 3.1 and 3.2, and later between 3.4 and 3.5 one can read https://quarkus.io/blog/mandrel-23-0-image-size-increase/ and https://quarkus.io/blog/mandrel-23-1-image-size-increase/ (at least for the part that Mandrel/GraalVM is responsible for).

zakkak avatar Feb 15 '24 12:02 zakkak