Skip to content

Commit

Permalink
Improve the performance of expireTimeouts() in HashedWheelTimer (#12888)
Browse files Browse the repository at this point in the history
Motivation:

The original logic relies on a variable `remainingRounds` and in order to maintain it, all timeouts in the bucket have to be traversed in a tick.
In fact, the complete traversing of the linked list is not necessary.

Modification:

My idea is to introduce a new variable `currRound` which represent the current round of the timer, and execRound for the execution round of each timeout. `currRound` is added by 1 when the tick starts a new round. Then for each timeout, we compare `currRound` and `execRound` to determine if the task should be executed, and break the loop once `currRound` < `execRound`.

Result:

By this means, we can reduce the number of traversed nodes, and the performance would be especially improved when the node number is large.
  • Loading branch information
needmorecode committed Oct 14, 2022
1 parent a563b27 commit ae7b88c
Showing 1 changed file with 11 additions and 7 deletions.
18 changes: 11 additions & 7 deletions common/src/main/java/io/netty5/util/HashedWheelTimer.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public class HashedWheelTimer implements Timer {
private final AtomicLong pendingTimeouts = new AtomicLong(0);
private final long maxPendingTimeouts;
private final Executor taskExecutor;
private long currRound;

private volatile long startTime;

Expand Down Expand Up @@ -495,11 +496,14 @@ public void run() {
final long deadline = waitForNextTick();
if (deadline > 0) {
int idx = (int) (tick & mask);
if (idx == 0 && tick > 0) {
currRound ++;
}
processCancelledTasks();
HashedWheelBucket bucket =
wheel[idx];
transferTimeoutsToBuckets();
bucket.expireTimeouts(deadline);
bucket.expireTimeouts(deadline, currRound);
tick++;
}
} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
Expand Down Expand Up @@ -535,7 +539,7 @@ private void transferTimeoutsToBuckets() {
}

long calculated = timeout.deadline / tickDuration;
timeout.remainingRounds = (calculated - tick) / wheel.length;
timeout.execRound = calculated / wheel.length;

final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past.
int stopIndex = (int) (ticks & mask);
Expand Down Expand Up @@ -625,9 +629,9 @@ private static final class HashedWheelTimeout implements Timeout, Runnable {
@SuppressWarnings({"unused", "FieldMayBeFinal", "RedundantFieldInitialization" })
private volatile int state = ST_INIT;

// remainingRounds will be calculated and set by Worker.transferTimeoutsToBuckets() before the
// execRound will be calculated and set by Worker.transferTimeoutsToBuckets() before the
// HashedWheelTimeout will be added to the correct HashedWheelBucket.
long remainingRounds;
long execRound;

// This will be used to chain timeouts in HashedWheelTimerBucket via a double-linked-list.
// As only the workerThread will act on it there is no need for synchronization / volatile.
Expand Down Expand Up @@ -777,13 +781,13 @@ public void addTimeout(HashedWheelTimeout timeout) {
/**
* Expire all {@link HashedWheelTimeout}s for the given {@code deadline}.
*/
public void expireTimeouts(long deadline) {
public void expireTimeouts(long deadline, long currRound) {
HashedWheelTimeout timeout = head;

// process all timeouts
while (timeout != null) {
HashedWheelTimeout next = timeout.next;
if (timeout.remainingRounds <= 0) {
if (timeout.execRound <= currRound) {
next = remove(timeout);
if (timeout.deadline <= deadline) {
timeout.expire();
Expand All @@ -795,7 +799,7 @@ public void expireTimeouts(long deadline) {
} else if (timeout.isCancelled()) {
next = remove(timeout);
} else {
timeout.remainingRounds --;
break;
}
timeout = next;
}
Expand Down

0 comments on commit ae7b88c

Please sign in to comment.