diff --git a/README.md b/README.md index df437f1..82ed643 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,11 @@ SparklyPaper's config file is `sparklypaper.yml`, the file is, by default, place * When ticking an item frame, on each tick, it checks if the item on the item frame is a map and, if it is, it adds the map to be carried by the entity player * However, the `getItem()` call is a bit expensive, especially because this is only really used if the item in the item frame is a map * We can avoid this call by checking if the `cachedMapId` is not null, if it is, then we get the item in the item frame, if not, then we ignore the `getItem()` call. +* Optimize `EntityScheduler`'s `executeTick` + * On each tick, Paper runs `EntityScheduler`'s `executeTick` of each entity. This is a bit expensive, due to `ArrayDeque`'s `size()` call because it ain't a simple "get the current queue size" function, and due to the thread checks. + * To avoid the hefty `ArrayDeque`'s `size()` call, we check if we *really* need to execute the `executeTick`, by checking if `currentlyExecuting` is not empty or if `oneTimeDelayed` is not empty. This brings `executeTick`'s CPU % from 2.46% down to 1.14% in a server with ~13k entities. + * Most entities won't have any scheduled tasks, so this is a nice performance bonus. These optimizations, however, wouldn't work in a Folia environment, but because in SparklyPaper `executeTick` is always executed on the main thread, it ain't an issue for us (yay). + * Of course, this doesn't mean that `ArrayDeque#size()` is slow! It is mostly that because the `executeTick` function is called each tick for each entity, it would be better for us to avoid as many useless calls as possible. * Check how much MSPT (milliseconds per tick) each world is using in `/mspt` * Useful to figure out which worlds are lagging your server. ![Per World MSPT](docs/per-world-mspt.png) diff --git a/patches/server/0010-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch b/patches/server/0010-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch new file mode 100644 index 0000000..7ce98ce --- /dev/null +++ b/patches/server/0010-Skip-EntityScheduler-s-executeTick-checks-if-there-i.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrPowerGamerBR +Date: Sun, 19 Nov 2023 12:35:16 -0300 +Subject: [PATCH] Skip EntityScheduler's executeTick checks if there isn't any + tasks to be run + +On each tick, Paper runs EntityScheduler's executeTick of each entity. This is a bit expensive, due to ArrayDeque's size() call because it ain't a simple "get the current queue size" function, and due to the thread checks. + +To avoid the hefty ArrayDeque's size() call, we check if we *really* need to execute the executeTick, by checking if currentlyExecuting is not empty or if oneTimeDelayed is not empty. + +Most entities won't have any scheduled tasks, so this is a nice performance bonus. These optimizations, however, wouldn't work in a Folia environment, but because in SparklyPaper executeTick is always executed on the main thread, it ain't an issue for us (yay). + +diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +index 62484ebf4550b05182f693a3180bbac5d5fd906d..1ee31db28d3a4b9b841efeb37f7df7932dfad2dc 100644 +--- a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java ++++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +@@ -138,15 +138,27 @@ public final class EntityScheduler { + * @throws IllegalStateException If the scheduler is retired. + */ + public void executeTick() { ++ // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ // This wouldn't work on a multithreaded environment like Folia!!! But because the executeTick is always executed on the ++ // main thread, we don't need to care about concurrency ++ if (this.tickCount == RETIRED_TICK_COUNT) { ++ throw new IllegalStateException("Ticking retired scheduler"); ++ } ++ ++this.tickCount; ++ if (this.currentlyExecuting.isEmpty() && this.oneTimeDelayed.isEmpty()) // Check if we have any pending tasks and, if not, skip! ++ return; ++ // SparklyPaper end + final Entity thisEntity = this.entity.getHandleRaw(); + + TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); + final List toRun; + synchronized (this.stateLock) { +- if (this.tickCount == RETIRED_TICK_COUNT) { +- throw new IllegalStateException("Ticking retired scheduler"); +- } +- ++this.tickCount; ++ // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run ++ // if (this.tickCount == RETIRED_TICK_COUNT) { ++ // throw new IllegalStateException("Ticking retired scheduler"); ++ // } ++ // ++this.tickCount; ++ // SparklyPaper end + if (this.oneTimeDelayed.isEmpty()) { + toRun = null; + } else { diff --git a/patches/server/0010-Track-how-much-MSPT-each-world-used.patch b/patches/server/0011-Track-how-much-MSPT-each-world-used.patch similarity index 100% rename from patches/server/0010-Track-how-much-MSPT-each-world-used.patch rename to patches/server/0011-Track-how-much-MSPT-each-world-used.patch diff --git a/patches/server/0011-Parallel-world-ticking.patch b/patches/server/0012-Parallel-world-ticking.patch similarity index 100% rename from patches/server/0011-Parallel-world-ticking.patch rename to patches/server/0012-Parallel-world-ticking.patch