diff --git a/jmh-benchmarks/src/jmh/java/net/minestom/jmh/timer/SchedulerTickBenchmark.java b/jmh-benchmarks/src/jmh/java/net/minestom/jmh/timer/SchedulerTickBenchmark.java new file mode 100644 index 000000000..9d0c7e73e --- /dev/null +++ b/jmh-benchmarks/src/jmh/java/net/minestom/jmh/timer/SchedulerTickBenchmark.java @@ -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(); + } +} diff --git a/src/main/java/net/minestom/server/timer/SchedulerImpl.java b/src/main/java/net/minestom/server/timer/SchedulerImpl.java index 11404bc8c..f8fd76b8b 100644 --- a/src/main/java/net/minestom/server/timer/SchedulerImpl.java +++ b/src/main/java/net/minestom/server/timer/SchedulerImpl.java @@ -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 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> tickTaskQueue = new Int2ObjectAVLTreeMap<>(); @@ -68,36 +63,17 @@ final class SchedulerImpl implements Scheduler { @Override public @NotNull Task submitTask(@NotNull Supplier 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 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); } diff --git a/src/main/java/net/minestom/server/timer/TaskImpl.java b/src/main/java/net/minestom/server/timer/TaskImpl.java index 63d478f69..165a31639 100644 --- a/src/main/java/net/minestom/server/timer/TaskImpl.java +++ b/src/main/java/net/minestom/server/timer/TaskImpl.java @@ -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 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 task; + private final @NotNull ExecutionType executionType; + private final @NotNull SchedulerImpl owner; + + volatile boolean alive; + volatile boolean parked; + + TaskImpl(int id, + @NotNull Supplier 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 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 + ']'; + } + }