Skip to content

Commit 06654e0

Browse files
committed
[GR-69981] Remove DynamicThresholdsQueue and instead execute the compilation thresholds scaling when submitting a compilation task/before execution of a compilation task.
PullRequest: graal/22211
2 parents 1427c2c + 7209a6f commit 06654e0

File tree

3 files changed

+262
-142
lines changed

3 files changed

+262
-142
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Copyright (c) 2025, 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 jdk.graal.compiler.truffle.test;
26+
27+
import java.io.IOException;
28+
import java.util.LinkedList;
29+
import java.util.Map;
30+
import java.util.concurrent.ConcurrentHashMap;
31+
import java.util.concurrent.CountDownLatch;
32+
import java.util.concurrent.TimeUnit;
33+
import java.util.concurrent.atomic.AtomicInteger;
34+
import java.util.regex.Matcher;
35+
import java.util.regex.Pattern;
36+
37+
import org.graalvm.polyglot.Context;
38+
import org.graalvm.polyglot.Source;
39+
import org.junit.Assert;
40+
import org.junit.Assume;
41+
import org.junit.Test;
42+
43+
import com.oracle.truffle.api.Truffle;
44+
import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage;
45+
import com.oracle.truffle.api.source.SourceSection;
46+
import com.oracle.truffle.api.test.SubprocessTestUtils;
47+
import com.oracle.truffle.compiler.TruffleCompilerListener;
48+
import com.oracle.truffle.runtime.AbstractCompilationTask;
49+
import com.oracle.truffle.runtime.FixedPointMath;
50+
import com.oracle.truffle.runtime.OptimizedCallTarget;
51+
import com.oracle.truffle.runtime.OptimizedTruffleRuntime;
52+
import com.oracle.truffle.runtime.OptimizedTruffleRuntimeListener;
53+
54+
public class DynamicCompilationThresholdsTest {
55+
private static final Pattern TARGET_NAME_PATTERN = Pattern.compile("DynamicCompilationThresholdsTest(\\d+)");
56+
static AtomicInteger ID = new AtomicInteger();
57+
58+
static class SourceCompilation {
59+
private final int id;
60+
private final Source source;
61+
private final CountDownLatch compilationDoneLatch = new CountDownLatch(1);
62+
private final CountDownLatch compilationStartedLatch = new CountDownLatch(1);
63+
private final CountDownLatch compilationGoLatch = new CountDownLatch(1);
64+
65+
SourceCompilation(int id, Source source) {
66+
this.id = id;
67+
this.source = source;
68+
}
69+
}
70+
71+
Map<Integer, SourceCompilation> compilationMap = new ConcurrentHashMap<>();
72+
73+
@Test
74+
public void testDynamicCompilationThreshods() throws IOException, InterruptedException {
75+
Assume.assumeTrue(Truffle.getRuntime() instanceof OptimizedTruffleRuntime);
76+
Runnable test = () -> {
77+
OptimizedTruffleRuntime optimizedTruffleRuntime = (OptimizedTruffleRuntime) Truffle.getRuntime();
78+
OptimizedTruffleRuntimeListener listener = new OptimizedTruffleRuntimeListener() {
79+
@Override
80+
public void onCompilationSuccess(OptimizedCallTarget target, AbstractCompilationTask task, TruffleCompilerListener.GraphInfo graph,
81+
TruffleCompilerListener.CompilationResultInfo result) {
82+
if (getSourceCompilation(target) instanceof SourceCompilation compilation) {
83+
compilation.compilationDoneLatch.countDown();
84+
}
85+
}
86+
87+
private SourceCompilation getSourceCompilation(OptimizedCallTarget target) {
88+
if (target.getRootNode().getSourceSection() instanceof SourceSection section && section.getSource() != null) {
89+
Matcher matcher = TARGET_NAME_PATTERN.matcher(section.getSource().getName());
90+
if (matcher.find()) {
91+
return compilationMap.get(Integer.parseInt(matcher.group(1)));
92+
}
93+
}
94+
return null;
95+
}
96+
97+
@Override
98+
public void onCompilationStarted(OptimizedCallTarget target, AbstractCompilationTask task) {
99+
if (getSourceCompilation(target) instanceof SourceCompilation compilation) {
100+
compilation.compilationStartedLatch.countDown();
101+
try {
102+
compilation.compilationGoLatch.await();
103+
} catch (InterruptedException ie) {
104+
throw new AssertionError(ie);
105+
}
106+
}
107+
}
108+
};
109+
optimizedTruffleRuntime.addListener(listener);
110+
try (Context context = Context.newBuilder().allowExperimentalOptions(true) //
111+
.option("engine.BackgroundCompilation", "true") //
112+
.option("engine.CompileImmediately", "false") //
113+
.option("engine.SingleTierCompilationThreshold", "1") //
114+
.option("engine.MultiTier", "false") //
115+
.option("engine.DynamicCompilationThresholds", "true") //
116+
.option("engine.DynamicCompilationThresholdsMaxNormalLoad", "20") //
117+
.option("engine.DynamicCompilationThresholdsMinNormalLoad", "10") //
118+
.option("engine.DynamicCompilationThresholdsMinScale", "0.1") //
119+
.option("engine.CompilerThreads", "1").build()) {
120+
int firstCompilation = submitCompilation(context);
121+
waitForCompilationStart(firstCompilation);
122+
LinkedList<Integer> scales = new LinkedList<>();
123+
int firstScale = FixedPointMath.toFixedPoint(0.1);
124+
Assert.assertEquals(firstScale, optimizedTruffleRuntime.compilationThresholdScale());
125+
scales.push(firstScale);
126+
for (int i = 1; i <= 10; i++) {
127+
submitCompilation(context);
128+
int scale = FixedPointMath.toFixedPoint(0.1 + 0.9 * i / 10);
129+
Assert.assertEquals(scale, optimizedTruffleRuntime.compilationThresholdScale());
130+
scales.push(scale);
131+
}
132+
for (int i = 1; i <= 10; i++) {
133+
submitCompilation(context);
134+
int scale = FixedPointMath.toFixedPoint(1.0);
135+
Assert.assertEquals(scale, optimizedTruffleRuntime.compilationThresholdScale());
136+
scales.push(scale);
137+
}
138+
for (int i = 1; i <= 10; i++) {
139+
submitCompilation(context);
140+
int scale = FixedPointMath.toFixedPoint(1.0 + 0.9 * i / 10);
141+
Assert.assertEquals(scale, optimizedTruffleRuntime.compilationThresholdScale());
142+
scales.push(scale);
143+
}
144+
allowCompilationToProceed(firstCompilation);
145+
waitForCompilationDone(firstCompilation);
146+
scales.pop();
147+
for (int i = firstCompilation + 1; i <= 30; i++) {
148+
waitForCompilationStart(i);
149+
Assert.assertEquals((int) scales.pop(), optimizedTruffleRuntime.compilationThresholdScale());
150+
allowCompilationToProceed(i);
151+
waitForCompilationDone(i);
152+
}
153+
Assert.assertTrue(scales.isEmpty());
154+
} catch (IOException | InterruptedException e) {
155+
throw new AssertionError(e);
156+
} finally {
157+
optimizedTruffleRuntime.removeListener(listener);
158+
}
159+
};
160+
SubprocessTestUtils.newBuilder(DynamicCompilationThresholdsTest.class, test).run();
161+
}
162+
163+
private void allowCompilationToProceed(int id) throws InterruptedException {
164+
compilationMap.get(id).compilationGoLatch.countDown();
165+
}
166+
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+
173+
private void waitForCompilationDone(int id) throws InterruptedException {
174+
if (!compilationMap.get(id).compilationDoneLatch.await(5, TimeUnit.MINUTES)) {
175+
throw new AssertionError("Compilation of source " + id + " did not finish in time");
176+
}
177+
}
178+
179+
int submitCompilation(Context context) throws IOException {
180+
int id = ID.getAndIncrement();
181+
Source source = Source.newBuilder(InstrumentationTestLanguage.ID, "CONSTANT(" + id + ")", "DynamicCompilationThresholdsTest" + id).build();
182+
SourceCompilation compilation = new SourceCompilation(id, source);
183+
compilationMap.put(id, compilation);
184+
context.eval(source);
185+
return id;
186+
}
187+
}

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

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.util.concurrent.Callable;
5252
import java.util.concurrent.ConcurrentHashMap;
5353
import java.util.concurrent.ExecutorService;
54+
import java.util.concurrent.Future;
5455
import java.util.concurrent.LinkedBlockingDeque;
5556
import java.util.concurrent.PriorityBlockingQueue;
5657
import java.util.concurrent.RejectedExecutionException;
@@ -149,10 +150,25 @@ private ExecutorService getExecutorService(OptimizedCallTarget callTarget) {
149150
long compilerIdleDelay = runtime.getCompilerIdleDelay(callTarget);
150151
long keepAliveTime = compilerIdleDelay >= 0 ? compilerIdleDelay : 0;
151152

152-
BlockingQueue<Runnable> queue = createQueue(callTarget, threads);
153+
BlockingQueue<Runnable> queue;
154+
if (callTarget.getOptionValue(OptimizedRuntimeOptions.TraversingCompilationQueue)) {
155+
queue = new TraversingBlockingQueue(new IdlingLinkedBlockingDeque<>());
156+
} else {
157+
queue = new IdlingPriorityBlockingQueue<>();
158+
}
159+
160+
DynamicCompilationThresholds dynamicCompilationThresholds = null;
161+
if (callTarget.getOptionValue(OptimizedRuntimeOptions.TraversingCompilationQueue)) {
162+
if (callTarget.getOptionValue(OptimizedRuntimeOptions.DynamicCompilationThresholds) && callTarget.getOptionValue(OptimizedRuntimeOptions.BackgroundCompilation)) {
163+
double minScale = callTarget.getOptionValue(OptimizedRuntimeOptions.DynamicCompilationThresholdsMinScale);
164+
int minNormalLoad = callTarget.getOptionValue(OptimizedRuntimeOptions.DynamicCompilationThresholdsMinNormalLoad);
165+
int maxNormalLoad = callTarget.getOptionValue(OptimizedRuntimeOptions.DynamicCompilationThresholdsMaxNormalLoad);
166+
dynamicCompilationThresholds = new DynamicCompilationThresholds(threads, minScale, minNormalLoad, maxNormalLoad);
167+
}
168+
}
153169
TruffleThreadPoolExecutor threadPoolExecutor = new TruffleThreadPoolExecutor(threads, threads,
154170
keepAliveTime, TimeUnit.MILLISECONDS,
155-
queue, factory);
171+
queue, factory, dynamicCompilationThresholds);
156172

