From 397ba6e9dffea1e1a2541596adb6b037088b645f Mon Sep 17 00:00:00 2001 From: Aikar Date: Fri, 16 Oct 2015 22:14:48 -0500 Subject: [PATCH] Optimize Entity and Tile Entity Removal Java's implementation of List.removeAll is extremely slow. This is currently causing lots of TPS loss when lots of chunk unload activity occurs, as the process iterates the removal list for every entry in the source list, resulting in O(n^2) performance. This change will switch the process to instead iterate over the removal list, and marking a boolean that its removed. Then, we then iterate the source list and use a compaction technique that skips any object marked for removal. After all live objects are compacted down, we do a range removal to clear out any removed objects at the end of the current list. This gives us O(n) performance and a much cheaper overall operation. Compaction technique was originally used by CyberTiger in a different implementation. Finally, we remove MOST single .remove() calls, and run a 2nd compaction after ticking in order to remove the singles. This also fixes a bug with Tick Position in the Tick limiter, where previously .removeAll would shift entity index order but the tick position was never moved to its new location. diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index 68126c4..3b995c5 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -32,7 +32,12 @@ import org.bukkit.event.entity.EntityPortalEvent; import org.bukkit.plugin.PluginManager; // CraftBukkit end -public abstract class Entity implements ICommandListener { +// PaperSpigot start - Optimized TE/E Removal +public abstract class Entity implements ICommandListener, org.github.paperspigot.OptimizedRemoveAllArrayList.Marker { + private boolean needsRemoved = false; + public boolean isToBeRemoved() { return needsRemoved; } + public void setRemovalState(boolean removalState) { needsRemoved = removalState; } + // PaperSpigot End // CraftBukkit start private static final int CURRENT_LEVEL = 2; diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java index 3fc6450..6d13a55 100644 --- a/src/main/java/net/minecraft/server/TileEntity.java +++ b/src/main/java/net/minecraft/server/TileEntity.java @@ -9,7 +9,12 @@ import org.apache.logging.log4j.Logger; import org.spigotmc.CustomTimingsHandler; // Spigot import org.bukkit.inventory.InventoryHolder; // CraftBukkit -public abstract class TileEntity { +// PaperSpigot start - Optimized TE/E Removal +public abstract class TileEntity implements org.github.paperspigot.OptimizedRemoveAllArrayList.Marker { + private boolean needsRemoved = false; + public boolean isToBeRemoved() { return needsRemoved; } + public void setRemovalState(boolean removalState) { needsRemoved = removalState; } + // PaperSpigot end public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot private static final Logger a = LogManager.getLogger(); @@ -126,7 +131,7 @@ public abstract class TileEntity { } public boolean x() { - return this.d; + return this.d && !isToBeRemoved(); // PaperSpigot - Optimize TE/E Removal } public void y() { diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 13ab789..0c1ed6c 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -32,7 +32,7 @@ public abstract class World implements IBlockAccess { private int a = 63; protected boolean e; // Spigot start - guard entity list from removals - public final List entityList = new java.util.ArrayList() + public final List entityList = new org.github.paperspigot.OptimizedRemoveAllArrayList() // PaperSpigot - Optimized TE/E Removal { @Override public Entity remove(int index) @@ -59,7 +59,7 @@ public abstract class World implements IBlockAccess { // Spigot end protected final List g = Lists.newArrayList(); //public final List h = Lists.newArrayList(); // PaperSpigot - Remove unused list - public final List tileEntityList = Lists.newArrayList(); + public final List tileEntityList = new org.github.paperspigot.OptimizedRemoveAllArrayList(); // PaperSpigot - Optimized TE/E Removal private final List b = Lists.newArrayList(); private final List c = Lists.newArrayList(); public final List players = Lists.newArrayList(); @@ -1462,13 +1462,14 @@ public abstract class World implements IBlockAccess { } guardEntityList = false; // Spigot - this.entityList.remove(this.tickPosition--); // CraftBukkit - Use field for loop variable + this.entityList.remove(entity); // CraftBukkit - Use field for loop variable // PaperSpigot - Optimized TE/E Removal guardEntityList = true; // Spigot this.b(entity); } this.methodProfiler.b(); } + this.entityList.removeAll(null); // PaperSpigot - Optimize TE/E Removal guardEntityList = false; // Spigot timings.entityTick.stopTiming(); // Spigot @@ -1524,13 +1525,14 @@ public abstract class World implements IBlockAccess { if (tileentity.x()) { tilesThisCycle--; - this.tileEntityList.remove(tileTickPosition--); + this.tileEntityList.remove(tileentity); // PaperSpigot - Optimized TE/E Removal //this.h.remove(tileentity); // PaperSpigot - Remove unused list if (this.isLoaded(tileentity.getPosition())) { this.getChunkAtWorldCoords(tileentity.getPosition()).e(tileentity.getPosition()); } } } + this.tileEntityList.removeAll(null); // PaperSpigot - Optimized TE/E Removal timings.tileEntityTick.stopTiming(); // Spigot timings.tileEntityPending.startTiming(); // Spigot @@ -2619,6 +2621,7 @@ public abstract class World implements IBlockAccess { while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); + if (entity.isToBeRemoved()) { continue; } // PaperSpigot - Optimized TE/E Removal if (oclass.isAssignableFrom(entity.getClass()) && predicate.apply((T) entity)) { // CraftBukkit - fix decompile error arraylist.add(entity); @@ -2703,6 +2706,7 @@ public abstract class World implements IBlockAccess { while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); + if (entity.isToBeRemoved()) { continue; } // PaperSpigot - Optimized TE/E Removal // CraftBukkit start - Split out persistent check, don't apply it to special persistent mobs if (entity instanceof EntityInsentient) { EntityInsentient entityinsentient = (EntityInsentient) entity; diff --git a/src/main/java/org/github/paperspigot/OptimizedRemoveAllArrayList.java b/src/main/java/org/github/paperspigot/OptimizedRemoveAllArrayList.java new file mode 100644 index 0000000..6d4388a --- /dev/null +++ b/src/main/java/org/github/paperspigot/OptimizedRemoveAllArrayList.java @@ -0,0 +1,78 @@ +package org.github.paperspigot; + +import org.github.paperspigot.OptimizedRemoveAllArrayList.Marker; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Improved algorithim for bulk removing entries from a list + * + * WARNING: This system only works on Identity Based lists, + * unlike traditional .removeAll() that operates on object equality. + * + */ +public class OptimizedRemoveAllArrayList extends ArrayList { + public OptimizedRemoveAllArrayList(int initialCapacity) { + super(initialCapacity); + } + + public OptimizedRemoveAllArrayList() { + } + + public OptimizedRemoveAllArrayList(Collection c) { + super(c); + } + + public OptimizedRemoveAllArrayList clone() { + return new OptimizedRemoveAllArrayList(this); + } + + @Override + public boolean addAll(Collection c) { + for (T t : c) { + t.setRemovalState(false); + } + return super.addAll(c); + } + + @Override + public boolean add(T t) { + t.setRemovalState(false); + return super.add(t); + } + + @Override + public boolean remove(Object o) { + ((Marker) o).setRemovalState(true); + return true; + } + + @Override + public boolean removeAll(Collection c) { + if (c != null) { + for (Object o : c) { + ((Marker) o).setRemovalState(true); + } + } + + int size = size(); + int insertAt = 0; + + for (int i = 0; i < size; i++) { + T el = get(i); + + if (el != null && !el.isToBeRemoved()) { + set(insertAt++, el); + } + } + subList(insertAt, size).clear(); + + return size() != size; + } + + public interface Marker { + boolean isToBeRemoved(); + void setRemovalState(boolean removalState); + } +} -- 2.6.2