Skip to content

Commit 872d7cd

Browse files
authored
Merge pull request #2 from nicktindall/framed-thread-pool-utilization_bm
Framed thread pool utilization benchmark hacking
2 parents 8850789 + efa48b4 commit 872d7cd

File tree

2 files changed

+56
-74
lines changed

2 files changed

+56
-74
lines changed

benchmarks/src/main/java/org/elasticsearch/benchmark/common/util/concurrent/ThreadPoolUtilizationBenchmark.java

Lines changed: 50 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,110 +9,92 @@
99

1010
package org.elasticsearch.benchmark.common.util.concurrent;
1111

12-
import org.elasticsearch.common.settings.Settings;
13-
import org.elasticsearch.common.util.concurrent.EsExecutors;
14-
import org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor;
1512
import org.elasticsearch.common.util.concurrent.TaskExecutionTimeTrackingEsThreadPoolExecutor;
16-
import org.elasticsearch.common.util.concurrent.ThreadContext;
1713
import org.openjdk.jmh.annotations.Benchmark;
1814
import org.openjdk.jmh.annotations.BenchmarkMode;
1915
import org.openjdk.jmh.annotations.Fork;
16+
import org.openjdk.jmh.annotations.Group;
2017
import org.openjdk.jmh.annotations.Measurement;
2118
import org.openjdk.jmh.annotations.Mode;
2219
import org.openjdk.jmh.annotations.OutputTimeUnit;
2320
import org.openjdk.jmh.annotations.Param;
2421
import org.openjdk.jmh.annotations.Scope;
2522
import org.openjdk.jmh.annotations.Setup;
2623
import org.openjdk.jmh.annotations.State;
27-
import org.openjdk.jmh.annotations.TearDown;
24+
import org.openjdk.jmh.annotations.Threads;
2825
import org.openjdk.jmh.annotations.Warmup;
2926
import org.openjdk.jmh.infra.Blackhole;
3027

31-
import java.time.Duration;
32-
import java.util.concurrent.CountDownLatch;
33-
import java.util.concurrent.Executors;
3428
import java.util.concurrent.TimeUnit;
3529

