Skip to content

Commit afaec4b

Browse files
committed
[GR-67701] [GR-68034] Improve the parallelism of ImageHeapCollectionFeature and the associated ImageHeap{List,Map} data structures
PullRequest: graal/21495
2 parents c48891b + 8182919 commit afaec4b

File tree

3 files changed

+116
-43
lines changed

3 files changed

+116
-43
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapList.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -40,14 +40,19 @@
4040
/**
4141
* A list that is filled at image build time while the static analysis is running, and then read at
4242
* run time.
43-
*
43+
* <p>
4444
* Filling the list at image build time is thread safe. Every object added to the list while the
4545
* static analysis is running is properly added to the shadow heap.
46-
*
46+
* <p>
4747
* The list is immutable at run time. The run-time list can optionally be sorted, to make code at
4848
* run time deterministic regardless of the order in which elements are discovered and added at
4949
* image build time. Sorting happens at image build time, but does not affect the list that users
5050
* are adding to at image build time.
51+
* <p>
52+
* We support (mostly) <b>append-only</b> semantics. Removing elements is not allowed. Changing the
53+
* content of the list via iterator is <b>not supported</b>. Note that we allow updating elements at
54+
* existing indexes as this operation is already used. We have deliberately chosen this design to
55+
* limit the number of analysis rescans needed.
5156
*/
5257
@Platforms(Platform.HOSTED_ONLY.class) //
5358
public final class ImageHeapList {
@@ -76,6 +81,12 @@ public static final class HostedImageHeapList<E> extends AbstractList<E> {
7681
private final Comparator<E> comparator;
7782
private final List<E> hostedList;
7883
public final RuntimeImageHeapList<E> runtimeList;
84+
/**
85+
* Used to signal if this list has been modified. If true, the change should be propagated
86+
* from the hosted list to the runtime list by calling {@link #update()}. This variable
87+
* should <b>always</b> be accessed within a <code>synchronized</code> block guarded by the
88+
* object's intrinsic lock.
89+
*/
7990
private boolean modified;
8091

8192
@SuppressWarnings("unchecked")
@@ -115,9 +126,8 @@ public synchronized void add(int index, E element) {
115126
}
116127

117128
@Override
118-
public synchronized E remove(int index) {
119-
modified = true;
120-
return hostedList.remove(index);
129+
public E remove(int index) {
130+
throw notSupported();
121131
}
122132

123133
@Override
@@ -138,6 +148,14 @@ public synchronized E set(int index, E element) {
138148
public synchronized int size() {
139149
return hostedList.size();
140150
}
151+
152+
private static UnsupportedOperationException notSupported() {
153+
/*
154+
* We have deliberately chosen to only support append-only behavior to limit the number
155+
* of analysis rescans needed.
156+
*/
157+
throw new UnsupportedOperationException("ImageHeapList has append-only semantics. Removing elements is forbidden.");
158+
}
141159
}
142160
}
143161

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,8 @@
2626

2727
import java.util.Map;
2828
import java.util.concurrent.ConcurrentHashMap;
29+
import java.util.concurrent.ConcurrentMap;
30+
import java.util.function.BiFunction;
2931

3032
import org.graalvm.collections.EconomicMap;
3133
import org.graalvm.collections.EconomicMapWrap;
@@ -51,6 +53,10 @@
5153
* <p>
5254
* This map implementation allows thread-safe collection of data at image build time and storing it
5355
* into a space efficient data structure at run time.
56+
* <p>
57+
* We support <b>append-only</b> semantics. Once added, the elements should never be removed.
58+
* Changing the contents of the map via {key, value, entry} iterators is <b>not supported</b>. We
59+
* have deliberately chosen this design to limit the number of analysis rescans needed.
5460
*/
5561
@Platforms(Platform.HOSTED_ONLY.class) //
5662
public final class ImageHeapMap {
@@ -107,6 +113,12 @@ public static final class HostedImageHeapMap<K, V> extends EconomicMapWrap<K, V>
107113
private final EconomicMap<Object, Object> currentLayerMap;
108114
private final EconomicMap<Object, Object> runtimeMap;
109115

116+
/**
117+
* Used to signal if the wrapped map has been modified. If true, the change should be
118+
* propagated to the {@link #currentLayerMap} by calling {@link #update()}.
119+
*/
120+
private volatile boolean modified;
121+
110122
/**
111123
* The {code key} is only used in the Layered Image context, to link the maps across each
112124
* layer. If an {@link ImageHeapMap} is in a singleton that is already layer aware, there is
@@ -116,10 +128,20 @@ public static final class HostedImageHeapMap<K, V> extends EconomicMapWrap<K, V>
116128
*/
117129
public HostedImageHeapMap(Map<K, V> hostedMap, EconomicMap<Object, Object> currentLayerMap, EconomicMap<Object, Object> runtimeMap) {
118130
super(hostedMap);
131+
assert hostedMap instanceof ConcurrentMap<K, V> : "The implementation of HostedImageHeapMap assumes the wrapped map is thread-safe: " + hostedMap;
119132
this.currentLayerMap = currentLayerMap;
120133
this.runtimeMap = runtimeMap;
121134
}
122135

136+
public boolean needsUpdate() {
137+
return modified;
138+
}
139+
140+
public void update() {
141+
modified = false;
142+
currentLayerMap.putAll(this);
143+
}
144+
123145
@Platforms(Platform.HOSTED_ONLY.class) //
124146
public EconomicMap<Object, Object> getCurrentLayerMap() {
125147
return currentLayerMap;
@@ -155,5 +177,47 @@ public static <K, V> HostedImageHeapMap<K, V> create(Equivalence strategy, Strin
155177
return new HostedImageHeapMap<>(hostedMap, currentLayerMap, currentLayerMap);
156178
}
157179
}
180+
181+
@Override
182+
public V put(K key, V value) {
183+
modified = true;
184+
return super.put(key, value);
185+
}
186+
187+
@Override
188+
public V putIfAbsent(K key, V value) {
189+
V previous = super.putIfAbsent(key, value);
190+
if (previous == null) {
191+
/*
192+
* Either there was no previous value or the previous value was null. In both cases,
193+
* the map was modified.
194+
*/
195+
modified = true;
196+
}
197+
return previous;
198+
}
199+
200+
@Override
201+
public void clear() {
202+
throw notSupported();
203+
}
204+
205+
@Override
206+
public V removeKey(K key) {
207+
throw notSupported();
208+
}
209+
210+
@Override
211+
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
212+
throw notSupported();
213+
}
214+
215+
private static UnsupportedOperationException notSupported() {
216+
/*
217+
* We have deliberately chosen to only support append-only behavior to limit the number
218+
* of analysis rescans needed.
219+
*/
220+
throw new UnsupportedOperationException("ImageHeapMap has append-only semantics. Replacing or removing elements is forbidden.");
221+
}
158222
}
159223
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/ImageHeapCollectionFeature.java

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,9 +28,6 @@
2828
import java.util.Set;
2929
import java.util.concurrent.ConcurrentHashMap;
3030

31-
import org.graalvm.collections.EconomicMap;
32-
import org.graalvm.collections.MapCursor;
33-
3431
import com.oracle.svm.core.BuildPhaseProvider;
3532
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
3633
import com.oracle.svm.core.feature.InternalFeature;
@@ -76,31 +73,46 @@ private Object replaceHostedWithRuntime(Object obj) {
7673
return obj;
7774
}
7875

76+
/**
77+
* This method makes sure that the content of all modified {@link HostedImageHeapMap}s and
78+
* {@link HostedImageHeapList}s is properly propagated to their runtime counterparts. As both
79+
* the number of these collections and their individual sizes are theoretically unbounded, we
80+
* use <i>parallel streams</i> to divide the load across all cores.
81+
* <p>
82+
* We split the process into two stages. First, the content of each modified collection is
83+
* propagated from the hosted to the runtime version. Then, the modified runtime collections are
84+
* rescanned. The split is done to prevent concurrent modifications of the hosted collections
85+
* during the execution of this method, as they may be updated indirectly during the heap
86+
* scanning.
87+
*/
7988
@Override
8089
public void duringAnalysis(DuringAnalysisAccess a) {
8190
DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a;
8291
if (ImageLayerBuildingSupport.buildingExtensionLayer()) {
8392
allMaps.addAll(LayeredHostedImageHeapMapCollector.singleton().getPreviousLayerReachableMaps());
8493
}
85-
for (var hostedImageHeapMap : allMaps) {
86-
if (needsUpdate(hostedImageHeapMap)) {
87-
update(hostedImageHeapMap);
88-
access.rescanObject(hostedImageHeapMap.getCurrentLayerMap());
89-
access.requireAnalysisIteration();
94+
Set<Object> objectsToRescan = ConcurrentHashMap.newKeySet();
95+
allMaps.parallelStream().forEach(hostedImageHeapMap -> {
96+
if (hostedImageHeapMap.needsUpdate()) {
97+
hostedImageHeapMap.update();
98+
objectsToRescan.add(hostedImageHeapMap.getCurrentLayerMap());
9099
}
91-
}
92-
for (var hostedImageHeapList : allLists) {
100+
});
101+
allLists.parallelStream().forEach(hostedImageHeapList -> {
93102
if (hostedImageHeapList.needsUpdate()) {
94103
hostedImageHeapList.update();
95-
access.rescanObject(hostedImageHeapList.runtimeList);
96-
access.requireAnalysisIteration();
104+
objectsToRescan.add(hostedImageHeapList.runtimeList);
97105
}
106+
});
107+
if (!objectsToRescan.isEmpty()) {
108+
objectsToRescan.parallelStream().forEach(access::rescanObject);
109+
access.requireAnalysisIteration();
98110
}
99111
}
100112

101113
public boolean needsUpdate() {
102114
for (var hostedImageHeapMap : allMaps) {
103-
if (needsUpdate(hostedImageHeapMap)) {
115+
if (hostedImageHeapMap.needsUpdate()) {
104116
return true;
105117
}
106118
}
@@ -115,7 +127,7 @@ public boolean needsUpdate() {
115127
@Override
116128
public void afterImageWrite(AfterImageWriteAccess access) {
117129
for (var hostedImageHeapMap : allMaps) {
118-
if (needsUpdate(hostedImageHeapMap)) {
130+
if (hostedImageHeapMap.needsUpdate()) {
119131
throw VMError.shouldNotReachHere("ImageHeapMap modified after static analysis:%n%s%n%s",
120132
hostedImageHeapMap, hostedImageHeapMap.getCurrentLayerMap());
121133
}
@@ -128,25 +140,4 @@ public void afterImageWrite(AfterImageWriteAccess access) {
128140
}
129141
}
130142
}
131-
132-
private static boolean needsUpdate(HostedImageHeapMap<?, ?> hostedMap) {
133-
EconomicMap<Object, Object> runtimeMap = hostedMap.getCurrentLayerMap();
134-
if (hostedMap.size() != runtimeMap.size()) {
135-
return true;
136-
}
137-
MapCursor<?, ?> hostedEntry = hostedMap.getEntries();
138-
while (hostedEntry.advance()) {
139-
Object hostedValue = hostedEntry.getValue();
140-
Object runtimeValue = runtimeMap.get(hostedEntry.getKey());
141-
if (hostedValue != runtimeValue) {
142-
return true;
143-
}
144-
}
145-
return false;
146-
}
147-
148-
private static void update(HostedImageHeapMap<?, ?> hostedMap) {
149-
hostedMap.getCurrentLayerMap().clear();
150-
hostedMap.getCurrentLayerMap().putAll(hostedMap);
151-
}
152143
}

0 commit comments

Comments
 (0)