Skip to content

Commit 846bff6

Browse files
committed
[GR-32382] Add AggressiveShrinkCollectionPolicy for libgraal.
PullRequest: graal/11911
2 parents 83d0fdd + e0900ca commit 846bff6

File tree

15 files changed

+228
-18
lines changed

15 files changed

+228
-18
lines changed

compiler/CHANGELOG.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This changelog summarizes newly introduced optimizations that may be relevant to other teams.
44

5+
## Version 22.3.0
6+
* (GR-32382): Added a dedicated Native Image GC policy for libgraal that will adjust the eden space aggressively to
7+
minimize RSS memory usage.
8+
59
## Version 22.2.0
610
* (GR-23737): New global value numbering optimization for fixed nodes early in the compilation pipeline.
711
Early global value numbering and loop invariant code motion is enabled per default.
@@ -11,9 +15,9 @@ Disable early loop invariant code motion with `-Dgraal.EarlyLICM=false`.
1115
* (GR-35033): Enable floating and global value numbering of division nodes early on in the compilation pipeline if
1216
it is known they will not trap.
1317
* (GR-38405): Compute all unswitchable invariant then pick the most frequent one.
14-
* (GR-38857): Deprecated and disabled the JMX `HotSpotGraalRuntime` management bean. Re-enable the `HotSpotGraalRuntime`
18+
* (GR-38857): Deprecated and disabled the JMX `HotSpotGraalRuntime` management bean. Re-enable the `HotSpotGraalRuntime`
1519
management bean with `-Dgraal.LibGraalManagementDelay=0`.
16-
20+
1721
## Version 22.1.0
1822
* (GR-36751): Removed the `DuplicateIrreducibleLoops` option. To disable irreducible loop handling, set
1923
`-Dgraal.MaxDuplicationFactor` to a value less than or equal to 1. For AOT compilations, the effort
@@ -25,7 +29,7 @@ Disable early loop invariant code motion with `-Dgraal.EarlyLICM=false`.
2529
## Version 21.2.0
2630
* (GR-29770) Loop safepoint elimination: Not only consider 32bit loops for safepoint removal but also 64bit ones
2731
that iterate in 32bit ranges.
28-
* (GR-29341) AVX-512 support: Fix EVEX encoding and feature checks for existing instructions and add AVX-512
32+
* (GR-29341) AVX-512 support: Fix EVEX encoding and feature checks for existing instructions and add AVX-512
2933
alternatives.
3034
* (GR-31162) Do not de-duplicate ValueAnchorNode. As part of this change, there is a new marker interface
3135
NodeWithIdentity to mark nodes that have identity.
@@ -38,7 +42,7 @@ NodeWithIdentity to mark nodes that have identity.
3842

3943
## Version 21.1.0
4044
* (GR-29126) Unify box optimizations in the compiler. Remove `-Dgraal.ReuseOutOfCacheBoxedValues=false`.
41-
* (GR-28523) Optimize Box nodes: Optimizes box operations by re-using boxed representations
45+
* (GR-28523) Optimize Box nodes: Optimizes box operations by re-using boxed representations
4246
if the value of the boxed primitive is outside of the cache range of the Int/Long/Short/Char caches.
4347
Box node optimization is enabled per default. Disable it with `-Dgraal.ReuseOutOfCacheBoxedValues=false`.
4448
* (GR-29373) Eliminate unneeded STORE_LOAD barriers on sequential volatile writes on x86.

compiler/src/org.graalvm.compiler.core/src/org/graalvm/compiler/core/CompilationWrapper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.graalvm.compiler.debug.PathUtilities;
4949
import org.graalvm.compiler.debug.TTY;
5050
import org.graalvm.compiler.options.OptionValues;
51+
import org.graalvm.compiler.serviceprovider.GraalServices;
5152

5253
import jdk.vm.ci.code.BailoutException;
5354

