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.
This commit is contained in:
Spottedleaf 2020-04-11 20:15:22 -04:00
parent 6ad6ec1ac9
commit f654174059
2 changed files with 491 additions and 3 deletions

View file

@ -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 +0,0 @@
+package com.destroystokyo.paper.util.math;
+
+/**
+ * @author Spottedleaf <Spottedleaf@users.noreply.github.com>
+ */
+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,7 +1776,7 @@ 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 +0,0 @@
@ -1548,7 +1784,9 @@ index 0000000000..4d74a7a908
+
+import java.util.Collection;
+
+// containing utils to work on small numbers of enums
+/**
+ * @author Spottedleaf <Spottedleaf@users.noreply.github.com>
+ */
+public final class OptimizedSmallEnumSet<E extends Enum<E>> {
+
+ private final Class<E> 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
@@ -0,0 +0,0 @@ import com.mojang.datafixers.DataFixUtils;

View file

@ -0,0 +1,250 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
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
@@ -0,0 +0,0 @@
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<PathfinderGoal.Type> a = EnumSet.noneOf(PathfinderGoal.Type.class);
+ private final OptimizedSmallEnumSet<Type> goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Paper - remove streams from pathfindergoalselector
public PathfinderGoal() {}
@@ -0,0 +0,0 @@ public abstract class PathfinderGoal {
public void e() {}
public void a(EnumSet<PathfinderGoal.Type> 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<PathfinderGoal.Type> i() {
- return this.a;
+ // Paper start - remove streams from pathfindergoalselector
+ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<PathfinderGoal.Type> 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
@@ -0,0 +0,0 @@
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;
@@ -0,0 +0,0 @@ public class PathfinderGoalSelector {
private final Map<PathfinderGoal.Type, PathfinderGoalWrapped> c = new EnumMap(PathfinderGoal.Type.class);
private final Set<PathfinderGoalWrapped> d = Sets.newLinkedHashSet();private Set<PathfinderGoalWrapped> getTasks() { return d; }// Paper - OBFHELPER
private final GameProfilerFiller e;
- private final EnumSet<PathfinderGoal.Type> f = EnumSet.noneOf(PathfinderGoal.Type.class);
+ private final OptimizedSmallEnumSet<PathfinderGoal.Type> 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
@@ -0,0 +0,0 @@ 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<PathfinderGoalWrapped> 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<PathfinderGoalWrapped> 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);
@@ -0,0 +0,0 @@ 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<PathfinderGoalWrapped> iterator = this.d.iterator(); iterator.hasNext();) {
+ PathfinderGoalWrapped wrappedGoal = iterator.next();
+ if (wrappedGoal.g()) {
+ continue;
+ }
+
+ OptimizedSmallEnumSet<PathfinderGoal.Type> 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<PathfinderGoalWrapped> iterator = this.d.iterator(); iterator.hasNext();) {
+ PathfinderGoalWrapped wrappedGoal = iterator.next();
+ if (wrappedGoal.g()) {
+ wrappedGoal.e();
+ }
+ }
+ // Paper end - remove streams from pathfindergoalselector
this.e.exit();
}
@@ -0,0 +0,0 @@ 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
@@ -0,0 +0,0 @@ public class PathfinderGoalWrapped extends PathfinderGoal {
this.a.a(enumset);
}
- @Override
- public EnumSet<PathfinderGoal.Type> i() {
- return this.a.i();
+ // Paper start - remove streams from pathfindergoalselector
+ public com.destroystokyo.paper.util.set.OptimizedSmallEnumSet<PathfinderGoal.Type> getGoalTypes() {
+ return this.a.getGoalTypes();
+ // Paper end - remove streams from pathfindergoalselector
}
public boolean isRunning() { return this.g(); } // Paper - OBFHELPER
--