Improve tasks performance

This commit is contained in:
themode 2022-03-10 16:57:36 +01:00
parent b906bd89ce
commit 024ba736ce
3 changed files with 120 additions and 39 deletions

View File

@ -0,0 +1,35 @@
package net.minestom.jmh.timer;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class SchedulerTickBenchmark {
@Param({"0", "1", "5"})
public int tickTasks;
Scheduler scheduler;
@Setup
public void setup() {
this.scheduler = Scheduler.newScheduler();
for (int i = 0; i < this.tickTasks; i++) {
this.scheduler.scheduleTask(() -> {
}, TaskSchedule.nextTick(), TaskSchedule.nextTick());
}
}
@Benchmark
public void call() {
this.scheduler.processTick();
}
}

View File

@ -1,8 +1,6 @@
package net.minestom.server.timer;
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import org.jctools.queues.MpscUnboundedArrayQueue;
import org.jetbrains.annotations.NotNull;
@ -26,9 +24,6 @@ final class SchedulerImpl implements Scheduler {
private static final ForkJoinPool EXECUTOR = ForkJoinPool.commonPool();
private final MpscUnboundedArrayQueue<TaskImpl> taskQueue = new MpscUnboundedArrayQueue<>(64);
private final IntSet registeredTasks = new IntOpenHashSet();
private final IntSet parkedTasks = new IntOpenHashSet();
// Tasks scheduled on a certain tick
private final Int2ObjectAVLTreeMap<List<TaskImpl>> tickTaskQueue = new Int2ObjectAVLTreeMap<>();
@ -68,36 +63,17 @@ final class SchedulerImpl implements Scheduler {
@Override
public @NotNull Task submitTask(@NotNull Supplier<TaskSchedule> task,
@NotNull ExecutionType executionType) {
final TaskImpl taskRef = register(task, executionType);
final TaskImpl taskRef = new TaskImpl(TASK_COUNTER.getAndIncrement(), task,
executionType, this);
handleTask(taskRef);
return taskRef;
}
synchronized void unparkTask(TaskImpl task) {
if (parkedTasks.remove(task.id()))
void unparkTask(TaskImpl task) {
if (task.tryUnpark())
this.taskQueue.relaxedOffer(task);
}
synchronized boolean isTaskParked(TaskImpl task) {
return parkedTasks.contains(task.id());
}
synchronized void cancelTask(TaskImpl task) {
this.registeredTasks.remove(task.id());
}
synchronized boolean isTaskAlive(TaskImpl task) {
return registeredTasks.contains(task.id());
}
private synchronized TaskImpl register(@NotNull Supplier<TaskSchedule> task,
@NotNull ExecutionType executionType) {
TaskImpl taskRef = new TaskImpl(TASK_COUNTER.getAndIncrement(), task,
executionType, this);
this.registeredTasks.add(taskRef.id());
return taskRef;
}
private void safeExecute(TaskImpl task) {
// Prevent the task from being executed in the current thread
// By either adding the task to the execution queue or submitting it to the pool
@ -120,11 +96,9 @@ final class SchedulerImpl implements Scheduler {
} else if (schedule instanceof TaskScheduleImpl.FutureSchedule futureSchedule) {
futureSchedule.future().thenRun(() -> safeExecute(task));
} else if (schedule instanceof TaskScheduleImpl.Park) {
synchronized (this) {
this.parkedTasks.add(task.id());
}
task.parked = true;
} else if (schedule instanceof TaskScheduleImpl.Stop) {
cancelTask(task);
task.cancel();
} else if (schedule instanceof TaskScheduleImpl.Immediate) {
this.taskQueue.relaxedOffer(task);
}

View File

@ -1,30 +1,102 @@
package net.minestom.server.timer;
import it.unimi.dsi.fastutil.HashCommon;
import org.jetbrains.annotations.NotNull;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.function.Supplier;
record TaskImpl(int id,
@NotNull Supplier<TaskSchedule> task,
@NotNull ExecutionType executionType,
@NotNull SchedulerImpl owner) implements Task {
final class TaskImpl implements Task {
private static final VarHandle PARKED;
static {
try {
PARKED = MethodHandles.lookup().findVarHandle(TaskImpl.class, "parked", boolean.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
private final int id;
private final @NotNull Supplier<TaskSchedule> task;
private final @NotNull ExecutionType executionType;
private final @NotNull SchedulerImpl owner;
volatile boolean alive;
volatile boolean parked;
TaskImpl(int id,
@NotNull Supplier<TaskSchedule> task,
@NotNull ExecutionType executionType,
@NotNull SchedulerImpl owner) {
this.id = id;
this.task = task;
this.executionType = executionType;
this.owner = owner;
this.alive = true;
}
@Override
public void unpark() {
this.owner.unparkTask(this);
}
boolean tryUnpark() {
return PARKED.compareAndSet(this, true, false);
}
@Override
public boolean isParked() {
return owner.isTaskParked(this);
return parked;
}
@Override
public void cancel() {
this.owner.cancelTask(this);
this.alive = false;
}
@Override
public boolean isAlive() {
return owner.isTaskAlive(this);
return alive;
}
public int id() {
return id;
}
public @NotNull Supplier<TaskSchedule> task() {
return task;
}
public @NotNull ExecutionType executionType() {
return executionType;
}
public @NotNull SchedulerImpl owner() {
return owner;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (TaskImpl) obj;
return this.id == that.id;
}
@Override
public int hashCode() {
return HashCommon.murmurHash3(id);
}
@Override
public String toString() {
return "TaskImpl[" +
"id=" + id + ", " +
"task=" + task + ", " +
"executionType=" + executionType + ", " +
"owner=" + owner + ']';
}
}