From 78b56f52acb9c3a7580837618cccfb95ac85797d Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 11 Sep 2024 13:14:34 +0200 Subject: [PATCH 1/6] Keep encoded graphs in a separate file and load them lazily. --- .../pointsto/AbstractAnalysisEngine.java | 3 + .../graal/pointsto/heap/ImageLayerLoader.java | 74 ++++++++++++++----- .../pointsto/heap/ImageLayerSnapshotUtil.java | 5 ++ .../graal/pointsto/heap/ImageLayerWriter.java | 52 +++++++++++-- .../com/oracle/svm/core/BuildArtifacts.java | 1 + .../svm/hosted/NativeImageGenerator.java | 2 +- .../svm/hosted/heap/SVMImageLayerLoader.java | 7 +- .../HostedImageLayerBuildingSupport.java | 27 +++++-- .../imagelayer/LoadLayerArchiveSupport.java | 4 + .../imagelayer/WriteLayerArchiveSupport.java | 4 +- .../com/oracle/svm/util/FileDumpingUtil.java | 10 +-- 11 files changed, 147 insertions(+), 42 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java index e4a6f3ed1741..93ca08f7f329 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java @@ -232,6 +232,9 @@ public void cleanupAfterAnalysis() { universe.getHeapScanner().cleanupAfterAnalysis(); universe.getHeapVerifier().cleanupAfterAnalysis(); + if (universe.getImageLayerLoader() != null) { + universe.getImageLayerLoader().cleanupAfterAnalysis(); + } } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java index d8ee577e4e57..d2c18eb50c23 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java @@ -105,6 +105,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Executable; import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.nio.file.Path; import java.util.HashMap; import java.util.List; @@ -135,6 +137,7 @@ import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.core.common.NumUtil; import jdk.graal.compiler.core.common.SuppressFBWarnings; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.nodes.EncodedGraph; @@ -302,7 +305,7 @@ public class ImageLayerLoader { protected final Map methods = new ConcurrentHashMap<>(); protected final Map fields = new ConcurrentHashMap<>(); protected final Map constants = new ConcurrentHashMap<>(); - private final List loadPaths; + private final List loadPaths; private final Map baseLayerTypes = new ConcurrentHashMap<>(); private final Map typeToHubIdentityHashCode = new ConcurrentHashMap<>(); private final Map baseLayerMethods = new ConcurrentHashMap<>(); @@ -332,22 +335,22 @@ record FieldIdentifier(String tid, String name) { protected HostedValuesProvider hostedValuesProvider; protected EconomicMap jsonMap; + protected FileChannel graphsChannel; private long imageHeapSize; + public record FilePaths(Path snapshot, Path snapshotGraphs) { + } + public ImageLayerLoader() { this(new ImageLayerSnapshotUtil(), List.of()); } - public ImageLayerLoader(ImageLayerSnapshotUtil imageLayerSnapshotUtil, List loadPaths) { + public ImageLayerLoader(ImageLayerSnapshotUtil imageLayerSnapshotUtil, List loadPaths) { this.imageLayerSnapshotUtil = imageLayerSnapshotUtil; this.loadPaths = loadPaths; } - public List getLoadPaths() { - return loadPaths; - } - public AnalysisUniverse getUniverse() { return universe; } @@ -360,16 +363,18 @@ public void setImageLayerLoaderHelper(ImageLayerLoaderHelper imageLayerLoaderHel this.imageLayerLoaderHelper = imageLayerLoaderHelper; } - /** - * Note this code is not thread safe. - */ - protected void loadJsonMap() { + /** This code is not thread safe. */ + protected void openFilesAndLoadJsonMap() { assert loadPaths.size() == 1 : "Currently only one path is supported for image layer loading " + loadPaths; if (jsonMap == null) { - for (Path layerPath : loadPaths) { - try (InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(layerPath.toFile()))) { - Object json = new JsonParser(inputStreamReader).parse(); - jsonMap = cast(json); + for (FilePaths paths : loadPaths) { + try { + graphsChannel = FileChannel.open(paths.snapshotGraphs); + + try (InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(paths.snapshot.toFile()))) { + Object json = new JsonParser(inputStreamReader).parse(); + jsonMap = cast(json); + } } catch (IOException e) { throw AnalysisError.shouldNotReachHere("Error during image layer snapshot loading", e); } @@ -378,10 +383,20 @@ protected void loadJsonMap() { } public void loadLayerAnalysis() { - loadJsonMap(); + openFilesAndLoadJsonMap(); loadLayerAnalysis0(); } + public void cleanupAfterAnalysis() { + if (graphsChannel != null) { + try { + graphsChannel.close(); + } catch (IOException e) { + throw AnalysisError.shouldNotReachHere(e); + } + } + } + /** * Initializes the {@link ImageLayerLoader}. */ @@ -792,7 +807,7 @@ public boolean hasAnalysisParsedGraph(AnalysisMethod analysisMethod) { public AnalysisParsedGraph getAnalysisParsedGraph(AnalysisMethod analysisMethod) { EconomicMap methodData = getMethodData(analysisMethod); - String encodedAnalyzedGraph = get(methodData, ANALYSIS_PARSED_GRAPH_TAG); + String encodedAnalyzedGraph = readEncodedGraph(methodData, ANALYSIS_PARSED_GRAPH_TAG); Boolean intrinsic = get(methodData, INTRINSIC_TAG); /* * Methods without a persisted graph are folded and static methods. @@ -802,13 +817,36 @@ public AnalysisParsedGraph getAnalysisParsedGraph(AnalysisMethod analysisMethod) if (encodedAnalyzedGraph != null) { EncodedGraph analyzedGraph = (EncodedGraph) ObjectCopier.decode(imageLayerSnapshotUtil.getGraphDecoder(this, analysisMethod, universe.getSnippetReflection()), encodedAnalyzedGraph); if (hasStrengthenedGraph(analysisMethod)) { - loadAllAnalysisElements(get(methodData, STRENGTHENED_GRAPH_TAG)); + loadAllAnalysisElements(readEncodedGraph(methodData, STRENGTHENED_GRAPH_TAG)); } return new AnalysisParsedGraph(analyzedGraph, intrinsic); } throw AnalysisError.shouldNotReachHere("The method " + analysisMethod + " does not have a graph from the base layer"); } + private String readEncodedGraph(EconomicMap methodData, String elementIdentifier) { + String location = get(methodData, elementIdentifier); + int closingBracketAt = location.length() - 1; + AnalysisError.guarantee(location.charAt(0) == '@' && location.charAt(closingBracketAt) == ']', "Location must start with '@' and end with ']': %s", location); + int openingBracketAt = location.indexOf('[', 1, closingBracketAt); + AnalysisError.guarantee(openingBracketAt < closingBracketAt, "Location does not contain '[' at expected location: %s", location); + long offset; + long nbytes; + try { + offset = Long.parseUnsignedLong(location.substring(1, openingBracketAt)); + nbytes = Long.parseUnsignedLong(location.substring(openingBracketAt + 1, closingBracketAt)); + } catch (NumberFormatException e) { + throw AnalysisError.shouldNotReachHere("Location contains invalid positive integer(s): " + location); + } + ByteBuffer bb = ByteBuffer.allocate(NumUtil.safeToInt(nbytes)); + try { + graphsChannel.read(bb, offset); + } catch (IOException e) { + throw AnalysisError.shouldNotReachHere("Failed reading a graph from location: " + location, e); + } + return new String(bb.array(), ImageLayerWriter.GRAPHS_CHARSET); + } + public boolean hasStrengthenedGraph(AnalysisMethod analysisMethod) { EconomicMap methodData = getMethodData(analysisMethod); return get(methodData, STRENGTHENED_GRAPH_TAG) != null; @@ -816,7 +854,7 @@ public boolean hasStrengthenedGraph(AnalysisMethod analysisMethod) { public void setStrengthenedGraph(AnalysisMethod analysisMethod) { EconomicMap methodData = getMethodData(analysisMethod); - String encodedAnalyzedGraph = get(methodData, STRENGTHENED_GRAPH_TAG); + String encodedAnalyzedGraph = readEncodedGraph(methodData, STRENGTHENED_GRAPH_TAG); EncodedGraph analyzedGraph = (EncodedGraph) ObjectCopier.decode(imageLayerSnapshotUtil.getGraphDecoder(this, analysisMethod, universe.getSnippetReflection()), encodedAnalyzedGraph); processGraph(analyzedGraph); analysisMethod.setAnalyzedGraph(analyzedGraph); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java index 0e333da2eee0..609a55de69c0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java @@ -58,6 +58,7 @@ public class ImageLayerSnapshotUtil { public static final String FILE_NAME_PREFIX = "layer-snapshot-"; + public static final String GRAPHS_FILE_NAME_PREFIX = "layer-snapshot-graphs-"; public static final String FILE_EXTENSION = ".json"; public static final String CONSTRUCTOR_NAME = ""; @@ -196,6 +197,10 @@ public static String snapshotFileName(String imageName) { return FILE_NAME_PREFIX + imageName + FILE_EXTENSION; } + public static String snapshotGraphsFileName(String imageName) { + return GRAPHS_FILE_NAME_PREFIX + imageName + FILE_EXTENSION; + } + public String getTypeIdentifier(AnalysisType type) { String javaName = type.toJavaName(true); return addModuleName(javaName, type.getJavaClass().getModule().getName()); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java index 253f6610100c..a247e3df1376 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java @@ -93,8 +93,13 @@ import static jdk.graal.compiler.java.LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING; import java.io.IOException; +import java.io.PrintWriter; import java.lang.reflect.Executable; import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -132,6 +137,8 @@ import jdk.vm.ci.meta.ResolvedJavaField; public class ImageLayerWriter { + static final Charset GRAPHS_CHARSET = Charset.defaultCharset(); + protected final ImageLayerSnapshotUtil imageLayerSnapshotUtil; private ImageLayerWriterHelper imageLayerWriterHelper; private ImageHeap imageHeap; @@ -145,7 +152,9 @@ public class ImageLayerWriter { protected final Map> methodsMap; protected final Map> fieldsMap; private final Map> constantsMap; + private final Graphs graphs; FileInfo fileInfo; + FileInfo graphsFileInfo; private final boolean useSharedLayerGraphs; protected final Set> elementsToPersist = ConcurrentHashMap.newKeySet(); @@ -153,6 +162,26 @@ public class ImageLayerWriter { private record FileInfo(Path layerSnapshotPath, String fileName, String suffix) { } + private static class Graphs { + final List encodedGraphs = new ArrayList<>(4096); + long currentOffset = 0; + + synchronized String add(String encodedGraph) { + ByteBuffer encoded = GRAPHS_CHARSET.encode(encodedGraph); + encodedGraphs.add(encoded); + int size = encoded.limit(); + long offset = currentOffset; + currentOffset += size; + return new StringBuilder("@").append(offset).append("[").append(size).append("]").toString(); + } + + synchronized void dump(WritableByteChannel channel) throws IOException { + for (ByteBuffer bb : encodedGraphs) { + channel.write(bb); + } + } + } + public ImageLayerWriter() { this(true, new ImageLayerSnapshotUtil()); } @@ -168,6 +197,7 @@ public ImageLayerWriter(boolean useSharedLayerGraphs, ImageLayerSnapshotUtil ima this.methodsMap = new ConcurrentHashMap<>(); this.fieldsMap = new ConcurrentHashMap<>(); this.constantsMap = new ConcurrentHashMap<>(); + this.graphs = new Graphs(); } public void setInternedStringsIdentityMap(IdentityHashMap map) { @@ -182,22 +212,33 @@ public void setImageLayerWriterHelper(ImageLayerWriterHelper imageLayerWriterHel this.imageLayerWriterHelper = imageLayerWriterHelper; } - public void setFileInfo(Path layerSnapshotPath, String fileName, String suffix) { + public void setSnapshotFileInfo(Path layerSnapshotPath, String fileName, String suffix) { fileInfo = new FileInfo(layerSnapshotPath, fileName, suffix); } + public void setSnapshotGraphsFileInfo(Path layerGraphsPath, String fileName, String suffix) { + graphsFileInfo = new FileInfo(layerGraphsPath, fileName, suffix); + } + public void setAnalysisUniverse(AnalysisUniverse aUniverse) { this.aUniverse = aUniverse; } - public void dumpFile() { - FileDumpingUtil.dumpFile(fileInfo.layerSnapshotPath, fileInfo.fileName, fileInfo.suffix, writer -> { - try (JsonWriter jw = new JsonWriter(writer)) { + public void dumpFiles() { + FileDumpingUtil.dumpFile(fileInfo.layerSnapshotPath, fileInfo.fileName, fileInfo.suffix, outputStream -> { + try (JsonWriter jw = new JsonWriter(new PrintWriter(outputStream))) { jw.print(jsonMap); } catch (IOException e) { throw new RuntimeException(e); } }); + FileDumpingUtil.dumpFile(graphsFileInfo.layerSnapshotPath, graphsFileInfo.fileName, graphsFileInfo.suffix, stream -> { + try { + graphs.dump(Channels.newChannel(stream)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); } public void persistImageHeapSize(long imageHeapSize) { @@ -413,7 +454,8 @@ private boolean persistGraph(EncodedGraph analyzedGraph, EconomicMap entryPoints, try (TemporaryBuildDirectoryProviderImpl tempDirectoryProvider = new TemporaryBuildDirectoryProviderImpl()) { ImageSingletons.add(TemporaryBuildDirectoryProvider.class, tempDirectoryProvider); if (ImageLayerBuildingSupport.buildingSharedLayer()) { - HostedImageLayerBuildingSupport.setupImageLayerArtifact(imageName); + HostedImageLayerBuildingSupport.setupImageLayerArtifacts(imageName); } doRun(entryPoints, javaMainSupport, imageName, k, harnessSubstitutions); if (ImageLayerBuildingSupport.buildingSharedLayer()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java index d435896556c0..2b9402318fdf 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java @@ -46,7 +46,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -105,8 +104,8 @@ public class SVMImageLayerLoader extends ImageLayerLoader { private HostedUniverse hostedUniverse; private ImageClassLoader imageClassLoader; - public SVMImageLayerLoader(List loaderPaths) { - super(new SVMImageLayerSnapshotUtil(), loaderPaths); + public SVMImageLayerLoader(List loadPaths) { + super(new SVMImageLayerSnapshotUtil(), loadPaths); dynamicHubArrayHubField = ReflectionUtil.lookupField(DynamicHub.class, "arrayHub"); } @@ -345,7 +344,7 @@ protected ImageHeapConstant getValueForObject(Object object) { } public Map>> loadImageSingletons(Object forbiddenObject) { - loadJsonMap(); + openFilesAndLoadJsonMap(); return loadImageSingletons0(forbiddenObject); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java index 6f201014c3cd..5d0a1d7fc49c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java @@ -41,6 +41,7 @@ import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; +import com.oracle.graal.pointsto.heap.ImageLayerLoader; import com.oracle.graal.pointsto.heap.ImageLayerSnapshotUtil; import com.oracle.svm.core.BuildArtifacts; import com.oracle.svm.core.SubstrateOptions; @@ -94,7 +95,7 @@ public LoadLayerArchiveSupport getLoadLayerArchiveSupport() { } public void archiveLayer(String imageName) { - writer.dumpFile(); + writer.dumpFiles(); writeLayerArchiveSupport.write(imageName); } @@ -177,7 +178,8 @@ public static HostedImageLayerBuildingSupport initialize(HostedOptionValues valu if (isLayerOptionEnabled(LayerUse, values)) { Path layerFileName = LayerUse.getValue(values).lastValue().orElseThrow(); loadLayerArchiveSupport = new LoadLayerArchiveSupport(layerFileName, archiveSupport); - loader = new SVMImageLayerLoader(List.of(loadLayerArchiveSupport.getSnapshotPath())); + ImageLayerLoader.FilePaths paths = new ImageLayerLoader.FilePaths(loadLayerArchiveSupport.getSnapshotPath(), loadLayerArchiveSupport.getSnapshotGraphsPath()); + loader = new SVMImageLayerLoader(List.of(paths)); } boolean buildingImageLayer = loader != null || writer != null; @@ -199,14 +201,25 @@ public static void setupSharedLayerLibrary(NativeLibraries nativeLibs) { nativeLibs.addDynamicNonJniLibrary(libName.substring("lib".length(), libName.indexOf(".so"))); } - public static void setupImageLayerArtifact(String imageName) { + public static void setupImageLayerArtifacts(String imageName) { VMError.guarantee(!imageName.contains(File.separator), "Expected simple file name, found %s.", imageName); + Path snapshotFile = NativeImageGenerator.getOutputDirectory().resolve(ImageLayerSnapshotUtil.snapshotFileName(imageName)); - Path fileName = snapshotFile.getFileName(); + Path snapshotFileName = getFileName(snapshotFile); + HostedImageLayerBuildingSupport.singleton().getWriter().setSnapshotFileInfo(snapshotFile, snapshotFileName.toString(), ImageLayerSnapshotUtil.FILE_EXTENSION); + BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.LAYER_SNAPSHOT, snapshotFile); + + Path graphsFile = NativeImageGenerator.getOutputDirectory().resolve(ImageLayerSnapshotUtil.snapshotGraphsFileName(imageName)); + Path graphsFileName = getFileName(graphsFile); + HostedImageLayerBuildingSupport.singleton().getWriter().setSnapshotGraphsFileInfo(graphsFile, graphsFileName.toString(), ImageLayerSnapshotUtil.FILE_EXTENSION); + BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.LAYER_SNAPSHOT_GRAPHS, graphsFile); + } + + private static Path getFileName(Path path) { + Path fileName = path.getFileName(); if (fileName == null) { - throw VMError.shouldNotReachHere("Layer snapshot file doesn't exist."); + throw VMError.shouldNotReachHere("Layer snapshot file(s) missing."); } - HostedImageLayerBuildingSupport.singleton().getWriter().setFileInfo(snapshotFile, fileName.toString(), ImageLayerSnapshotUtil.FILE_EXTENSION); - BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.LAYER_SNAPSHOT, snapshotFile); + return fileName; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java index a69a3d37ec40..6431886058bd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java @@ -55,6 +55,10 @@ public Path getSnapshotPath() { return expandedInputLayerDir.resolve(ImageLayerSnapshotUtil.snapshotFileName(layerProperties.layerName())); } + public Path getSnapshotGraphsPath() { + return expandedInputLayerDir.resolve(ImageLayerSnapshotUtil.snapshotGraphsFileName(layerProperties.layerName())); + } + private static Path validateLayerFile(Path layerFile) { Path fileName = layerFile.getFileName(); if (fileName == null || !fileName.toString().endsWith(LAYER_FILE_EXTENSION)) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java index cfaa96a5e85b..c58701da4406 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java @@ -75,9 +75,11 @@ public void write(String imageName) { layerProperties.write(); try (JarOutputStream jarOutStream = new JarOutputStream(Files.newOutputStream(outputLayerLocation), archiveSupport.createManifest())) { Path imageBuilderOutputDir = NativeImageGenerator.getOutputDirectory(); - // copy the layer snapshot file to the jar + // copy the layer snapshot file and its graphs file to the jar Path snapshotFile = BuildArtifacts.singleton().get(BuildArtifacts.ArtifactType.LAYER_SNAPSHOT).getFirst(); archiveSupport.addFileToJar(imageBuilderOutputDir, snapshotFile, outputLayerLocation, jarOutStream); + Path snapshotGraphsFile = BuildArtifacts.singleton().get(BuildArtifacts.ArtifactType.LAYER_SNAPSHOT_GRAPHS).getFirst(); + archiveSupport.addFileToJar(imageBuilderOutputDir, snapshotGraphsFile, outputLayerLocation, jarOutStream); // copy the shared object file to the jar Path sharedLibFile = BuildArtifacts.singleton().get(BuildArtifacts.ArtifactType.IMAGE_LAYER).getFirst(); archiveSupport.addFileToJar(imageBuilderOutputDir, sharedLibFile, outputLayerLocation, jarOutStream); diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/FileDumpingUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/FileDumpingUtil.java index b52f48de9ff1..8447a9c2cc86 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/FileDumpingUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/FileDumpingUtil.java @@ -26,7 +26,7 @@ import java.io.FileOutputStream; import java.io.IOException; -import java.io.PrintWriter; +import java.io.OutputStream; import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.Path; @@ -38,17 +38,15 @@ public class FileDumpingUtil { @SuppressFBWarnings(value = "", justification = "FB reports null pointer dereferencing although it is not possible in this case.") - public static void dumpFile(Path path, String name, String suffix, Consumer writerConsumer) { + public static void dumpFile(Path path, String name, String suffix, Consumer streamConsumer) { long start = System.nanoTime(); String filePrefix = String.format(name + "-%d", start); try { Path tempPath = Files.createTempFile(path.getParent(), filePrefix, suffix); try (FileOutputStream fileOutputStream = new FileOutputStream(tempPath.toFile())) { - try (PrintWriter writer = new PrintWriter(fileOutputStream)) { - writerConsumer.accept(writer); - } - tryAtomicMove(tempPath, path); + streamConsumer.accept(fileOutputStream); } + tryAtomicMove(tempPath, path); } catch (Exception e) { throw GraalError.shouldNotReachHere(e, "Error during file dumping."); } From 894ec6b4fd66cd56d27f8c6c630111970c0d735e Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 11 Sep 2024 13:15:59 +0200 Subject: [PATCH 2/6] Disable layer zip compression for faster writing and loading. --- .../oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java index c58701da4406..938194568b4e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java @@ -75,6 +75,8 @@ public void write(String imageName) { layerProperties.write(); try (JarOutputStream jarOutStream = new JarOutputStream(Files.newOutputStream(outputLayerLocation), archiveSupport.createManifest())) { Path imageBuilderOutputDir = NativeImageGenerator.getOutputDirectory(); + // disable compression for significant (un)archiving speedup at the cost of file size + jarOutStream.setLevel(0); // copy the layer snapshot file and its graphs file to the jar Path snapshotFile = BuildArtifacts.singleton().get(BuildArtifacts.ArtifactType.LAYER_SNAPSHOT).getFirst(); archiveSupport.addFileToJar(imageBuilderOutputDir, snapshotFile, outputLayerLocation, jarOutStream); From c04bfa68e07bd487de1966bbce7a9e8edfc30247 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 11 Sep 2024 13:17:14 +0200 Subject: [PATCH 3/6] Add buffer in JsonParser that does not use locking (unlike BufferedReader). --- .../graal/compiler/util/json/JsonParser.java | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonParser.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonParser.java index a94f71390048..c5fe5645a893 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonParser.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonParser.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.nio.CharBuffer; import java.util.ArrayList; import java.util.List; @@ -64,13 +65,18 @@ public final class JsonParser { private final Reader source; // Current reading position within source - private int pos = 0; + private int pos; // Current line number, used for error reporting. private int line = 0; // Position of the start of the current line in source, used for error reporting. private int beginningOfLine = 0; // Next character to be scanned, obtained from source private int next; + /** + * Characters following 'next'. Managing our own buffer instead of using {@link BufferedReader} + * avoids unnecessary locking that becomes significant when getting individual characters. + */ + private final CharBuffer buffer = CharBuffer.allocate(8192).limit(0); private static final int EOF = -1; @@ -90,12 +96,12 @@ public JsonParser(String source) throws IOException { /** * Creates a new {@link JsonParser} that reads characters from {@code source}. * - * @param source character stream containing JSON text. Will be adapted internally through a - * {@link BufferedReader}. + * @param source character reader containing JSON text. */ public JsonParser(Reader source) throws IOException { - this.source = new BufferedReader(source); - next = source.read(); + this.source = source; + next(); + this.pos = 0; } /** @@ -475,8 +481,10 @@ private Number parseNumber() throws IOException { } private Object parseKeyword(final String keyword, final Object value) throws IOException { - if (!read(keyword.length()).equals(keyword)) { - throw expectedError(pos, "json literal", "ident"); + for (int i = 0; i < keyword.length(); i++) { + if (next() != keyword.charAt(i)) { + throw expectedError(pos, "json literal", "ident"); + } } return value; } @@ -487,22 +495,19 @@ private int peek() { private int next() throws IOException { int cur = next; - next = source.read(); + if (buffer.isEmpty()) { + buffer.clear(); // resets position and limit + if (source.read(buffer) == -1) { // eof + next = -1; + return cur; + } + buffer.flip(); + } + next = buffer.get(); pos++; return cur; } - private String read(int length) throws IOException { - char[] buffer = new char[length]; - - buffer[0] = (char) peek(); - source.read(buffer, 1, length - 1); - pos += length - 1; - next(); - - return String.valueOf(buffer); - } - private void skipWhiteSpace() throws IOException { while (next != -1) { switch (peek()) { From 0ef7cecaa9949070a172a3412a37083cf3434596 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 11 Sep 2024 13:33:32 +0200 Subject: [PATCH 4/6] Iterate streams instead of eagerly turning them into lists. --- .../jdk/graal/compiler/util/ObjectCopier.java | 6 ++--- .../graal/pointsto/heap/ImageLayerLoader.java | 24 ++++++++++--------- .../svm/hosted/heap/SVMImageLayerLoader.java | 21 ++++++++-------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java index faa78f81f31c..9af28e1cde78 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java @@ -516,8 +516,7 @@ private Object decode(String encoded) { deferred = new ArrayList<>(); lineNum = 0; - List lines = encoded.lines().toList(); - Iterator iter = lines.iterator(); + Iterator iter = encoded.lines().iterator(); try { while (iter.hasNext()) { String line = iter.next(); @@ -707,7 +706,8 @@ private Object decode(String encoded) { d.runnable().run(); } } catch (Throwable e) { - throw new GraalError(e, "Error on line %d: %s", lineNum, lines.get(lineNum - 1)); + String line = encoded.lines().skip(lineNum - 1).findFirst().get(); + throw new GraalError(e, "Error on line %d: %s", lineNum, line); } finally { deferred = null; lineNum = -1; diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java index d2c18eb50c23..9d841571cdab 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java @@ -865,17 +865,19 @@ protected void processGraph(EncodedGraph encodedGraph) { } - protected void loadAllAnalysisElements(String encoding) { - for (String line : encoding.lines().toList()) { - if (line.contains(PointsToAnalysisType.class.getName())) { - getAnalysisType(getId(line)); - } else if (line.contains(PointsToAnalysisMethod.class.getName())) { - getAnalysisMethod(getId(line)); - } else if (line.contains(PointsToAnalysisField.class.getName())) { - getAnalysisField(getId(line)); - } else if (line.contains(ImageHeapInstance.class.getName()) || line.contains(ImageHeapObjectArray.class.getName()) || line.contains(ImageHeapPrimitiveArray.class.getName())) { - getOrCreateConstant(getId(line)); - } + private void loadAllAnalysisElements(String encoding) { + encoding.lines().forEach(this::loadEncodedGraphLineAnalysisElements); + } + + protected void loadEncodedGraphLineAnalysisElements(String line) { + if (line.contains(PointsToAnalysisType.class.getName())) { + getAnalysisType(getId(line)); + } else if (line.contains(PointsToAnalysisMethod.class.getName())) { + getAnalysisMethod(getId(line)); + } else if (line.contains(PointsToAnalysisField.class.getName())) { + getAnalysisField(getId(line)); + } else if (line.contains(ImageHeapInstance.class.getName()) || line.contains(ImageHeapObjectArray.class.getName()) || line.contains(ImageHeapPrimitiveArray.class.getName())) { + getOrCreateConstant(getId(line)); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java index 2b9402318fdf..32d964b48e63 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java @@ -209,18 +209,17 @@ protected void processGraph(EncodedGraph encodedGraph) { } @Override - protected void loadAllAnalysisElements(String encoding) { - for (String line : encoding.lines().toList()) { - if (line.contains(HostedInstanceClass.class.getName()) || line.contains(HostedPrimitiveType.class.getName()) || line.contains(HostedArrayClass.class.getName()) || - line.contains(HostedInterface.class.getName())) { - getAnalysisType(getId(line)); - } else if (line.contains(HostedMethod.class.getName())) { - getAnalysisMethod(getId(line)); - } else if (line.contains(HostedField.class.getName())) { - getAnalysisField(getId(line)); - } + protected void loadEncodedGraphLineAnalysisElements(String line) { + if (line.contains(HostedInstanceClass.class.getName()) || line.contains(HostedPrimitiveType.class.getName()) || line.contains(HostedArrayClass.class.getName()) || + line.contains(HostedInterface.class.getName())) { + getAnalysisType(getId(line)); + } else if (line.contains(HostedMethod.class.getName())) { + getAnalysisMethod(getId(line)); + } else if (line.contains(HostedField.class.getName())) { + getAnalysisField(getId(line)); + } else { + super.loadEncodedGraphLineAnalysisElements(line); } - super.loadAllAnalysisElements(encoding); } @Override From 9b1cbbbdca94b46dbc931a45561fc5d1cdf1cfd7 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 12 Sep 2024 16:22:08 +0200 Subject: [PATCH 5/6] Immediately write image layer graphs to temporary file. --- .../graal/pointsto/heap/ImageLayerWriter.java | 71 +++++++++++-------- .../HostedImageLayerBuildingSupport.java | 2 +- .../com/oracle/svm/util/FileDumpingUtil.java | 16 +++-- 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java index a247e3df1376..b514539380da 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java @@ -97,18 +97,20 @@ import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.WritableByteChannel; +import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumSet; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; import org.graalvm.collections.EconomicMap; @@ -152,32 +154,50 @@ public class ImageLayerWriter { protected final Map> methodsMap; protected final Map> fieldsMap; private final Map> constantsMap; - private final Graphs graphs; - FileInfo fileInfo; - FileInfo graphsFileInfo; + private FileInfo fileInfo; + private GraphsOutput graphsOutput; private final boolean useSharedLayerGraphs; protected final Set> elementsToPersist = ConcurrentHashMap.newKeySet(); - private record FileInfo(Path layerSnapshotPath, String fileName, String suffix) { + private record FileInfo(Path layerFilePath, String fileName, String suffix) { } - private static class Graphs { - final List encodedGraphs = new ArrayList<>(4096); - long currentOffset = 0; + private static class GraphsOutput { + private final Path path; + private final Path tempPath; + private final FileChannel tempChannel; - synchronized String add(String encodedGraph) { + private final AtomicLong currentOffset = new AtomicLong(0); + + GraphsOutput(Path path, String fileName, String suffix) { + this.path = path; + this.tempPath = FileDumpingUtil.createTempFile(path.getParent(), fileName, suffix); + try { + this.tempChannel = FileChannel.open(this.tempPath, EnumSet.of(StandardOpenOption.WRITE)); + } catch (IOException e) { + throw GraalError.shouldNotReachHere(e, "Error opening temporary graphs file."); + } + } + + String add(String encodedGraph) { ByteBuffer encoded = GRAPHS_CHARSET.encode(encodedGraph); - encodedGraphs.add(encoded); int size = encoded.limit(); - long offset = currentOffset; - currentOffset += size; + long offset = currentOffset.getAndAdd(size); + try { + tempChannel.write(encoded, offset); + } catch (Exception e) { + throw GraalError.shouldNotReachHere(e, "Error during graphs file dumping."); + } return new StringBuilder("@").append(offset).append("[").append(size).append("]").toString(); } - synchronized void dump(WritableByteChannel channel) throws IOException { - for (ByteBuffer bb : encodedGraphs) { - channel.write(bb); + void finish() { + try { + tempChannel.close(); + FileDumpingUtil.tryAtomicMove(tempPath, path); + } catch (Exception e) { + throw GraalError.shouldNotReachHere(e, "Error during graphs file dumping."); } } } @@ -197,7 +217,6 @@ public ImageLayerWriter(boolean useSharedLayerGraphs, ImageLayerSnapshotUtil ima this.methodsMap = new ConcurrentHashMap<>(); this.fieldsMap = new ConcurrentHashMap<>(); this.constantsMap = new ConcurrentHashMap<>(); - this.graphs = new Graphs(); } public void setInternedStringsIdentityMap(IdentityHashMap map) { @@ -216,8 +235,9 @@ public void setSnapshotFileInfo(Path layerSnapshotPath, String fileName, String fileInfo = new FileInfo(layerSnapshotPath, fileName, suffix); } - public void setSnapshotGraphsFileInfo(Path layerGraphsPath, String fileName, String suffix) { - graphsFileInfo = new FileInfo(layerGraphsPath, fileName, suffix); + public void openGraphsOutput(Path layerGraphsPath, String fileName, String suffix) { + AnalysisError.guarantee(graphsOutput == null, "Graphs file has already been opened"); + graphsOutput = new GraphsOutput(layerGraphsPath, fileName, suffix); } public void setAnalysisUniverse(AnalysisUniverse aUniverse) { @@ -225,20 +245,15 @@ public void setAnalysisUniverse(AnalysisUniverse aUniverse) { } public void dumpFiles() { - FileDumpingUtil.dumpFile(fileInfo.layerSnapshotPath, fileInfo.fileName, fileInfo.suffix, outputStream -> { + graphsOutput.finish(); + + FileDumpingUtil.dumpFile(fileInfo.layerFilePath, fileInfo.fileName, fileInfo.suffix, outputStream -> { try (JsonWriter jw = new JsonWriter(new PrintWriter(outputStream))) { jw.print(jsonMap); } catch (IOException e) { throw new RuntimeException(e); } }); - FileDumpingUtil.dumpFile(graphsFileInfo.layerSnapshotPath, graphsFileInfo.fileName, graphsFileInfo.suffix, stream -> { - try { - graphs.dump(Channels.newChannel(stream)); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); } public void persistImageHeapSize(long imageHeapSize) { @@ -454,7 +469,7 @@ private boolean persistGraph(EncodedGraph analyzedGraph, EconomicMap streamConsumer) { - long start = System.nanoTime(); - String filePrefix = String.format(name + "-%d", start); + Path tempPath = createTempFile(path.getParent(), name, suffix); try { - Path tempPath = Files.createTempFile(path.getParent(), filePrefix, suffix); try (FileOutputStream fileOutputStream = new FileOutputStream(tempPath.toFile())) { streamConsumer.accept(fileOutputStream); } @@ -52,7 +60,7 @@ public static void dumpFile(Path path, String name, String suffix, Consumer Date: Fri, 13 Sep 2024 14:15:13 +0200 Subject: [PATCH 6/6] Rename tryAtomicMove to moveTryAtomically. --- .../src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java | 2 +- .../src/com/oracle/svm/util/FileDumpingUtil.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java index b514539380da..9823cf2876b1 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java @@ -195,7 +195,7 @@ String add(String encodedGraph) { void finish() { try { tempChannel.close(); - FileDumpingUtil.tryAtomicMove(tempPath, path); + FileDumpingUtil.moveTryAtomically(tempPath, path); } catch (Exception e) { throw GraalError.shouldNotReachHere(e, "Error during graphs file dumping."); } diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/FileDumpingUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/FileDumpingUtil.java index 9add5428cf38..e7194e4ec78f 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/FileDumpingUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/FileDumpingUtil.java @@ -54,13 +54,13 @@ public static void dumpFile(Path path, String name, String suffix, Consumer