Skip to content

Commit 2d8672b

Browse files
committed
[GR-71299] Improve traversing compilation queue responsiveness.
PullRequest: graal/22602
2 parents d1a39d3 + 6267c0a commit 2d8672b

File tree

13 files changed

+110
-32
lines changed

13 files changed

+110
-32
lines changed

compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/truffle/test/DynamicCompilationThresholdsTest.java

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.io.IOException;
2828
import java.util.LinkedList;
2929
import java.util.Map;
30+
import java.util.concurrent.ArrayBlockingQueue;
31+
import java.util.concurrent.BlockingQueue;
3032
import java.util.concurrent.ConcurrentHashMap;
3133
import java.util.concurrent.CountDownLatch;
3234
import java.util.concurrent.TimeUnit;
@@ -59,7 +61,6 @@ static class SourceCompilation {
5961
private final int id;
6062
private final Source source;
6163
private final CountDownLatch compilationDoneLatch = new CountDownLatch(1);
62-
private final CountDownLatch compilationStartedLatch = new CountDownLatch(1);
6364
private final CountDownLatch compilationGoLatch = new CountDownLatch(1);
6465

6566
SourceCompilation(int id, Source source) {
@@ -75,6 +76,7 @@ public void testDynamicCompilationThreshods() throws IOException, InterruptedExc
7576
Assume.assumeTrue(Truffle.getRuntime() instanceof OptimizedTruffleRuntime);
7677
Runnable test = () -> {
7778
OptimizedTruffleRuntime optimizedTruffleRuntime = (OptimizedTruffleRuntime) Truffle.getRuntime();
79+
BlockingQueue<SourceCompilation> startedCompilationsQueue = new ArrayBlockingQueue<>(30);
7880
OptimizedTruffleRuntimeListener listener = new OptimizedTruffleRuntimeListener() {
7981
@Override
8082
public void onCompilationSuccess(OptimizedCallTarget target, AbstractCompilationTask task, TruffleCompilerListener.GraphInfo graph,
@@ -97,7 +99,7 @@ private SourceCompilation getSourceCompilation(OptimizedCallTarget target) {
9799
@Override
98100
public void onCompilationStarted(OptimizedCallTarget target, AbstractCompilationTask task) {
99101
if (getSourceCompilation(target) instanceof SourceCompilation compilation) {
100-
compilation.compilationStartedLatch.countDown();
102+
startedCompilationsQueue.add(compilation);
101103
try {
102104
compilation.compilationGoLatch.await();
103105
} catch (InterruptedException ie) {
@@ -117,8 +119,9 @@ public void onCompilationStarted(OptimizedCallTarget target, AbstractCompilation
117119
.option("engine.DynamicCompilationThresholdsMinNormalLoad", "10") //
118120
.option("engine.DynamicCompilationThresholdsMinScale", "0.1") //
119121
.option("engine.CompilerThreads", "1").build()) {
120-
int firstCompilation = submitCompilation(context);
121-
waitForCompilationStart(firstCompilation);
122+
int firstCompilationId = submitCompilation(context);
123+
SourceCompilation firstCompilation = startedCompilationsQueue.take();
124+
Assert.assertEquals(firstCompilationId, firstCompilation.id);
122125
LinkedList<Integer> scales = new LinkedList<>();
123126
int firstScale = FixedPointMath.toFixedPoint(0.1);
124127
Assert.assertEquals(firstScale, optimizedTruffleRuntime.compilationThresholdScale());
@@ -141,14 +144,14 @@ public void onCompilationStarted(OptimizedCallTarget target, AbstractCompilation
141144
Assert.assertEquals(scale, optimizedTruffleRuntime.compilationThresholdScale());
142145
scales.push(scale);
143146
}
144-
allowCompilationToProceed(firstCompilation);
145-
waitForCompilationDone(firstCompilation);
147+
allowCompilationToProceed(firstCompilationId);
148+
waitForCompilationDone(firstCompilationId);
146149
scales.pop();
147-
for (int i = firstCompilation + 1; i <= 30; i++) {
148-
waitForCompilationStart(i);
150+
for (int i = firstCompilationId + 1; i <= 30; i++) {
151+
SourceCompilation compilation = startedCompilationsQueue.take();
149152
Assert.assertEquals((int) scales.pop(), optimizedTruffleRuntime.compilationThresholdScale());
150-
allowCompilationToProceed(i);
151-
waitForCompilationDone(i);
153+
allowCompilationToProceed(compilation.id);
154+
waitForCompilationDone(compilation.id);
152155
}
153156
Assert.assertTrue(scales.isEmpty());
154157
} catch (IOException | InterruptedException e) {
@@ -164,12 +167,6 @@ private void allowCompilationToProceed(int id) throws InterruptedException {
164167
compilationMap.get(id).compilationGoLatch.countDown();
165168
}
166169

167-
private void waitForCompilationStart(int id) throws InterruptedException {
168-
if (!compilationMap.get(id).compilationStartedLatch.await(5, TimeUnit.MINUTES)) {
169-
throw new AssertionError("Compilation of source " + id + " did not start in time");
170-
}
171-
}
172-
173170
private void waitForCompilationDone(int id) throws InterruptedException {
174171
if (!compilationMap.get(id).compilationDoneLatch.await(5, TimeUnit.MINUTES)) {
175172
throw new AssertionError("Compilation of source " + id + " did not finish in time");

substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
import com.oracle.svm.core.stack.JavaStackWalker;
127127
import com.oracle.svm.core.util.UserError;
128128
import com.oracle.svm.core.util.VMError;
129+
import com.oracle.svm.graal.TruffleRuntimeCompilationSupport;
129130
import com.oracle.svm.graal.hosted.runtimecompilation.CallTreeInfo;
130131
import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationCandidate;
131132
import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature;
@@ -136,13 +137,12 @@
136137
import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl;
137138
import com.oracle.svm.hosted.meta.HostedType;
138139
import com.oracle.svm.hosted.substitute.DeletedElementException;
139-
import com.oracle.svm.graal.TruffleRuntimeCompilationSupport;
140+
import com.oracle.svm.truffle.api.SubstrateKnownTruffleTypes;
140141
import com.oracle.svm.truffle.api.SubstrateThreadLocalHandshake;
141142
import com.oracle.svm.truffle.api.SubstrateThreadLocalHandshakeSnippets;
142143
import com.oracle.svm.truffle.api.SubstrateTruffleCompiler;
143144
import com.oracle.svm.truffle.api.SubstrateTruffleRuntime;
144145
import com.oracle.svm.truffle.api.SubstrateTruffleUniverseFactory;
145-
import com.oracle.svm.truffle.api.SubstrateKnownTruffleTypes;
146146
import com.oracle.svm.util.AnnotationUtil;
147147
import com.oracle.svm.util.LogUtils;
148148
import com.oracle.svm.util.ReflectionUtil;
@@ -1118,6 +1118,19 @@ final class Target_com_oracle_truffle_runtime_OptimizedCallTarget {
11181118
@Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) //
11191119
long initializedTimestamp;
11201120

1121+
/*
1122+
* The call count must start from zero at runtime, compilation prioritization depends on it.
1123+
*/
1124+
@Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) //
1125+
int callCount;
1126+
1127+
/*
1128+
* The call and loop count must start from zero at runtime, compilation prioritization depends
1129+
* on it.
1130+
*/
1131+
@Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) //
1132+
int callAndLoopCount;
1133+
11211134
private static final class TransformToTrue implements FieldValueTransformer {
11221135
@Override
11231136
public Object transform(Object receiver, Object originalValue) {

truffle/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ This changelog summarizes major changes between Truffle versions relevant to lan
3737

3838
* GR-70086: Added `replacementOf` and `replacementMethod` attributes to `GenerateLibrary.Abstract` annotation. They enable automatic generation of legacy delegators during message library evolution, while allowing custom conversions when needed.
3939
* GR-70086 Deprecated `Message.resolve(Class<?>, String)`. Use `Message.resolveExact(Class<?>, String, Class<?>...)` with argument types instead. This deprecation was necessary as library messages are no longer unique by message name, if the previous message was deprecated.
40+
* GR-71299 Improved the responsiveness of the Truffle compilation queue by refining the computation of the execution rate of compilation units in the queue.
41+
* Added `engine.TraversingQueueRateHalfLife` to allow fine-tuning of the compilation queue responsiveness.
4042

4143
* GR-69861: Bytecode DSL: Added a `BytecodeFrame` abstraction for capturing frame state and accessing frame data. This abstraction should be preferred over `BytecodeNode` access methods because it captures the correct interpreter location data.
4244
* GR-69861: Bytecode DSL: Added a `captureFramesForTrace` parameter to `@GenerateBytecode` that enables capturing of frames in `TruffleStackTraceElement`s. Previously, frame data was unreliably available in stack traces; now, it is guaranteed to be available if requested. Languages must use the `BytecodeFrame` abstraction to access frame data from `TruffleStackTraceElement`s rather than access the frame directly.

truffle/docs/Options.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ These are internal options for debugging language implementations and tools.
179179
- `--engine.TraversingQueueFirstTierPriority` : Traversing queue gives first tier compilations priority.
180180
- `--engine.TraversingQueueInvalidatedBonus=[0.0, inf)` : Controls how much of a priority should be given to compilations after invalidations (default: 1.0, no bonus).
181181
- `--engine.TraversingQueueOSRBonus=[0.0, inf)` : Controls how much of a priority should be given to OSR compilations (default: 1.0, no bonus).
182+
- `--engine.TraversingQueueRateHalfLife=[0, inf)` : Sets the time, in milliseconds, after which the impact of a compilation unit's observed execution rate is halved. (default: 300 ms)
182183
- `--engine.TraversingQueueWeightingBothTiers=true|false` : Traversing queue uses rate as priority for both tier. (default: true)
183184
- `--compiler.CompilationTimeout` : Time limit in seconds before a compilation expires and throws a bailout (0 to disable the limit).
184185
- `--compiler.DeoptCycleDetectionAllowedRepeats` : Maximum allowed repeats of the same compiled code for the same compilable. Works only if the detection of repeated compilation is enabled after DeoptCycleDetectionThreshold has been reached for the compilable. (negative integer means 0, default: 0)

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,8 @@ public AbstractFastThreadLocal getContextThreadLocal() {
13341334
public abstract boolean isLegacyCompilerOption(String key);
13351335

13361336
public abstract <T> ThreadLocal<T> createTerminatingThreadLocal(Supplier<T> initialValue, Consumer<T> onThreadTermination);
1337+
1338+
public abstract void setInitializedTimestamp(CallTarget target, long timestamp);
13371339
}
13381340

13391341
public abstract static class LanguageProviderSupport extends Support {

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/DefaultRuntimeAccessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ public AbstractFastThreadLocal getContextThreadLocal() {
304304
public <T> ThreadLocal<T> createTerminatingThreadLocal(Supplier<T> initialValue, Consumer<T> onThreadTermination) {
305305
return ThreadLocal.withInitial(initialValue);
306306
}
307+
308+
@Override
309+
public void setInitializedTimestamp(CallTarget target, long timestamp) {
310+
311+
}
307312
}
308313

309314
}

truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,12 @@ boolean patch(SandboxPolicy newSandboxPolicy,
774774
}
775775
validateSandbox();
776776
printDeprecatedOptionsWarning(deprecatedDescriptors);
777+
778+
long currentTimestamp = System.nanoTime();
779+
for (CallTarget loadedCallTarget : getCallTargets()) {
780+
RUNTIME.setInitializedTimestamp(loadedCallTarget, currentTimestamp);
781+
}
782+
777783
return true;
778784
}
779785

truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/CompilationTask.java

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,31 @@ private CompilationTask(BackgroundCompileQueue.Priority priority, WeakReference<
102102
// cannot compute a reliable rate yet. The rate will be computed the first time updateWeight
103103
// is called.
104104
lastWeight = -1.0;
105-
lastTime = System.nanoTime();
105+
long timestamp = System.nanoTime();
106106
OptimizedCallTarget target = targetRef.get();
107107
if (target == null) {
108+
lastTime = timestamp;
108109
lastCount = Integer.MIN_VALUE;
109110
engineData = null;
110111
isOSR = false;
111112
cancelledPredicate = null;
112113
} else {
113-
lastCount = target.getCallAndLoopCount();
114114
engineData = target.engine;
115115
isOSR = target.isOSR();
116116
cancelledPredicate = target.engine.cancelledPredicate;
117+
lastCount = 0;
118+
lastTime = target.getInitializedTimestamp();
119+
if (target.isInitialized()) {
120+
assert lastTime != 0;
121+
updateWeight(timestamp);
122+
} else {
123+
/*
124+
* Call targets used for the initialization of the compiler are not initialized
125+
* before they are sent to the compilation queue.
126+
*/
127+
lastCount = target.getCallAndLoopCount();
128+
lastTime = timestamp;
129+
}
117130
}
118131
}
119132

@@ -284,11 +297,28 @@ boolean updateWeight(long currentTime) {
284297
// A last weight > 0 indicates that it has been initialized. If so, only update its weight
285298
// if 1_000_000ns have elapsed since its last calculation. The elapsed time may be negative
286299
// since tasks may be added to the queue while traversing it.
287-
if (lastWeight > 0 && elapsed < 1_000_000 || elapsed < 0) {
300+
if (lastWeight >= 0 && elapsed < 1_000_000 || elapsed < 0) {
288301
return true;
289302
}
290303
int count = target.getCallAndLoopCount();
291-
lastRate = rate(count, elapsed);
304+
double currentRate = rate(count, elapsed);
305+
if (lastWeight < 0) {
306+
lastRate = currentRate;
307+
} else {
308+
/*
309+
* "Assembles" the new rate from the last rate and the current rate by applying a decay
310+
* from the interval (0, 1) to the last rate. The decay is computed using the configured
311+
* rate half-life. For example, if the elapsed time is equal to the half-life, then <new
312+
* rate> = <last rate> * 0.5 + <current rate> * 0.5; if the elapsed time is twice the
313+
* half-life, then <new rate> = <last rate> * 0.25 + <current rate> * 0.75. The reason
314+
* we do this is that the current interval, which has an unpredictable size, might not
315+
* capture the current hotness of the compilation unit very well. Using the decay makes
316+
* the computed rate capture a longer time interval while still giving priority to more
317+
* recent executions.
318+
*/
319+
double decay = Math.pow(2.0d, -1.0d * elapsed / engineData.traversingRateHalfLifeNs);
320+
lastRate = lastRate * decay + currentRate * (1 - decay);
321+
}
292322
lastTime = currentTime;
293323
lastCount = count;
294324
double weight = (1 + lastRate) * lastCount;
@@ -351,7 +381,8 @@ private static boolean hasEffect(double bonus) {
351381

352382
private double rate(int count, long elapsed) {
353383
// Divide by a minimum of 1000 to prevent division by zero/very small numbers.
354-
return lastRate = ((double) count - lastCount) / Math.max(elapsed, 1000);
384+
// Multiply by 1_000_000 to get per-millisecond rate.
385+
return ((double) count - lastCount) / Math.max(elapsed, 1000) * 1_000_000;
355386
}
356387

357388
public int targetHighestCompiledTier() {

truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/EngineData.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraversingQueueFirstTierPriority;
8181
import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraversingQueueInvalidatedBonus;
8282
import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraversingQueueOSRBonus;
83+
import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraversingQueueRateHalfLife;
8384
import static com.oracle.truffle.runtime.OptimizedRuntimeOptions.TraversingQueueWeightingBothTiers;
8485
import static com.oracle.truffle.runtime.OptimizedTruffleRuntime.getRuntime;
8586

@@ -164,6 +165,7 @@ public final class EngineData {
164165
@CompilationFinal public double traversingFirstTierBonus;
165166
@CompilationFinal public double traversingInvalidatedBonus;
166167
@CompilationFinal public double traversingOSRBonus;
168+
@CompilationFinal public long traversingRateHalfLifeNs;
167169
@CompilationFinal public boolean propagateCallAndLoopCount;
168170
@CompilationFinal public int propagateCallAndLoopCountMaxDepth;
169171
@CompilationFinal public int maximumCompilations;
@@ -346,6 +348,7 @@ private void loadOptions(OptionValues options, SandboxPolicy sandboxPolicy) {
346348
maximumCompilations = options.get(MaximumCompilations);
347349
traversingInvalidatedBonus = options.get(TraversingQueueInvalidatedBonus);
348350
traversingOSRBonus = options.get(TraversingQueueOSRBonus);
351+
traversingRateHalfLifeNs = options.get(TraversingQueueRateHalfLife) * 1_000_000;
349352

350353
this.returnTypeSpeculation = options.get(ReturnTypeSpeculation);
351354
this.argumentTypeSpeculation = options.get(ArgumentTypeSpeculation);

truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/OptimizedCallTarget.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,9 @@ private void propagateCallAndLoopCount() {
807807
}
808808
if (callerCallTarget.frameDescriptorEquals(parentFrameDescriptor)) {
809809
callerCallNode.forceInlining();
810-
callerCallTarget.callAndLoopCount += this.callAndLoopCount;
810+
int oldLoopCallCount = callerCallTarget.callAndLoopCount;
811+
int newLoopCallCount = oldLoopCallCount + this.callAndLoopCount;
812+
callerCallTarget.callAndLoopCount = newLoopCallCount >= oldLoopCallCount ? newLoopCallCount : Integer.MAX_VALUE;
811813
return;
812814
}
813815
currentSingleCallNode = callerCallTarget.singleCallNode;
@@ -882,11 +884,7 @@ private synchronized void initialize(boolean validate) {
882884
assert !validate || OptimizedRuntimeAccessor.NODES.getCallTargetWithoutInitialization(rootNode) == this : "Call target out of sync.";
883885

884886
OptimizedRuntimeAccessor.INSTRUMENT.onFirstExecution(getRootNode(), validate);
885-
if (engine.callTargetStatistics) {
886-
this.initializedTimestamp = System.nanoTime();
887-
} else {
888-
this.initializedTimestamp = 0L;
889-
}
887+
this.initializedTimestamp = System.nanoTime();
890888
initialized = true;
891889
}
892890
}
@@ -1364,6 +1362,12 @@ public final long getInitializedTimestamp() {
13641362
return initializedTimestamp;
13651363
}
13661364

1365+
final void setInitializedTimestamp(long timestamp) {
1366+
if (initialized) {
1367+
initializedTimestamp = timestamp;
1368+
}
1369+
}
1370+
13671371
public final Map<String, Object> getDebugProperties() {
13681372
Map<String, Object> properties = new LinkedHashMap<>();
13691373
OptimizedTruffleRuntimeListener.addASTSizeProperty(this, properties);

0 commit comments

Comments
 (0)