diff --git a/docs/reference-manual/native-image/BuildOutput.md b/docs/reference-manual/native-image/BuildOutput.md index 0cfabc2840c2..1f1a9c0de17f 100644 --- a/docs/reference-manual/native-image/BuildOutput.md +++ b/docs/reference-manual/native-image/BuildOutput.md @@ -24,7 +24,7 @@ Below is the example output when building a native executable of the `HelloWorld GraalVM Native Image: Generating 'helloworld' (executable)... ================================================================================ [1/8] Initializing... (2.8s @ 0.15GB) - Java version: 20+34, vendor version: GraalVM CE 20-dev+34.1 + Java version: 25+13, vendor version: GraalVM CE 25-dev+13.1 Graal compiler: optimization level: 2, target machine: x86-64-v3 C compiler: gcc (linux, x86_64, 12.2.0) Garbage collector: Serial GC (max heap size: 80% of RAM) @@ -49,7 +49,7 @@ GraalVM Native Image: Generating 'helloworld' (executable)... 7.03MB (32.02%) for image heap: 93,301 objects and 5 resources 8.96MB (40.83%) for debug info generated in 1.0s 659.13kB ( 2.93%) for other data - 21.96MB in total + 21.96MB in total image size, 21.04MB in total file size -------------------------------------------------------------------------------- Top 10 origins of code area: Top 10 object types in image heap: 4.03MB java.base 1.14MB byte[] for code metadata @@ -201,6 +201,10 @@ The progress indicator is printed periodically at an increasing interval. ### Creating Image In this stage, the native binary is created and written to disk. Debug info is also generated as part of this stage (if requested). +This section breaks down the total image size as well as [code area](#glossary-code-area) and [image heap](#glossary-image-heap) (see below for more details). +The total image size is calculated before linking by summing the sizes of the code area, image heap, debug information (if requested and embedded in the binary), and other data. +The total file size is the actual size of the image on disk after linking. +Typically, the file size is slightly smaller than the image size due to additional link time optimizations. #### Code Area The code area contains machine code produced by the Graal compiler for all reachable methods. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java index 67996723d9ea..a35f87d6a30a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java @@ -30,8 +30,9 @@ public class ByteFormattingUtil { private static final double BYTES_TO_GB = 1000d * 1000d * 1000d; public static String bytesToHuman(long bytes) { + assert bytes >= 0; if (bytes < BYTES_TO_KB) { - return toHuman(bytes, "B"); + return plainBytes(bytes, "B"); } else if (bytes < BYTES_TO_MB) { return toHuman(bytes / BYTES_TO_KB, "kB"); } else if (bytes < BYTES_TO_GB) { @@ -48,4 +49,9 @@ public static String bytesToHumanGB(long bytes) { private static String toHuman(double value, String unit) { return "%.2f%s".formatted(value, unit); } + + private static String plainBytes(long value, String unit) { + assert 0 <= value && value < BYTES_TO_KB; + return "%d%s".formatted(value, unit); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 96754c5f959e..8fe78fb8b2c6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -734,6 +734,7 @@ protected void doRun(Map entryPoints, JavaMainSupport j compileQueue.purge(); int numCompilations = codeCache.getOrderedCompilations().size(); + int imageDiskFileSize = -1; try (StopTimer t = TimerCollection.createTimerAndStart(TimerCollection.Registry.WRITE)) { loader.watchdog.recordActivity(); @@ -755,6 +756,12 @@ protected void doRun(Map entryPoints, JavaMainSupport j AfterImageWriteAccessImpl afterConfig = new AfterImageWriteAccessImpl(featureHandler, loader, hUniverse, inv, tmpDir, image.getImageKind(), debug); featureHandler.forEachFeature(feature -> feature.afterImageWrite(afterConfig)); + try { + // size changes during linking and afterConfig phase + imageDiskFileSize = (int) inv.getOutputFile().toFile().length(); + } catch (Exception e) { + imageDiskFileSize = -1; // we can't read a disk file size + } } try (StopTimer t = TimerCollection.createTimerAndStart(TimerCollection.Registry.ARCHIVE_LAYER)) { if (ImageLayerBuildingSupport.buildingSharedLayer()) { @@ -762,7 +769,8 @@ protected void doRun(Map entryPoints, JavaMainSupport j HostedImageLayerBuildingSupport.singleton().archiveLayer(imageName); } } - reporter.printCreationEnd(image.getImageFileSize(), heap.getLayerObjectCount(), image.getImageHeapSize(), codeCache.getCodeAreaSize(), numCompilations, image.getDebugInfoSize()); + reporter.printCreationEnd(image.getImageFileSize(), heap.getLayerObjectCount(), image.getImageHeapSize(), codeCache.getCodeAreaSize(), numCompilations, image.getDebugInfoSize(), + imageDiskFileSize); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index f618815ce85e..d17651603c89 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -584,7 +584,7 @@ public void setDebugInfoTimer(Timer timer) { this.debugInfoTimer = timer; } - public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageHeapSize, int codeAreaSize, int numCompilations, int debugInfoSize) { + public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageHeapSize, int codeAreaSize, int numCompilations, int debugInfoSize, int imageDiskFileSize) { recordJsonMetric(ImageDetailKey.IMAGE_HEAP_OBJECT_COUNT, heapObjectCount); Timer imageTimer = getTimer(TimerCollection.Registry.IMAGE); Timer writeTimer = getTimer(TimerCollection.Registry.WRITE); @@ -620,7 +620,11 @@ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageH recordJsonMetric(ImageDetailKey.NUM_COMP_UNITS, numCompilations); l().a(format, ByteFormattingUtil.bytesToHuman(otherBytes), Utils.toPercentage(otherBytes, imageFileSize)) .doclink("other data", "#glossary-other-data").println(); - l().a("%9s in total", ByteFormattingUtil.bytesToHuman(imageFileSize)).println(); + l().a("%9s in total image size", ByteFormattingUtil.bytesToHuman(imageFileSize)); + if (imageDiskFileSize >= 0) { + l().a(", %s in total file size", ByteFormattingUtil.bytesToHuman(imageDiskFileSize)); + } + l().println(); printBreakdowns(); ImageSingletons.lookup(ProgressReporterFeature.class).afterBreakdowns(); printRecommendations();