Skip to content

Commit 2791f0f

Browse files
njhillnormanmaurer
authored andcommitted
Avoid use of global AtomicLong for ScheduledFutureTask ids (#9599)
Motivation Currently a static AtomicLong is used to allocate a unique id whenever a task is scheduled to any event loop. This could be a source of contention if delayed tasks are scheduled at a high frequency and can be easily avoided by having a non-volatile id counter per queue. Modifications - Replace static AtomicLong ScheduledFutureTask#nextTaskId with a long field in AbstractScheduledExecutorService - Set ScheduledFutureTask#id based on this when adding the task to the queue (in event loop) instead of at construction time - Add simple benchmark Result Less contention / cache-miss possibility when scheduling future tasks Before: Benchmark (num) Mode Cnt Score Error Units scheduleLots 100000 thrpt 20 346.008 ± 21.931 ops/s Benchmark (num) Mode Cnt Score Error Units scheduleLots 100000 thrpt 20 654.824 ± 22.064 ops/s
1 parent 86ff76a commit 2791f0f

File tree

4 files changed

+120
-8
lines changed

4 files changed

+120
-8
lines changed

common/src/main/java/io/netty/util/concurrent/AbstractScheduledEventExecutor.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public int compare(ScheduledFutureTask<?> o1, ScheduledFutureTask<?> o2) {
3939

4040
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;
4141

42+
long nextTaskId;
43+
4244
protected AbstractScheduledEventExecutor() {
4345
}
4446

@@ -241,12 +243,12 @@ protected void validateScheduled(long amount, TimeUnit unit) {
241243

242244
private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
243245
if (inEventLoop()) {
244-
scheduledTaskQueue().add(task);
246+
scheduledTaskQueue().add(task.setId(nextTaskId++));
245247
} else {
246248
executeScheduledRunnable(new Runnable() {
247249
@Override
248250
public void run() {
249-
scheduledTaskQueue().add(task);
251+
scheduledTaskQueue().add(task.setId(nextTaskId++));
250252
}
251253
}, true, task.deadlineNanos());
252254
}

common/src/main/java/io/netty/util/concurrent/ScheduledFutureTask.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@
2323
import java.util.concurrent.Callable;
2424
import java.util.concurrent.Delayed;
2525
import java.util.concurrent.TimeUnit;
26-
import java.util.concurrent.atomic.AtomicLong;
2726

2827
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
2928
final class ScheduledFutureTask<V> extends PromiseTask<V> implements ScheduledFuture<V>, PriorityQueueNode {
30-
private static final AtomicLong nextTaskId = new AtomicLong();
3129
private static final long START_TIME = System.nanoTime();
3230

3331
static long nanoTime() {
@@ -44,7 +42,9 @@ static long initialNanoTime() {
4442
return START_TIME;
4543
}
4644

47-
private final long id = nextTaskId.getAndIncrement();
45+
// set once when added to priority queue
46+
private long id;
47+
4848
private long deadlineNanos;
4949
/* 0 - no repeat, >0 - repeat at fixed rate, <0 - repeat with fixed delay */
5050
private final long periodNanos;
@@ -79,6 +79,11 @@ static long initialNanoTime() {
7979
periodNanos = 0;
8080
}
8181

82+
ScheduledFutureTask<V> setId(long id) {
83+
this.id = id;
84+
return this;
85+
}
86+
8287
@Override
8388
protected EventExecutor executor() {
8489
return super.executor();
@@ -182,9 +187,7 @@ protected StringBuilder toStringBuilder() {
182187
StringBuilder buf = super.toStringBuilder();
183188
buf.setCharAt(buf.length() - 1, ',');
184189

185-
return buf.append(" id: ")
186-
.append(id)
187-
.append(", deadline: ")
190+
return buf.append(" deadline: ")
188191
.append(deadlineNanos)
189192
.append(", period: ")
190193
.append(periodNanos)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2019 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.util.concurrent;
17+
18+
import java.util.concurrent.Callable;
19+
import java.util.concurrent.TimeUnit;
20+
21+
import org.openjdk.jmh.annotations.Benchmark;
22+
import org.openjdk.jmh.annotations.Level;
23+
import org.openjdk.jmh.annotations.Measurement;
24+
import org.openjdk.jmh.annotations.Param;
25+
import org.openjdk.jmh.annotations.Scope;
26+
import org.openjdk.jmh.annotations.Setup;
27+
import org.openjdk.jmh.annotations.State;
28+
import org.openjdk.jmh.annotations.TearDown;
29+
import org.openjdk.jmh.annotations.Threads;
30+
import org.openjdk.jmh.annotations.Warmup;
31+
32+
import io.netty.channel.DefaultEventLoop;
33+
import io.netty.microbench.util.AbstractMicrobenchmark;
34+
35+
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
36+
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
37+
@State(Scope.Benchmark)
38+
public class ScheduleFutureTaskBenchmark extends AbstractMicrobenchmark {
39+
40+
static final Callable<Void> NO_OP = new Callable<Void>() {
41+
@Override
42+
public Void call() throws Exception {
43+
return null;
44+
}
45+
};
46+
47+
@State(Scope.Thread)
48+
public static class ThreadState {
49+
50+
@Param({ "100000" })
51+
int num;
52+
53+
AbstractScheduledEventExecutor eventLoop;
54+
55+
@Setup(Level.Trial)
56+
public void reset() {
57+
eventLoop = new DefaultEventLoop();
58+
}
59+
60+
@Setup(Level.Invocation)
61+
public void clear() {
62+
eventLoop.submit(new Runnable() {
63+
@Override
64+
public void run() {
65+
eventLoop.cancelScheduledTasks();
66+
}
67+
}).awaitUninterruptibly();
68+
}
69+
70+
@TearDown(Level.Trial)
71+
public void shutdown() {
72+
eventLoop.shutdownGracefully().awaitUninterruptibly();
73+
}
74+
}
75+
76+
@Benchmark
77+
@Threads(3)
78+
public Future<?> scheduleLots(final ThreadState threadState) {
79+
return threadState.eventLoop.submit(new Runnable() {
80+
@Override
81+
public void run() {
82+
for (int i = 1; i <= threadState.num; i++) {
83+
threadState.eventLoop.schedule(NO_OP, i, TimeUnit.HOURS);
84+
}
85+
}
86+
}).syncUninterruptibly();
87+
}
88+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2019 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
/**
17+
* Benchmarks for {@link io.netty.util.concurrent}.
18+
*/
19+
package io.netty.util.concurrent;

0 commit comments

Comments
 (0)