@@ -223,6 +224,11 @@ public final T run(DebugContext initialDebug) {
223224
return performCompilation(initialDebug);
224225
} catch (Throwable cause) {
225226
return onCompilationFailure(new Failure(cause, initialDebug));
227+
} finally {
228+
// Notify the runtime that most objects allocated in the current compilation are dead
229+
// and can be reclaimed. If performCompilation includes code installation, the GC pause
230+
// should not prolong the time until the compiled code can be executed.
231+
GraalServices.notifyLowMemoryPoint(true);
226232
}
227233
}
228234

compiler/src/org.graalvm.compiler.core/src/org/graalvm/compiler/core/phases/BaseTier.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424
*/
2525
package org.graalvm.compiler.core.phases;
2626

27+
import org.graalvm.compiler.nodes.StructuredGraph;
2728
import org.graalvm.compiler.nodes.loop.DefaultLoopPolicies;
2829
import org.graalvm.compiler.nodes.loop.LoopPolicies;
2930
import org.graalvm.compiler.options.OptionValues;
31+
import org.graalvm.compiler.phases.BasePhase;
3032
import org.graalvm.compiler.phases.PhaseSuite;
3133
import org.graalvm.compiler.phases.common.CanonicalizerPhase;
34+
import org.graalvm.compiler.serviceprovider.GraalServices;
3235

3336
public class BaseTier<C> extends PhaseSuite<C> {
3437

@@ -39,4 +42,14 @@ public LoopPolicies createLoopPolicies(@SuppressWarnings("unused") OptionValues
3942
public CanonicalizerPhase createCanonicalizerPhase() {
4043
return CanonicalizerPhase.create();
4144
}
45+
46+
@Override
47+
protected void run(StructuredGraph graph, C context) {
48+
for (BasePhase<? super C> phase : getPhases()) {
49+
// Notify the runtime that most objects allocated in previous HIR phase are dead and can
50+
// be reclaimed. This will lower the chance of allocation failure in the next HIR phase.
51+
GraalServices.notifyLowMemoryPoint(false);
52+
phase.apply(graph, context);
53+
}
54+
}
4255
}

compiler/src/org.graalvm.compiler.lir/src/org/graalvm/compiler/lir/phases/LIRPhaseSuite.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import org.graalvm.compiler.core.common.util.PhasePlan;
3333
import org.graalvm.compiler.lir.gen.LIRGenerationResult;
34+
import org.graalvm.compiler.serviceprovider.GraalServices;
3435

3536
import jdk.vm.ci.code.TargetDescription;
3637

@@ -97,6 +98,9 @@ public static <C> boolean findNextPhase(ListIterator<LIRPhase<C>> it, Class<? ex
9798
@Override
9899
protected final void run(TargetDescription target, LIRGenerationResult lirGenRes, C context) {
99100
for (LIRPhase<C> phase : phases) {
101+
// Notify the runtime that most objects allocated in previous LIR phase are dead and can
102+
// be reclaimed. This will lower the chance of allocation failure in the next LIR phase.
103+
GraalServices.notifyLowMemoryPoint(false);
100104
phase.apply(target, lirGenRes, context);
101105
}
102106
}

compiler/src/org.graalvm.compiler.serviceprovider/src/org/graalvm/compiler/serviceprovider/GraalServices.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,4 +563,16 @@ public static Infopoint genImplicitException(int pcOffset, int dispatchOffset, D
563563
throw new InternalError("Exception when instantiating implicit exception dispatch", e);
564564
}
565565
}
566+
567+
/**
568+
* Notifies that the compiler is at a point where memory usage is expected to be relatively low
569+
* (e.g., just before/after a compilation). The garbage collector might be able to make use of
570+
* such a hint to optimize its performance.
571+
*
572+
* @param fullGC controls whether the hinted GC should be a full GC.
573+
*/
574+
public static void notifyLowMemoryPoint(@SuppressWarnings("unused") boolean fullGC) {
575+
// Substituted by
576+
// com.oracle.svm.hotspot.libgraal.Target_org_graalvm_compiler_serviceprovider_GraalServices
577+
}
566578
}

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java

Lines changed: 12 additions & 2 deletions
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, 2022, 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,7 @@
2626

2727
import java.util.concurrent.atomic.AtomicBoolean;
2828