157173
if (compilerIdleDelay > 0) {
158174
// There are two mechanisms to signal idleness: if core threads can timeout, then
@@ -165,21 +181,6 @@ private ExecutorService getExecutorService(OptimizedCallTarget callTarget) {
165181
}
166182
}
167183

168-
private BlockingQueue<Runnable> createQueue(OptimizedCallTarget callTarget, int threads) {
169-
if (callTarget.getOptionValue(OptimizedRuntimeOptions.TraversingCompilationQueue)) {
170-
if (callTarget.getOptionValue(OptimizedRuntimeOptions.DynamicCompilationThresholds) && callTarget.getOptionValue(OptimizedRuntimeOptions.BackgroundCompilation)) {
171-
double minScale = callTarget.getOptionValue(OptimizedRuntimeOptions.DynamicCompilationThresholdsMinScale);
172-
int minNormalLoad = callTarget.getOptionValue(OptimizedRuntimeOptions.DynamicCompilationThresholdsMinNormalLoad);
173-
int maxNormalLoad = callTarget.getOptionValue(OptimizedRuntimeOptions.DynamicCompilationThresholdsMaxNormalLoad);
174-
return new DynamicThresholdsQueue(threads, minScale, minNormalLoad, maxNormalLoad, new IdlingLinkedBlockingDeque<>());
175-
} else {
176-
return new TraversingBlockingQueue(new IdlingLinkedBlockingDeque<>());
177-
}
178-
} else {
179-
return new IdlingPriorityBlockingQueue<>();
180-
}
181-
}
182-
183184
@SuppressWarnings("unused")
184185
protected ThreadFactory newThreadFactory(String threadNamePrefix, OptimizedCallTarget callTarget) {
185186
return new TruffleCompilerThreadFactory(threadNamePrefix, runtime);
@@ -326,9 +327,12 @@ private static final class TruffleThreadPoolExecutor extends ThreadPoolExecutor
326327
* queued and active compilations without duplicates and misses.
327328
*/
328329
private final Set<CompilationTask.ExecutorServiceWrapper> allTargets = ConcurrentHashMap.newKeySet();
330+
private final DynamicCompilationThresholds dynamicCompilationThresholds;
329331

330-
private TruffleThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
332+
private TruffleThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
333+
DynamicCompilationThresholds dynamicCompilationThresholds) {
331334
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
335+
this.dynamicCompilationThresholds = dynamicCompilationThresholds;
332336
}
333337