36-
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
37-
@Measurement(iterations = 1, time = 10, timeUnit = TimeUnit.SECONDS)
30+
@Threads(12)
31+
@Warmup(iterations = 3, time = 200, timeUnit = TimeUnit.MILLISECONDS)
32+
@Measurement(iterations = 5, time = 600, timeUnit = TimeUnit.MILLISECONDS)
3833
@BenchmarkMode(Mode.AverageTime)
39-
@OutputTimeUnit(TimeUnit.MILLISECONDS)
34+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
35+
@State(Scope.Benchmark)
4036
@Fork(1)
41-
@State(Scope.Thread)
4237
public class ThreadPoolUtilizationBenchmark {
4338

44-
@Param({ "false", "true" })
45-
private boolean trackUtilization;
39+
@Param({ "0", "10000", "100000" })
40+
private int callIntervalTicks;
4641

47-
@Param({ "4", "8", "16" })
48-
private int poolSize;
49-
50-
@Param({ "1000000" })
51-
private int tasksNum;
52-
53-
@Param({ "10" }) // 10ms is aggressive interval, it increases frame updates on FramedTimeTracker, normally we run at 30/60
54-
// seconds
42+
/**
43+
* This makes very little difference, all the overhead is in the synchronization
44+
*/
45+
@Param({ "10" })
5546
private int utilizationIntervalMs;
5647

57-
private EsThreadPoolExecutor executor;
48+
@State(Scope.Thread)
49+
public static class TaskState {
50+
boolean running = false;
5851

59-
private EsThreadPoolExecutor newExecutor(boolean tracking) {
60-
var conf = EsExecutors.TaskTrackingConfig.builder();
61-
if (tracking) {
62-
conf.trackExecutionTime(0.3).trackUtilization(Duration.ofMillis(utilizationIntervalMs));
52+
boolean shouldStart() {
53+
return (running = running == false);
6354
}
64-
return EsExecutors.newFixed(
65-
"bench",
66-
poolSize,
67-
tasksNum,
68-
Executors.defaultThreadFactory(),
69-
new ThreadContext(Settings.EMPTY),
70-
conf.build()
71-
);
7255
}
7356

57+
private TaskExecutionTimeTrackingEsThreadPoolExecutor.FramedTimeTracker timeTracker;
58+
7459
@Setup
7560
public void setup() {
76-
if (trackUtilization) {
77-
var exec = newExecutor(true);
78-
if (exec instanceof TaskExecutionTimeTrackingEsThreadPoolExecutor trackingExecutor) {
79-
if (trackingExecutor.trackingConfig().trackUtilization() == false) {
80-
throw new IllegalStateException("utilization tracking must be enabled");
81-
} else {
82-
executor = trackingExecutor;
83-
}
84-
} else {
85-
throw new IllegalStateException("must be tracking executor");
86-
}
61+
timeTracker = new TaskExecutionTimeTrackingEsThreadPoolExecutor.FramedTimeTracker(
62+
TimeUnit.MILLISECONDS.toNanos(utilizationIntervalMs),
63+
System::nanoTime
64+
);
65+
}
66+
67+
@Benchmark
68+
public void baseline() {
69+
Blackhole.consumeCPU(callIntervalTicks);
70+
}
71+
72+
@Group("ReadAndWrite")
73+
@Benchmark
74+
public void startAndStopTasks(TaskState state) {
75+
Blackhole.consumeCPU(callIntervalTicks);
76+
if (state.shouldStart()) {
77+
timeTracker.startTask();
8778
} else {
88-
var exec = newExecutor(false);
89-
if (exec instanceof TaskExecutionTimeTrackingEsThreadPoolExecutor) {
90-
throw new IllegalStateException("must be non-tracking executor");
91-
}
92-
executor = exec;
79+
timeTracker.endTask();
9380
}
9481
}
9582

96-
@TearDown
97-
public void tearDown() throws InterruptedException {
98-
executor.shutdown();
99-
executor.awaitTermination(0, TimeUnit.MILLISECONDS);
83+
@Benchmark
84+
@Group("ReadAndWrite")
85+
public void readPrevious(Blackhole blackhole) {
86+
Blackhole.consumeCPU(callIntervalTicks);
87+
blackhole.consume(timeTracker.previousFrameTime());
10088
}
10189

10290
@Benchmark
103-
public void run(Blackhole bh) throws InterruptedException {
104-
var completedTasks = new CountDownLatch(tasksNum);
105-
for (var i = 0; i < tasksNum; i++) {
106-
executor.execute(() -> {
107-
// busy cycles for cpu
108-
var r = 0;
109-
for (var j = 0; j < 1000; j++) {
110-
r += j * 2;
111-
}
112-
bh.consume(r);
113-
completedTasks.countDown();
114-
});
91+
@Group("JustWrite")
92+
public void startAndStopTasksOnly(TaskState state) {
93+
Blackhole.consumeCPU(callIntervalTicks);
94+
if (state.shouldStart()) {
95+
timeTracker.startTask();
96+
} else {
97+
timeTracker.endTask();
11598
}
116-
completedTasks.await();
11799
}
118100
}

server/src/main/java/org/elasticsearch/common/util/concurrent/TaskExecutionTimeTrackingEsThreadPoolExecutor.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ public boolean trackingMaxQueueLatency() {
257257
*
258258
* Can be extended to remember multiple past frames.
259259
*/
260-
static class FramedTimeTracker {
260+
public static class FramedTimeTracker {
261261
final long interval;
262262
private final Supplier<Long> timeNow;
263263
long ongoingTasks;
@@ -266,7 +266,7 @@ static class FramedTimeTracker {
266266
long previousTime;
267267

268268
// for testing
269-
FramedTimeTracker(long intervalNano, Supplier<Long> timeNow) {
269+
public FramedTimeTracker(long intervalNano, Supplier<Long> timeNow) {
270270
assert intervalNano > 0;
271271
this.interval = intervalNano;
272272
this.timeNow = timeNow;
@@ -278,7 +278,7 @@ static class FramedTimeTracker {
278278
this.timeNow = System::nanoTime;
279279
}
280280

281-
synchronized void updateFrame() {
281+
public synchronized void updateFrame() {
282282
updateFrame0(timeNow.get());
283283
}
284284

@@ -310,7 +310,7 @@ private void updateFrame0(long nowTime) {
310310
* Start tracking new task, assume that task runs indefinitely, or at least till end of frame.
311311
* If task finishes sooner than end of interval {@link FramedTimeTracker#endTask()} will deduct remaining time.
312312
*/
313-
synchronized void startTask() {
313+
public synchronized void startTask() {
314314
var now = timeNow.get();
315315
updateFrame0(now);
316316
currentTime += (currentFrame + 1) * interval - now;
@@ -320,7 +320,7 @@ synchronized void startTask() {
320320
/**
321321
* Stop task tracking. We already assumed that task runs till end of frame, here we deduct not used time.
322322
*/
323-
synchronized void endTask() {
323+
public synchronized void endTask() {
324324
var now = timeNow.get();
325325
updateFrame0(now);
326326
currentTime -= (currentFrame + 1) * interval - now;
@@ -330,7 +330,7 @@ synchronized void endTask() {
330330
/**
331331
* Returns previous frame total execution time.
332332
*/
333-
synchronized long previousFrameTime() {
333+
public synchronized long previousFrameTime() {
334334
updateFrame0(timeNow.get());
335335
return previousTime;
336336
}

0 commit comments

Comments
 (0)