From d929b17a91d793456cff87012a8aa28af2dad89a Mon Sep 17 00:00:00 2001 From: Ivan Ristovic Date: Fri, 28 Mar 2025 08:49:49 +0100 Subject: [PATCH] Remove excess allocations from chunked layouting --- .../ChunkedImageHeapPartition.java | 66 +++++-------------- .../com/oracle/svm/core/image/ImageHeap.java | 4 +- .../svm/core/image/ImageHeapObject.java | 6 +- .../svm/hosted/image/NativeImageHeap.java | 21 +++++- .../wasmgc/image/WasmGCHeapLayouter.java | 7 +- 5 files changed, 45 insertions(+), 59 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapPartition.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapPartition.java index 723e38c43956..3c33faace645 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapPartition.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ChunkedImageHeapPartition.java @@ -26,10 +26,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.NavigableMap; import java.util.Queue; import java.util.TreeMap; @@ -37,7 +34,6 @@ import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.image.ImageHeapObject; import com.oracle.svm.core.image.ImageHeapPartition; -import com.oracle.svm.core.meta.SubstrateObjectConstant; import jdk.graal.compiler.debug.Assertions; @@ -124,63 +120,44 @@ private ImageHeapObject dequeueBestFit(NavigableMap if (nbytes < minimumObjectSize) { return null; } - Map.Entry> entry = sortedObjects.floorEntry(nbytes); - if (entry == null) { + + /** + * Find a floor entry. We are purposefully not calling {@link TreeMap#getFloorEntry(Object)} + * as that method allocates a new entry object. Instead, we fetch the floor key and get the + * value for the returned key. + */ + Long floorKey = sortedObjects.floorKey(nbytes); + if (floorKey == null) { return null; } - Queue queue = entry.getValue(); - ImageHeapObject info = queue.remove(); + Queue queue = sortedObjects.get(floorKey); + ImageHeapObject obj = queue.remove(); if (queue.isEmpty()) { - sortedObjects.remove(entry.getKey()); + sortedObjects.remove(floorKey); } - return info; + return obj; } private NavigableMap> createSortedObjectsMap() { - ImageHeapObject[] sorted = objects.toArray(new ImageHeapObject[0]); - Arrays.sort(sorted, new SizeComparator()); - NavigableMap> map = new TreeMap<>(); - Queue currentQueue = null; - long currentObjectsSize = -1; - for (ImageHeapObject obj : sorted) { + for (ImageHeapObject obj : objects) { long objSize = obj.getSize(); - if (objSize != currentObjectsSize) { - assert objSize > currentObjectsSize && objSize >= ConfigurationValues.getObjectLayout().getMinImageHeapObjectSize() : Assertions.errorMessage(obj, objSize); - currentObjectsSize = objSize; - currentQueue = new ArrayDeque<>(); - map.put(currentObjectsSize, currentQueue); - } - assert currentQueue != null; - currentQueue.add(obj); + assert objSize >= ConfigurationValues.getObjectLayout().getMinImageHeapObjectSize() : Assertions.errorMessage(obj, objSize); + Queue q = map.computeIfAbsent(objSize, k -> new ArrayDeque<>()); + q.add(obj); } return map; } private void appendAllocatedObject(ImageHeapObject info, long allocationOffset) { if (firstObject == null) { - firstObject = extractObject(info); + firstObject = info.getWrapped(); } assert info.getPartition() == this; long offsetInPartition = allocationOffset - startOffset; assert ConfigurationValues.getObjectLayout().isAligned(offsetInPartition) : "start: " + offsetInPartition + " must be aligned."; info.setOffsetInPartition(offsetInPartition); - lastObject = extractObject(info); - } - - private static Object extractObject(ImageHeapObject info) { - if (info.getConstant() instanceof SubstrateObjectConstant) { - return info.getObject(); - } else { - /* - * The info wraps an ImageHeapObject, i.e., a build time representation of an object - * that is not backed by a raw hosted object. We set the partition limit to the actual - * constant. The constant reflection provider knows that this is a build time value, and - * it will not wrap it in a JavaConstant when reading it. This case is not different - * from normal objects referencing simulated objects. - */ - return info.getConstant(); - } + lastObject = info.getWrapped(); } @Override @@ -229,11 +206,4 @@ public boolean isFiller() { public String toString() { return name; } - - private static final class SizeComparator implements Comparator { - @Override - public int compare(ImageHeapObject o1, ImageHeapObject o2) { - return Long.signum(o1.getSize() - o2.getSize()); - } - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java index 6c0c5f12ed7e..be02adb4ed83 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java @@ -24,10 +24,8 @@ */ package com.oracle.svm.core.image; -import java.util.Collection; - public interface ImageHeap { - Collection getObjects(); + Iterable getObjects(); ImageHeapObject addLateToImageHeap(Object object, Object reason); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapObject.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapObject.java index b916134a958a..3f5021116e61 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapObject.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeapObject.java @@ -24,17 +24,15 @@ */ package com.oracle.svm.core.image; -import jdk.vm.ci.meta.JavaConstant; - public interface ImageHeapObject { long getSize(); + Object getWrapped(); + Object getObject(); Class getObjectClass(); - JavaConstant getConstant(); - void setHeapPartition(ImageHeapPartition value); void setOffsetInPartition(long value); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java index 00b99d4da95a..1e357df31d9a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java @@ -76,6 +76,7 @@ import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.HostedConfiguration; +import com.oracle.svm.hosted.ameta.SVMHostedValueProvider; import com.oracle.svm.hosted.config.DynamicHubLayout; import com.oracle.svm.hosted.config.HybridLayout; import com.oracle.svm.hosted.imagelayer.HostedImageLayerBuildingSupport; @@ -874,7 +875,6 @@ public Class getObjectClass() { return clazz.getJavaClass(); } - @Override public ImageHeapConstant getConstant() { return constant; } @@ -912,6 +912,25 @@ public long getSize() { return size; } + /** + * The image builder references heap objects via {@link ImageHeapConstant}, i.e., a build + * time representation of an object that permits hosted constants which may or may not be + * backed by a raw hosted object. For example the class initializer simulation will produce + * simulated objects, not present in the underlying VM. The {@link ImageHeapConstant} can be + * referenced directly from raw objects and the constant reflection provider knows that this + * is a build time value, and it will not wrap it again in a {@link JavaConstant} when + * reading it (see {@link SVMHostedValueProvider#interceptHosted(JavaConstant)}). This is + * useful for example when encoding the heap partitions limits: we simply use the constant + * representation regardless of whether the constant is backed by an object of the host VM + * or not. This case is not different from normal objects referencing simulated objects. The + * {@link NativeImageHeapWriter} will recursively unwrap the {@link ImageHeapConstant} all + * the way to primitive values, so there's no risk of it leaking into the runtime. + */ + @Override + public Object getWrapped() { + return getConstant(); + } + public int getIdentityHashCode() { return identityHashCode; } diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/image/WasmGCHeapLayouter.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/image/WasmGCHeapLayouter.java index 02571054cc40..0e276badb865 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/image/WasmGCHeapLayouter.java +++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/wasmgc/image/WasmGCHeapLayouter.java @@ -26,6 +26,7 @@ package com.oracle.svm.hosted.webimage.wasmgc.image; import java.nio.ByteBuffer; +import java.util.stream.StreamSupport; import com.oracle.graal.pointsto.heap.ImageHeapPrimitiveArray; import com.oracle.svm.core.image.ImageHeap; @@ -64,7 +65,7 @@ public ImageHeapPartition[] getPartitions() { @Override public void assignObjectToPartition(ImageHeapObject info, boolean immutable, boolean references, boolean relocatable, boolean patched) { - if (info.getConstant() instanceof ImageHeapPrimitiveArray) { + if (info.getWrapped() instanceof ImageHeapPrimitiveArray) { singlePartition.add(info); } else { pseudoPartition.add(info); @@ -76,7 +77,7 @@ public WasmGCImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize) { layoutPseudoPartition(); doLayout(); - long totalSize = imageHeap.getObjects().stream().mapToLong(ImageHeapObject::getSize).sum(); + long totalSize = StreamSupport.stream(imageHeap.getObjects().spliterator(), false).mapToLong(ImageHeapObject::getSize).sum(); long serializedSize = singlePartition.getStartOffset() + singlePartition.getSize() - startOffset; return new WasmGCImageHeapLayoutInfo(startOffset, serializedSize, totalSize); } @@ -85,7 +86,7 @@ private void doLayout() { int offset = 0; for (ImageHeapObject info : singlePartition.getObjects()) { // Only primitive arrays are supposed to be in this partition - ImageHeapPrimitiveArray primitiveArray = (ImageHeapPrimitiveArray) info.getConstant(); + ImageHeapPrimitiveArray primitiveArray = (ImageHeapPrimitiveArray) info.getWrapped(); info.setOffsetInPartition(offset); offset += primitiveArray.getType().getComponentType().getStorageKind().getByteCount() * primitiveArray.getLength(); }