From 09a942155fa31bbaa1807afda3ca2893ec9d7a79 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sat, 11 Apr 2020 20:15:22 -0400 Subject: [PATCH] Remove streams from Mob AI System The streams hurt performance and allocate tons of garbage, so replace them with the standard iterator. Also optimise the stream.anyMatch statement to move to a bitset where we can replace the call with a single bitwise operation. --- Spigot-Server-Patches/0004-MC-Utils.patch | 248 ++++++++++++++++- ...67-Remove-streams-from-Mob-AI-System.patch | 252 ++++++++++++++++++ 2 files changed, 495 insertions(+), 5 deletions(-) create mode 100644 Spigot-Server-Patches/0467-Remove-streams-from-Mob-AI-System.patch diff --git a/Spigot-Server-Patches/0004-MC-Utils.patch b/Spigot-Server-Patches/0004-MC-Utils.patch index d4a9460d9..5684a4e73 100644 --- a/Spigot-Server-Patches/0004-MC-Utils.patch +++ b/Spigot-Server-Patches/0004-MC-Utils.patch @@ -1,4 +1,4 @@ -From c9458dab207ad176a4dc2eb7ca23055d54c0b22c Mon Sep 17 00:00:00 2001 +From 75ae35c120230e8fbc4a20f7472276d0f398e21a Mon Sep 17 00:00:00 2001 From: Aikar Date: Mon, 28 Mar 2016 20:55:47 -0400 Subject: [PATCH] MC Utils @@ -815,6 +815,242 @@ index 0000000000..c2f7e4ca0f + return this.map.values().iterator(); + } +} +diff --git a/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java +new file mode 100644 +index 0000000000..c3b936f54b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/math/IntegerUtil.java +@@ -0,0 +1,230 @@ ++package com.destroystokyo.paper.util.math; ++ ++/** ++ * @author Spottedleaf ++ */ ++public final class IntegerUtil { ++ ++ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; ++ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; ++ ++ public static int ceilLog2(final int value) { ++ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static long ceilLog2(final long value) { ++ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final int value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int floorLog2(final long value) { ++ // xor is optimized subtract for 2^n -1 ++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) ++ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros ++ } ++ ++ public static int roundCeilLog2(final int value) { ++ // optimized variant of 1 << (32 - leading(val - 1)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) ++ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) ++ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) ++ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static long roundCeilLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); ++ } ++ ++ public static int roundFloorLog2(final int value) { ++ // optimized variant of 1 << (31 - leading(val)) ++ // given ++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) ++ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - (31 - leading(val))) ++ // HIGH_BIT_32 >> (31 - 31 + leading(val)) ++ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); ++ } ++ ++ public static long roundFloorLog2(final long value) { ++ // see logic documented above ++ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); ++ } ++ ++ public static boolean isPowerOfTwo(final int n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ public static boolean isPowerOfTwo(final long n) { ++ // 2^n has one bit ++ // note: this rets true for 0 still ++ return IntegerUtil.getTrailingBit(n) == n; ++ } ++ ++ ++ public static int getTrailingBit(final int n) { ++ return -n & n; ++ } ++ ++ public static long getTrailingBit(final long n) { ++ return -n & n; ++ } ++ ++ public static int trailingZeros(final int n) { ++ return Integer.numberOfTrailingZeros(n); ++ } ++ ++ public static long trailingZeros(final long n) { ++ return Long.numberOfTrailingZeros(n); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorMultiple(final long numbers) { ++ return (int)(numbers >>> 32); ++ } ++ ++ // from hacker's delight (signed division magic value) ++ public static int getDivisorShift(final long numbers) { ++ return (int)numbers; ++ } ++ ++ // copied from hacker's delight (signed division magic value) ++ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt ++ public static long getDivisorNumbers(final int d) { ++ final int ad = IntegerUtil.branchlessAbs(d); ++ ++ if (ad < 2) { ++ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); ++ } ++ ++ final int two31 = 0x80000000; ++ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour ++ ++ int p = 31; ++ ++ // all these variables are UNSIGNED! ++ int t = two31 + (d >>> 31); ++ int anc = t - 1 - t%ad; ++ int q1 = (int)((two31 & mask)/(anc & mask)); ++ int r1 = two31 - q1*anc; ++ int q2 = (int)((two31 & mask)/(ad & mask)); ++ int r2 = two31 - q2*ad; ++ int delta; ++ ++ do { ++ p = p + 1; ++ q1 = 2*q1; // Update q1 = 2**p/|nc|. ++ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). ++ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) ++ q1 = q1 + 1; ++ r1 = r1 - anc; ++ } ++ q2 = 2*q2; // Update q2 = 2**p/|d|. ++ r2 = 2*r2; // Update r2 = rem(2**p, |d|). ++ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) ++ q2 = q2 + 1; ++ r2 = r2 - ad; ++ } ++ delta = ad - r2; ++ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); ++ ++ int magicNum = q2 + 1; ++ if (d < 0) { ++ magicNum = -magicNum; ++ } ++ int shift = p - 32; ++ return ((long)magicNum << 32) | shift; ++ } ++ ++ public static int branchlessAbs(final int val) { ++ // -n = -1 ^ n + 1 ++ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ public static long branchlessAbs(final long val) { ++ // -n = -1 ^ n + 1 ++ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 ++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 ++ } ++ ++ //https://github.com/skeeto/hash-prospector for hash functions ++ ++ //score = ~590.47984224483832 ++ public static int hash0(int x) { ++ x *= 0x36935555; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ //score = ~310.01596637036749 ++ public static int hash1(int x) { ++ x ^= x >>> 15; ++ x *= 0x356aaaad; ++ x ^= x >>> 17; ++ return x; ++ } ++ ++ public static int hash2(int x) { ++ x ^= x >>> 16; ++ x *= 0x7feb352d; ++ x ^= x >>> 15; ++ x *= 0x846ca68b; ++ x ^= x >>> 16; ++ return x; ++ } ++ ++ public static int hash3(int x) { ++ x ^= x >>> 17; ++ x *= 0xed5ad4bb; ++ x ^= x >>> 11; ++ x *= 0xac4c1b51; ++ x ^= x >>> 15; ++ x *= 0x31848bab; ++ x ^= x >>> 14; ++ return x; ++ } ++ ++ //score = ~365.79959673201887 ++ public static long hash1(long x) { ++ x ^= x >>> 27; ++ x *= 0xb24924b71d2d354bL; ++ x ^= x >>> 28; ++ return x; ++ } ++ ++ //h2 hash ++ public static long hash2(long x) { ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ x *= 0xd6e8feb86659fd93L; ++ x ^= x >>> 32; ++ return x; ++ } ++ ++ public static long hash3(long x) { ++ x ^= x >>> 45; ++ x *= 0xc161abe5704b6c79L; ++ x ^= x >>> 41; ++ x *= 0xe3e5389aedbc90f7L; ++ x ^= x >>> 56; ++ x *= 0x1f9aba75a52db073L; ++ x ^= x >>> 53; ++ return x; ++ } ++ ++ private IntegerUtil() { ++ throw new RuntimeException(); ++ } ++} diff --git a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java new file mode 100644 index 0000000000..5a44bc644b @@ -1540,15 +1776,17 @@ index 0000000000..5f2d88797d +} diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java new file mode 100644 -index 0000000000..4d74a7a908 +index 0000000000..9df0006c1a --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java -@@ -0,0 +1,65 @@ +@@ -0,0 +1,67 @@ +package com.destroystokyo.paper.util.set; + +import java.util.Collection; + -+// containing utils to work on small numbers of enums ++/** ++ * @author Spottedleaf ++ */ +public final class OptimizedSmallEnumSet> { + + private final Class enumClass; @@ -2145,7 +2383,7 @@ index ce48210922..57ce9bde64 100644 this.displayName = this.getName(); this.canPickUpLoot = true; diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java -index f937b72945..cbf0c2f25d 100644 +index 29e776ca19..4328273b1f 100644 --- a/src/main/java/net/minecraft/server/EntityTypes.java +++ b/src/main/java/net/minecraft/server/EntityTypes.java @@ -4,6 +4,7 @@ import com.mojang.datafixers.DataFixUtils; diff --git a/Spigot-Server-Patches/0467-Remove-streams-from-Mob-AI-System.patch b/Spigot-Server-Patches/0467-Remove-streams-from-Mob-AI-System.patch new file mode 100644 index 000000000..ec9860540 --- /dev/null +++ b/Spigot-Server-Patches/0467-Remove-streams-from-Mob-AI-System.patch @@ -0,0 +1,252 @@ +From 31cd409fd68946bc43008a237ff6627c905d6633 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Mon, 6 Apr 2020 17:53:29 -0700 +Subject: [PATCH] Remove streams from Mob AI System + +The streams hurt performance and allocate tons of garbage, so +replace them with the standard iterator. + +Also optimise the stream.anyMatch statement to move to a bitset +where we can replace the call with a single bitwise operation. + +diff --git a/src/main/java/net/minecraft/server/PathfinderGoal.java b/src/main/java/net/minecraft/server/PathfinderGoal.java +index bdb90a3466..134f7d0013 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoal.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoal.java +@@ -1,10 +1,11 @@ + package net.minecraft.server; + ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; // Paper - remove streams from pathfindergoalselector + import java.util.EnumSet; + + public abstract class PathfinderGoal { + +- private final EnumSet a = EnumSet.noneOf(PathfinderGoal.Type.class); ++ private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Paper - remove streams from pathfindergoalselector + + public PathfinderGoal() {} + +@@ -28,16 +29,20 @@ public abstract class PathfinderGoal { + public void e() {} + + public void a(EnumSet enumset) { +- this.a.clear(); +- this.a.addAll(enumset); ++ // Paper start - remove streams from pathfindergoalselector ++ this.goalTypes.clear(); ++ this.goalTypes.addAllUnchecked(enumset); ++ // Paper end - remove streams from pathfindergoalselector + } + + public String toString() { + return this.getClass().getSimpleName(); + } + +- public EnumSet i() { +- return this.a; ++ // Paper start - remove streams from pathfindergoalselector ++ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getGoalTypes() { ++ return this.goalTypes; ++ // Paper end - remove streams from pathfindergoalselector + } + + public static enum Type { +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalSelector.java b/src/main/java/net/minecraft/server/PathfinderGoalSelector.java +index 935136771e..90319909e5 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalSelector.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalSelector.java +@@ -1,8 +1,10 @@ + package net.minecraft.server; + ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; // Paper - remove streams from pathfindergoalselector + import com.google.common.collect.Sets; + import java.util.EnumMap; + import java.util.EnumSet; ++import java.util.Iterator; // Paper - remove streams from pathfindergoalselector + import java.util.Map; + import java.util.Set; + import java.util.stream.Stream; +@@ -26,7 +28,7 @@ public class PathfinderGoalSelector { + private final Map c = new EnumMap(PathfinderGoal.Type.class); + private final Set d = Sets.newLinkedHashSet();private Set getTasks() { return d; }// Paper - OBFHELPER + private final GameProfilerFiller e; +- private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); ++ private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Paper - remove streams from pathfindergoalselector + private int g = 3;private int getTickRate() { return g; } // Paper - OBFHELPER + private int curRate;private int getCurRate() { return curRate; } private void incRate() { this.curRate++; } // Paper TODO + +@@ -58,33 +60,36 @@ public class PathfinderGoalSelector { + // Paper end + + public void a(PathfinderGoal pathfindergoal) { +- this.d.stream().filter((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.j() == pathfindergoal; +- }).filter(PathfinderGoalWrapped::g).forEach(PathfinderGoalWrapped::d); +- this.d.removeIf((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.j() == pathfindergoal; +- }); ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped goalWrapped = iterator.next(); ++ if (goalWrapped.j() != pathfindergoal) { ++ continue; ++ } ++ if (goalWrapped.g()) { ++ goalWrapped.d(); ++ } ++ iterator.remove(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + } + ++ private static final PathfinderGoal.Type[] PATHFINDER_GOAL_TYPES = PathfinderGoal.Type.values(); // Paper - remove streams from pathfindergoalselector ++ + public void doTick() { + this.e.enter("goalCleanup"); +- this.c().filter((pathfindergoalwrapped) -> { +- boolean flag; +- +- if (pathfindergoalwrapped.g()) { +- Stream stream = pathfindergoalwrapped.i().stream(); +- EnumSet enumset = this.f; +- +- this.f.getClass(); +- if (!stream.anyMatch(enumset::contains) && pathfindergoalwrapped.b()) { +- flag = false; +- return flag; +- } ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iterator.next(); ++ if (!wrappedGoal.g()) { ++ continue; + } +- +- flag = true; +- return flag; +- }).forEach(PathfinderGoal::d); ++ if (!this.goalTypes.hasCommonElements(wrappedGoal.getGoalTypes()) && wrappedGoal.b()) { ++ continue; ++ } ++ wrappedGoal.d(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + this.c.forEach((pathfindergoal_type, pathfindergoalwrapped) -> { + if (!pathfindergoalwrapped.g()) { + this.c.remove(pathfindergoal_type); +@@ -93,30 +98,58 @@ public class PathfinderGoalSelector { + }); + this.e.exit(); + this.e.enter("goalUpdate"); +- this.d.stream().filter((pathfindergoalwrapped) -> { +- return !pathfindergoalwrapped.g(); +- }).filter((pathfindergoalwrapped) -> { +- Stream stream = pathfindergoalwrapped.i().stream(); +- EnumSet enumset = this.f; +- +- this.f.getClass(); +- return stream.noneMatch(enumset::contains); +- }).filter((pathfindergoalwrapped) -> { +- return pathfindergoalwrapped.i().stream().allMatch((pathfindergoal_type) -> { +- return ((PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b)).a(pathfindergoalwrapped); +- }); +- }).filter(PathfinderGoalWrapped::a).forEach((pathfindergoalwrapped) -> { +- pathfindergoalwrapped.i().forEach((pathfindergoal_type) -> { +- PathfinderGoalWrapped pathfindergoalwrapped1 = (PathfinderGoalWrapped) this.c.getOrDefault(pathfindergoal_type, PathfinderGoalSelector.b); +- +- pathfindergoalwrapped1.d(); +- this.c.put(pathfindergoal_type, pathfindergoalwrapped); +- }); +- pathfindergoalwrapped.c(); +- }); ++ // Paper start - remove streams from pathfindergoalselector ++ goal_update_loop: for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iterator.next(); ++ if (wrappedGoal.g()) { ++ continue; ++ } ++ ++ OptimizedSmallEnumSet wrappedGoalSet = wrappedGoal.getGoalTypes(); ++ ++ if (this.goalTypes.hasCommonElements(wrappedGoalSet)) { ++ continue; ++ } ++ ++ long iterator1 = wrappedGoalSet.getBackingSet(); ++ int wrappedGoalSize = wrappedGoalSet.size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ PathfinderGoal.Type type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; ++ iterator1 ^= com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(iterator1); ++ PathfinderGoalWrapped wrapped = this.c.getOrDefault(type, PathfinderGoalSelector.b); ++ if (!wrapped.a(wrappedGoal)) { ++ continue goal_update_loop; ++ } ++ } ++ ++ if (!wrappedGoal.a()) { ++ continue; ++ } ++ ++ iterator1 = wrappedGoalSet.getBackingSet(); ++ wrappedGoalSize = wrappedGoalSet.size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ PathfinderGoal.Type type = PATHFINDER_GOAL_TYPES[Long.numberOfTrailingZeros(iterator1)]; ++ iterator1 ^= com.destroystokyo.paper.util.math.IntegerUtil.getTrailingBit(iterator1); ++ PathfinderGoalWrapped wrapped = this.c.getOrDefault(type, PathfinderGoalSelector.b); ++ ++ wrapped.d(); ++ this.c.put(type, wrappedGoal); ++ } ++ ++ wrappedGoal.c(); ++ } ++ // Paper end - remove streams from pathfindergoalselector + this.e.exit(); + this.e.enter("goalTick"); +- this.c().forEach(PathfinderGoalWrapped::e); ++ // Paper start - remove streams from pathfindergoalselector ++ for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { ++ PathfinderGoalWrapped wrappedGoal = iterator.next(); ++ if (wrappedGoal.g()) { ++ wrappedGoal.e(); ++ } ++ } ++ // Paper end - remove streams from pathfindergoalselector + this.e.exit(); + } + +@@ -125,11 +158,11 @@ public class PathfinderGoalSelector { + } + + public void a(PathfinderGoal.Type pathfindergoal_type) { +- this.f.add(pathfindergoal_type); ++ this.goalTypes.addUnchecked(pathfindergoal_type); // Paper - remove streams from pathfindergoalselector + } + + public void b(PathfinderGoal.Type pathfindergoal_type) { +- this.f.remove(pathfindergoal_type); ++ this.goalTypes.removeUnchecked(pathfindergoal_type); // Paper - remove streams from pathfindergoalselector + } + + public void a(PathfinderGoal.Type pathfindergoal_type, boolean flag) { +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java b/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java +index 29657fed75..1b800c558f 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java +@@ -59,9 +59,10 @@ public class PathfinderGoalWrapped extends PathfinderGoal { + this.a.a(enumset); + } + +- @Override +- public EnumSet i() { +- return this.a.i(); ++ // Paper start - remove streams from pathfindergoalselector ++ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet getGoalTypes() { ++ return this.a.getGoalTypes(); ++ // Paper end - remove streams from pathfindergoalselector + } + + public boolean isRunning() { return this.g(); } // Paper - OBFHELPER +-- +2.25.1 +