aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches-Unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch
diff options
context:
space:
mode:
authorKyle Wood <[email protected]>2021-04-24 17:01:33 -0500
committerKyle Wood <[email protected]>2021-04-25 18:37:43 -0500
commit3093b81fee3064603c368ab934eddf66ce304433 (patch)
treecb99f05b5f31de92c41af4cc40b4bef5f3cbf573 /Spigot-Server-Patches-Unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch
parent1af696a05d21cbdd7b5a7170f95598c013257588 (diff)
downloadPaper-3093b81fee3064603c368ab934eddf66ce304433.tar.gz
Paper-3093b81fee3064603c368ab934eddf66ce304433.zip
Move patches
Diffstat (limited to 'Spigot-Server-Patches-Unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch')
-rw-r--r--Spigot-Server-Patches-Unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch294
1 files changed, 294 insertions, 0 deletions
diff --git a/Spigot-Server-Patches-Unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch b/Spigot-Server-Patches-Unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch
new file mode 100644
index 0000000000..e7e8ed2c43
--- /dev/null
+++ b/Spigot-Server-Patches-Unmapped/0428-Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch
@@ -0,0 +1,294 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <[email protected]>
+Date: Wed, 8 Apr 2020 03:06:30 -0400
+Subject: [PATCH] Optimize PlayerChunkMap memory use for visibleChunks
+
+No longer clones visible chunks which is causing massive memory
+allocation issues, likely the source of Humongous Objects on large servers.
+
+Instead we just synchronize, clear and rebuild, reusing the same object buffers
+as before with only 2 small objects created (FastIterator/MapEntry)
+
+This should result in siginificant memory use reduction and improved GC behavior.
+
+diff --git a/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..f6ff4d8132a95895680f5bc81f8f873e78f0bbdb
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java
+@@ -0,0 +1,39 @@
++package com.destroystokyo.paper.util.map;
++
++import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
++
++public class Long2ObjectLinkedOpenHashMapFastCopy<V> extends Long2ObjectLinkedOpenHashMap<V> {
++
++ public void copyFrom(Long2ObjectLinkedOpenHashMapFastCopy<V> map) {
++ if (key.length != map.key.length) {
++ key = null;
++ key = new long[map.key.length];
++ }
++ if (value.length != map.value.length) {
++ value = null;
++ //noinspection unchecked
++ value = (V[]) new Object[map.value.length];
++ }
++ if (link.length != map.link.length) {
++ link = null;
++ link = new long[map.link.length];
++ }
++ System.arraycopy(map.key, 0, this.key, 0, map.key.length);
++ System.arraycopy(map.value, 0, this.value, 0, map.value.length);
++ System.arraycopy(map.link, 0, this.link, 0, map.link.length);
++ this.size = map.size;
++ this.mask = map.mask;
++ this.first = map.first;
++ this.last = map.last;
++ this.n = map.n;
++ this.maxFill = map.maxFill;
++ this.containsNullKey = map.containsNullKey;
++ }
++
++ @Override
++ public Long2ObjectLinkedOpenHashMapFastCopy<V> clone() {
++ Long2ObjectLinkedOpenHashMapFastCopy<V> clone = (Long2ObjectLinkedOpenHashMapFastCopy<V>) super.clone();
++ clone.copyFrom(this);
++ return clone;
++ }
++}
+diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
+index 5a410550cfb48505c9de9979465ed1528c8fbf05..9edbde8299bcd127e1727d34ed441f638e716b2a 100644
+--- a/src/main/java/net/minecraft/server/MCUtil.java
++++ b/src/main/java/net/minecraft/server/MCUtil.java
+@@ -616,7 +616,7 @@ public final class MCUtil {
+
+ WorldServer world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle();
+ PlayerChunkMap chunkMap = world.getChunkProvider().playerChunkMap;
+- Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks = chunkMap.visibleChunks;
++ Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks = chunkMap.getVisibleChunks();
+ ChunkMapDistance chunkMapDistance = chunkMap.chunkDistanceManager;
+ List<PlayerChunk> allChunks = new ArrayList<>(visibleChunks.values());
+ List<EntityPlayer> players = world.players;
+diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
+index a444f6214b90f7707be2265f4b2ab12632986c53..138676e5b03bc80a777a1f4c12f3f4b5316e8dea 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
++++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
+@@ -780,7 +780,7 @@ public class ChunkProviderServer extends IChunkProvider {
+ entityPlayer.playerNaturallySpawnedEvent.callEvent();
+ };
+ // Paper end
+- this.playerChunkMap.f().forEach((playerchunk) -> { // Paper - no... just no...
++ this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping
+ Optional<Chunk> optional = ((Either) playerchunk.a().getNow(PlayerChunk.UNLOADED_CHUNK)).left();
+
+ if (optional.isPresent()) {
+diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
+index 12cfe9f3c89316557e94c8b944b4f82277fb8877..8050be2ed04fb0b8141f92595680407bba65dad5 100644
+--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
+@@ -106,8 +106,33 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ public static final int GOLDEN_TICKET = 33 + ChunkStatus.b();
+- public final Long2ObjectLinkedOpenHashMap<PlayerChunk> updatingChunks = new Long2ObjectLinkedOpenHashMap();
+- public volatile Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks;
++ // Paper start - faster copying
++ public final Long2ObjectLinkedOpenHashMap<PlayerChunk> updatingChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<>(); // Paper - faster copying
++ public final Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks = new ProtectedVisibleChunksMap(); // Paper - faster copying
++
++ private class ProtectedVisibleChunksMap extends com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk> {
++ @Override
++ public PlayerChunk put(long k, PlayerChunk playerChunk) {
++ throw new UnsupportedOperationException("Updating visible Chunks");
++ }
++
++ @Override
++ public PlayerChunk remove(long k) {
++ throw new UnsupportedOperationException("Removing visible Chunks");
++ }
++
++ @Override
++ public PlayerChunk get(long k) {
++ return PlayerChunkMap.this.getVisibleChunk(k);
++ }
++
++ public PlayerChunk safeGet(long k) {
++ return super.get(k);
++ }
++ }
++ // Paper end
++ public final com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk> pendingVisibleChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk>(); // Paper - this is used if the visible chunks is updated while iterating only
++ public transient com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk> visibleChunksClone; // Paper - used for async access of visible chunks, clone and cache only when needed
+ private final Long2ObjectLinkedOpenHashMap<PlayerChunk> pendingUnload;
+ public final LongSet loadedChunks; // Paper - private -> public
+ public final WorldServer world;
+@@ -180,7 +205,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+
+ public PlayerChunkMap(WorldServer worldserver, Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler<Runnable> iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier<WorldPersistentData> supplier, int i, boolean flag) {
+ super(new File(convertable_conversionsession.a(worldserver.getDimensionKey()), "region"), datafixer, flag);
+- this.visibleChunks = this.updatingChunks.clone();
++ //this.visibleChunks = this.updatingChunks.clone(); // Paper - no more cloning
+ this.pendingUnload = new Long2ObjectLinkedOpenHashMap();
+ this.loadedChunks = new LongOpenHashSet();
+ this.unloadQueue = new LongOpenHashSet();
+@@ -272,9 +297,52 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ return (PlayerChunk) this.updatingChunks.get(i);
+ }
+
++ // Paper start - remove cloning of visible chunks unless accessed as a collection async
++ private static final boolean DEBUG_ASYNC_VISIBLE_CHUNKS = Boolean.getBoolean("paper.debug-async-visible-chunks");
++ private boolean isIterating = false;
++ private boolean hasPendingVisibleUpdate = false;
++ public void forEachVisibleChunk(java.util.function.Consumer<PlayerChunk> consumer) {
++ org.spigotmc.AsyncCatcher.catchOp("forEachVisibleChunk");
++ boolean prev = isIterating;
++ isIterating = true;
++ try {
++ for (PlayerChunk value : this.visibleChunks.values()) {
++ consumer.accept(value);
++ }
++ } finally {
++ this.isIterating = prev;
++ if (!this.isIterating && this.hasPendingVisibleUpdate) {
++ ((ProtectedVisibleChunksMap)this.visibleChunks).copyFrom(this.pendingVisibleChunks);
++ this.pendingVisibleChunks.clear();
++ this.hasPendingVisibleUpdate = false;
++ }
++ }
++ }
++ public Long2ObjectLinkedOpenHashMap<PlayerChunk> getVisibleChunks() {
++ if (Thread.currentThread() == this.world.serverThread) {
++ return this.visibleChunks;
++ } else {
++ synchronized (this.visibleChunks) {
++ if (DEBUG_ASYNC_VISIBLE_CHUNKS) new Throwable("Async getVisibleChunks").printStackTrace();
++ if (this.visibleChunksClone == null) {
++ this.visibleChunksClone = this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.clone() : ((ProtectedVisibleChunksMap)this.visibleChunks).clone();
++ }
++ return this.visibleChunksClone;
++ }
++ }
++ }
++ // Paper end
++
+ @Nullable
+ public PlayerChunk getVisibleChunk(long i) { // Paper - protected -> public
+- return (PlayerChunk) this.visibleChunks.get(i);
++ // Paper start - mt safe get
++ if (Thread.currentThread() != this.world.serverThread) {
++ synchronized (this.visibleChunks) {
++ return (PlayerChunk) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(i) : ((ProtectedVisibleChunksMap)this.visibleChunks).safeGet(i));
++ }
++ }
++ return (PlayerChunk) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(i) : ((ProtectedVisibleChunksMap)this.visibleChunks).safeGet(i));
++ // Paper end
+ }
+
+ protected IntSupplier c(long i) {
+@@ -462,8 +530,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ // Paper end
+
+ protected void save(boolean flag) {
++ Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks = this.getVisibleChunks(); // Paper remove clone of visible Chunks unless saving off main thread (watchdog kill)
+ if (flag) {
+- List<PlayerChunk> list = (List) this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList());
++ List<PlayerChunk> list = (List) visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).peek(PlayerChunk::m).collect(Collectors.toList()); // Paper - remove cloning of visible chunks
+ MutableBoolean mutableboolean = new MutableBoolean();
+
+ do {
+@@ -491,7 +560,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ // this.i(); // Paper - nuke IOWorker
+ PlayerChunkMap.LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.w.getName());
+ } else {
+- this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).forEach((playerchunk) -> {
++ visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).forEach((playerchunk) -> {
+ IChunkAccess ichunkaccess = (IChunkAccess) playerchunk.getChunkSave().getNow(null); // CraftBukkit - decompile error
+
+ if (ichunkaccess instanceof ProtoChunkExtension || ichunkaccess instanceof Chunk) {
+@@ -662,7 +731,20 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ if (!this.updatingChunksModified) {
+ return false;
+ } else {
+- this.visibleChunks = this.updatingChunks.clone();
++ // Paper start - stop cloning visibleChunks
++ synchronized (this.visibleChunks) {
++ if (isIterating) {
++ hasPendingVisibleUpdate = true;
++ this.pendingVisibleChunks.copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk>)this.updatingChunks);
++ } else {
++ hasPendingVisibleUpdate = false;
++ this.pendingVisibleChunks.clear();
++ ((ProtectedVisibleChunksMap)this.visibleChunks).copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk>)this.updatingChunks);
++ this.visibleChunksClone = null;
++ }
++ }
++ // Paper end
++
+ this.updatingChunksModified = false;
+ return true;
+ }
+@@ -1133,12 +1215,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
+ }
+
+ protected Iterable<PlayerChunk> f() {
+- return Iterables.unmodifiableIterable(this.visibleChunks.values());
++ return Iterables.unmodifiableIterable(this.getVisibleChunks().values()); // Paper
+ }
+
+ void a(Writer writer) throws IOException {
+ CSVWriter csvwriter = CSVWriter.a().a("x").a("z").a("level").a("in_memory").a("status").a("full_status").a("accessible_ready").a("ticking_ready").a("entity_ticking_ready").a("ticket").a("spawning").a("entity_count").a("block_entity_count").a(writer);
+- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunks.long2ObjectEntrySet().iterator();
++ ObjectBidirectionalIterator objectbidirectionaliterator = this.getVisibleChunks().long2ObjectEntrySet().iterator(); // Paper
+
+ while (objectbidirectionaliterator.hasNext()) {
+ Entry<PlayerChunk> entry = (Entry) objectbidirectionaliterator.next();
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index 2d142699ebe2062947ba3eb228aaad4468c11371..adcb2bd279f1f87d174556cb1b8aac497c11d7d3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -294,6 +294,7 @@ public class CraftWorld implements World {
+ return ret;
+ }
+ public int getTileEntityCount() {
++ return net.minecraft.server.MCUtil.ensureMain(() -> {
+ // We don't use the full world tile entity list, so we must iterate chunks
+ Long2ObjectLinkedOpenHashMap<PlayerChunk> chunks = world.getChunkProvider().playerChunkMap.visibleChunks;
+ int size = 0;
+@@ -305,11 +306,13 @@ public class CraftWorld implements World {
+ size += chunk.tileEntities.size();
+ }
+ return size;
++ });
+ }
+ public int getTickableTileEntityCount() {
+ return world.tileEntityListTick.size();
+ }
+ public int getChunkCount() {
++ return net.minecraft.server.MCUtil.ensureMain(() -> {
+ int ret = 0;
+
+ for (PlayerChunk chunkHolder : world.getChunkProvider().playerChunkMap.visibleChunks.values()) {
+@@ -318,7 +321,7 @@ public class CraftWorld implements World {
+ }
+ }
+
+- return ret;
++ return ret; });
+ }
+ public int getPlayerCount() {
+ return world.players.size();
+@@ -443,6 +446,14 @@ public class CraftWorld implements World {
+
+ @Override
+ public Chunk[] getLoadedChunks() {
++ // Paper start
++ if (Thread.currentThread() != world.getMinecraftWorld().serverThread) {
++ synchronized (world.getChunkProvider().playerChunkMap.visibleChunks) {
++ Long2ObjectLinkedOpenHashMap<PlayerChunk> chunks = world.getChunkProvider().playerChunkMap.visibleChunks;
++ return chunks.values().stream().map(PlayerChunk::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.Chunk::getBukkitChunk).toArray(Chunk[]::new);
++ }
++ }
++ // Paper end
+ Long2ObjectLinkedOpenHashMap<PlayerChunk> chunks = world.getChunkProvider().playerChunkMap.visibleChunks;
+ return chunks.values().stream().map(PlayerChunk::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.Chunk::getBukkitChunk).toArray(Chunk[]::new);
+ }