29+
import com.oracle.svm.core.heap.GCCause;
2930
import org.graalvm.compiler.api.replacements.Fold;
3031
import org.graalvm.compiler.nodes.PauseNode;
3132
import org.graalvm.nativeimage.Platform;
@@ -94,6 +95,11 @@ public boolean shouldCollectOnAllocation() {
9495
return edenUsed.aboveOrEqual(edenSize);
9596
}
9697

98+
@Override
99+
public boolean shouldCollectOnRequest(GCCause cause, boolean fullGC) {
100+
return cause == GCCause.JavaLangSystemGC && !SubstrateGCOptions.DisableExplicitGC.getValue();
101+
}
102+
97103
@Fold
98104
static UnsignedWord getAlignment() {
99105
return HeapParameters.getAlignedHeapChunkSize();
@@ -295,7 +301,7 @@ protected SizeParameters computeSizeParameters(SizeParameters existing) {
295301
}
296302
minHeap = UnsignedUtils.clamp(alignUp(minHeap), minAllSpaces, maxHeap);
297303

298-
UnsignedWord initialHeap = AbstractCollectionPolicy.INITIAL_HEAP_SIZE;
304+
UnsignedWord initialHeap = getInitialHeapSize();
299305
initialHeap = UnsignedUtils.clamp(alignUp(initialHeap), minHeap, maxHeap);
300306

301307
UnsignedWord initialYoung;
@@ -323,6 +329,10 @@ protected SizeParameters computeSizeParameters(SizeParameters existing) {
323329
return SizeParameters.get(existing, maxHeap, maxYoung, initialHeap, initialEden, initialSurvivor, minHeap);
324330
}
325331

332+
protected UnsignedWord getInitialHeapSize() {
333+
return AbstractCollectionPolicy.INITIAL_HEAP_SIZE;
334+
}
335+
326336
protected static final class SizeParameters {
327337
final UnsignedWord maxHeapSize;
328338
final UnsignedWord maxYoungSize;

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
* its base class {@code AdaptiveSizePolicy}. Method and variable names have been kept mostly the
4343
* same for comparability.
4444
*/
45-
final class AdaptiveCollectionPolicy extends AbstractCollectionPolicy {
45+
class AdaptiveCollectionPolicy extends AbstractCollectionPolicy {
4646

4747
/*
4848
* Constants that can be made options if desirable. These are -XX options in HotSpot, refer to
@@ -237,7 +237,7 @@ private void computeSurvivorSpaceSizeAndThreshold(boolean isSurvivorOverflow, Un
237237
}
238238
}
239239

240-
private void computeEdenSpaceSize() {
240+
protected void computeEdenSpaceSize(@SuppressWarnings("unused") boolean completeCollection, @SuppressWarnings("unused") GCCause cause) {
241241
boolean expansionReducesCost = true; // general assumption
242242
if (shouldUseEstimator(youngGenChangeForMinorThroughput, minorGcCost())) {
243243
expansionReducesCost = expansionSignificantlyReducesCost(minorCostEstimator, edenSize);
@@ -441,7 +441,7 @@ public void onCollectionEnd(boolean completeCollection, GCCause cause) { // {maj
441441
computeSurvivorSpaceSizeAndThreshold(survivorOverflow, sizes.maxSurvivorSize());
442442
}
443443
if (shouldUpdateStats(cause)) {
444-
computeEdenSpaceSize();
444+
computeEdenSpaceSize(completeCollection, cause);
445445
if (completeCollection) {
446446
computeOldGenSpaceSize(oldLive);
447447
}
@@ -536,11 +536,11 @@ private void decaySupplementalGrowth(boolean completeCollection) {
536536
}
537537
}
538538

539-
private static boolean shouldUpdateStats(GCCause cause) { // should_update_{eden,promo}_stats
539+
protected boolean shouldUpdateStats(GCCause cause) { // should_update_{eden,promo}_stats
540540
return cause == GenScavengeGCCause.OnAllocation || USE_ADAPTIVE_SIZE_POLICY_WITH_SYSTEM_GC;
541541
}
542542

543-
private static void updateCollectionEndAverages(AdaptiveWeightedAverage costAverage, AdaptivePaddedAverage pauseAverage, ReciprocalLeastSquareFit costEstimator,
543+
private void updateCollectionEndAverages(AdaptiveWeightedAverage costAverage, AdaptivePaddedAverage pauseAverage, ReciprocalLeastSquareFit costEstimator,
544544
AdaptiveWeightedAverage intervalSeconds, GCCause cause, long mutatorNanos, long pauseNanos, UnsignedWord sizeBytes) {
545545
if (shouldUpdateStats(cause)) {
546546
double cost = 0;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core.genscavenge;
26+
27+
import org.graalvm.compiler.options.Option;
28+
import org.graalvm.word.UnsignedWord;
29+
import org.graalvm.word.WordFactory;
30+
31+
import com.oracle.svm.core.heap.GCCause;
32+
import com.oracle.svm.core.option.HostedOptionKey;
33+
import com.oracle.svm.core.util.UnsignedUtils;
34+
35+
/**
36+
* A garbage collection policy that responses to GC hints and aggressively expands/shrinks the eden
37+
* space.
38+
*/
39+
class AggressiveShrinkCollectionPolicy extends AdaptiveCollectionPolicy {
40+
41+
public static final class Options {
42+
@Option(help = "Ratio of used bytes to total allocated bytes for eden space. Setting it to a smaller value " +
43+
"will trade more triggered hinted GCs for less resident set size.") //
44+
public static final HostedOptionKey<Double> UsedEdenProportionThreshold = new HostedOptionKey<>(0.75D);
45+
@Option(help = "Soft upper limit for used eden size. The hinted GC will be performed if the used eden size " +
46+
"exceeds this value.") //
47+
public static final HostedOptionKey<Integer> ExpectedEdenSize = new HostedOptionKey<>(32 * 1024 * 1024);
48+
}
49+
50+
protected static final UnsignedWord INITIAL_HEAP_SIZE = WordFactory.unsigned(64 * 1024 * 1024);
51+
protected static final UnsignedWord FULL_GC_BONUS = WordFactory.unsigned(2 * 1024 * 1024);
52+
53+
private UnsignedWord sizeBefore = WordFactory.zero();
54+
private GCCause lastGCCause = null;
55+
56+
/**
57+
* The hinted GC will be triggered only if the used bytes in eden space is greater than
58+
* {@link Options#ExpectedEdenSize}, or if the ratio of used bytes to total allocated bytes of
59+
* eden space is above {@link Options#UsedEdenProportionThreshold}. The former condition sets a
60+
* soft limit for max used eden space; the latter condition is a trade-off between more hinted
61+
* GCs and more used eden space -- for instance, in libgraal it fits multiple typical-size
62+
* compilations before actually performing a hinted GC, and the lower ratio is, the resident set
63+
* size is lower but the hinted GC is more often.
64+
*/
65+
@Override
66+
public boolean shouldCollectOnRequest(GCCause cause, boolean fullGC) {
67+
if (cause == GCCause.HintedGC) {
68+
guaranteeSizeParametersInitialized();
69+
UnsignedWord edenUsedBytes = HeapImpl.getHeapImpl().getAccounting().getEdenUsedBytes();
70+
if (fullGC) {
71+
// For full GC request, we slightly lower the threshold to increase their
72+
// probability to be performed, as they are supposed to be issued at the lowest
73+
// memory usage point.
74+
edenUsedBytes = edenUsedBytes.add(FULL_GC_BONUS);
75+
}
76+
return edenUsedBytes.aboveOrEqual(Options.ExpectedEdenSize.getValue()) ||
77+
(UnsignedUtils.toDouble(edenUsedBytes) / UnsignedUtils.toDouble(edenSize) >= Options.UsedEdenProportionThreshold.getValue());
78+
}
79+
return super.shouldCollectOnRequest(cause, fullGC);
80+
}
81+
82+
@Override
83+
protected UnsignedWord getInitialHeapSize() {
84+
return INITIAL_HEAP_SIZE;
85+
}
86+
87+
@Override
88+
public void onCollectionBegin(boolean completeCollection, long requestingNanoTime) {
89+
sizeBefore = GCImpl.getChunkBytes();
90+
super.onCollectionBegin(completeCollection, requestingNanoTime);
91+
}
92+
93+
@Override
94+
public void onCollectionEnd(boolean completeCollection, GCCause cause) {
95+
super.onCollectionEnd(completeCollection, cause);
96+
sizeBefore = WordFactory.zero();
97+
lastGCCause = cause;
98+
}
99+
100+
@Override
101+
protected boolean shouldUpdateStats(GCCause cause) {
102+
return cause == GCCause.HintedGC || super.shouldUpdateStats(cause);
103+
}
104+
105+
/**
106+
* The adjusting logic are as follows:
107+
*
108+
* 1. if we hit hinted GC twice in a row, there is no allocation failure in between. If the eden
109+
* space is previously expanded, we will aggressively shrink the eden space to half, such that
110+
* the memory footprint will be lower in subsequent execution.
111+
*
112+
* 2. if a non-hinted GC cannot reclaim half of used bytes, there is likely a session with
113+
* continuously high memory consumption (e.g, a huge libgraal compilation). In such case, we
114+
* will double the eden space to avoid frequent GCs.
115+
*/
116+
@Override
117+
protected void computeEdenSpaceSize(boolean completeCollection, GCCause cause) {
118+
if (cause == GCCause.HintedGC) {
119+
if (completeCollection && lastGCCause == GCCause.HintedGC) {
120+
UnsignedWord newEdenSize = UnsignedUtils.max(sizes.initialEdenSize, alignUp(edenSize.unsignedDivide(2)));
121+
if (edenSize.aboveThan(newEdenSize)) {
122+
edenSize = newEdenSize;
123+
}
124+
}
125+
} else {
126+
UnsignedWord sizeAfter = GCImpl.getChunkBytes();
127+
if (sizeBefore.notEqual(0) && sizeBefore.belowThan(sizeAfter.multiply(2))) {
128+
edenSize = alignUp(edenSize.multiply(2));
129+
} else {
130+
super.computeEdenSpaceSize(completeCollection, cause);
131+
}
132+
}
133+
}
134+
}

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public boolean shouldCollectOnAllocation() {
6161
return youngUsed.aboveOrEqual(getMaximumYoungGenerationSize());
6262
}
6363

64+
@Override
65+
public boolean shouldCollectOnRequest(GCCause cause, boolean fullGC) {
66+
return cause == GCCause.JavaLangSystemGC && !SubstrateGCOptions.DisableExplicitGC.getValue();
67+
}
68+
6469
@Override
6570
public UnsignedWord getCurrentHeapCapacity() {
6671
return getMaximumHeapSize();

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2014, 2022, 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
@@ -56,6 +56,8 @@ static CollectionPolicy getInitialPolicy() {
5656
switch (name) {
5757
case "Adaptive":
5858
return new AdaptiveCollectionPolicy();
59+
case "AggressiveShrink":
60+
return new AggressiveShrinkCollectionPolicy();
5961
case "Proportionate":
6062
return new ProportionateSpacesPolicy();
6163
case "BySpaceAndTime":
@@ -108,6 +110,13 @@ static boolean shouldCollectYoungGenSeparately(boolean defaultValue) {
108110
*/
109111
boolean shouldCollectOnAllocation();
110112

113+
/**
114+
* Return true if a user-requested GC (e.g., call to {@link System#gc()} or
115+
* {@link org.graalvm.compiler.serviceprovider.GraalServices#notifyLowMemoryPoint(boolean)})
116+
* should be performed.
117+
*/
118+
boolean shouldCollectOnRequest(GCCause cause, boolean fullGC);
119+
111120
/**
112121
* At a safepoint, decides whether to do a complete collection (returning {@code true}) or an
113122
* incremental collection (returning {@code false}).
@@ -167,4 +176,5 @@ static boolean shouldCollectYoungGenSeparately(boolean defaultValue) {
167176

168177
/** Called before the end of a collection, in the safepoint operation. */
169178
void onCollectionEnd(boolean completeCollection, GCCause cause);
179+
170180
}

0 commit comments

Comments
 (0)