334338
void flush(EngineData engine) {
@@ -370,6 +374,24 @@ public boolean remove(Runnable task) {
370374
return super.remove(task);
371375
}
372376

377+
@Override
378+
public <T> Future<T> submit(Callable<T> task) {
379+
Future<T> future = super.submit(task);
380+
scaleThresholds();
381+
return future;
382+
}
383+
384+
private void scaleThresholds() {
385+
if (dynamicCompilationThresholds != null) {
386+
dynamicCompilationThresholds.scaleThresholds();
387+
}
388+
}
389+
390+
@Override
391+
protected void beforeExecute(Thread t, Runnable r) {
392+
scaleThresholds();
393+
}
394+
373395
@Override
374396
protected void afterExecute(Runnable r, Throwable t) {
375397
super.afterExecute(r, t);
@@ -531,4 +553,39 @@ public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException {
531553
return super.pollFirst(timeoutMillis, TimeUnit.MILLISECONDS);
532554
}
533555
}
556+
557+
private final class DynamicCompilationThresholds {
558+
private final int threads;
559+
private final double minScale;
560+
private final int minNormalLoad;
561+
private final int maxNormalLoad;
562+
private final double slope;
563+
564+
DynamicCompilationThresholds(int threads, double minScale, int minNormalLoad, int maxNormalLoad) {
565+
this.threads = threads;
566+
this.minScale = minScale;
567+
this.minNormalLoad = minNormalLoad;
568+
this.maxNormalLoad = maxNormalLoad;
569+
this.slope = (1 - minScale) / minNormalLoad;
570+
}
571+
572+
private double load() {
573+
return (double) getQueueSize() / threads;
574+
}
575+
576+
private double scale() {
577+
double x = load();
578+
if (minNormalLoad <= x && x <= maxNormalLoad) {
579+
return 1;
580+
}
581+
if (x < minNormalLoad) {
582+
return slope * x + minScale;
583+
}
584+
return slope * x + (1 - slope * maxNormalLoad);
585+
}
586+
587+
private void scaleThresholds() {
588+
OptimizedTruffleRuntime.getRuntime().setCompilationThresholdScale(FixedPointMath.toFixedPoint(scale()));
589+
}
590+
}
534591
}

0 commit comments

Comments
 (0)