From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: nostalgic853 Date: Fri, 18 Nov 2022 23:39:13 +0800 Subject: [PATCH] Use a faster random implementation diff --git a/src/main/java/cc/keyimc/keyi/utils/FastRandom.java b/src/main/java/cc/keyimc/keyi/utils/FastRandom.java new file mode 100644 index 0000000000000000000000000000000000000000..a9b3a467e7dbfdc78c11f04e6bb5a9642c20470c --- /dev/null +++ b/src/main/java/cc/keyimc/keyi/utils/FastRandom.java @@ -0,0 +1,395 @@ +/** + * This is a faster implementation of java.util.Random. + * Code from https://gist.github.com/Xyene/4637619 + * Licensed under GNU Lesser General Public License v3.0 + *

+ * A random number generator based on the simple and fast xor-shift pseudo + * random number generator (RNG) specified in: + * Marsaglia, George. (2003). Xorshift RNGs. + * http://www.jstatsoft.org/v08/i14/xorshift.pdf + * Translated from: + * http://www.codeproject.com/Articles/9187/A-fast-equivalent-for-System-Random. + */ + +package cc.keyimc.keyi.utils; + +@SuppressWarnings("SuspiciousNameCombination") +public class FastRandom extends java.util.Random { + final double REAL_UNIT_INT = 1.0 / (0x7FFFFFFFL); + final double REAL_UNIT_UINT = 1.0 / (0xFFFFFFFFL); + final long Y = 842502087L, Z = 3579807591L, W = 273326509L; + long x, y, z, w; + + public FastRandom() { + seed((int) System.currentTimeMillis()); + } + + @Override + public void setSeed(long seed) { + seed((int) seed); + } + + public void seed(int seed) { + // The only stipulation stated for the xorshift RNG is that at least one of + // the seeds x,y,z,w is non-zero. We fulfill that requirement by only allowing + // resetting of the x seed + x = seed; + y = Y; + z = Z; + w = W; + } + + long boolBuffer; + int boolBufferBits = 0; + + @Override + public boolean nextBoolean() { + if (boolBufferBits == 0) { + boolBuffer = nextUInt(); + boolBufferBits = 32; + } + boolBuffer >>= 1; + boolean bit = (boolBuffer & 1) == 0; + --boolBufferBits; + return bit; + } + + @Override + public void nextBytes(byte[] buffer) { + // Fill up the bulk of the buffer in chunks of 4 bytes at a time. + long x = this.x, y = this.y, z = this.z, w = this.w; + int i = 0; + long t; + for (int bound = buffer.length - 3; i < bound; ) { + // Generate 4 bytes. + // Increased performance is achieved by generating 4 random bytes per loop. + // Also note that no mask needs to be applied to zero out the higher order bytes before + // casting because the cast ignores thos bytes. Thanks to Stefan Trosché»·z for pointing this out. + t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); + + buffer[i++] = (byte) w; + buffer[i++] = (byte) (w >> 8); + buffer[i++] = (byte) (w >> 16); + buffer[i++] = (byte) (w >> 24); + } + + // Fill up any remaining bytes in the buffer. + if (i < buffer.length) { + // Generate 4 bytes. + t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); + + buffer[i++] = (byte) w; + if (i < buffer.length) { + buffer[i++] = (byte) (w >> 8); + if (i < buffer.length) { + buffer[i++] = (byte) (w >> 16); + if (i < buffer.length) { + buffer[i] = (byte) (w >> 24); + } + } + } + } + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + @Override + public double nextDouble() { + long t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + + // Here we can gain a 2x speed improvement by generating a value that can be cast to + // an int instead of the more easily available uint. If we then explicitly cast to an + // int the compiler will then cast the int to a double to perform the multiplication, + // this final cast is a lot faster than casting from a uint to a double. The extra cast + // to an int is very fast (the allocated bits remain the same) and so the overall effect + // of the extra cast is a significant performance improvement. + // + // Also note that the loss of one bit of precision is equivalent to what occurs within + // System.Random. + return (REAL_UNIT_INT * (int) (0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))); + } + + public double random() { + return nextDouble(); + } + + @Override + public float nextFloat() { + return (float) nextDouble(); + } + + @Override + public int nextInt() { + long t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + return (int) (0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))); + } + + @Override + public int nextInt(int upperBound) { + if (upperBound < 0) + throw new IllegalArgumentException("upperBound must be >=0"); + + long t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + + return (int) ((REAL_UNIT_INT * (int) (0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * upperBound); + } + + public int nextInt(int lowerBound, int upperBound) { + if (lowerBound > upperBound) + throw new IllegalArgumentException("upperBound must be >=lowerBound"); + + long t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + + // The explicit int cast before the first multiplication gives better performance. + // See comments in NextDouble. + int range = upperBound - lowerBound; + if (range < 0) { + // If range is <0 then an overflow has occured and must resort to using long integer arithmetic instead (slower). + // We also must use all 32 bits of precision, instead of the normal 31, which again is slower. + return lowerBound + (int) ((REAL_UNIT_UINT * (double) (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))) * (double) ((long) upperBound - (long) lowerBound)); + } + // 31 bits of precision will suffice if range<=int.MaxValue. This allows us to cast to an int and gain + // a little more performance. + return lowerBound + (int) ((REAL_UNIT_INT * (double) (int) (0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * (double) range); + } + + public long nextUInt() { + long t = (x ^ (x << 11)); + x = y; + y = z; + z = w; + return (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))) & (0xFFFFFFFFL); + } + + @Override + public long nextLong() { + return nextUInt() << 32 + nextUInt(); + } + + double gaussNext; + boolean hasGaussNext; + final double TWOPI = Math.PI * 2; + + /** + * Get a random number in the range [min, max) or [min, max] depending on rounding. + * + * @param min Low bound + * @param max High bound + * @return A uniformly distributed double + */ + public double uniform(double min, double max) { + return min + (max - min) * nextDouble(); + } + + /** + * Triangular distribution. + *

+ * Continuous distribution bounded by given lower and upper limits, + * and having a given mode value in-between. + * http://en.wikipedia.org/wiki/Triangular_distribution + * + * @param low Low bound + * @param high High bound + * @param mode Mode + * @return A number from the triangular distribution specified + */ + public double triangular(int low, int high, int mode) { + double u = nextDouble(); + double c = (mode - low) / (high - low); + if (u > c) { + u = 1.0 - u; + c = 1.0 - c; + int k = low; + low = high; + high = k; + } + return low + (high - low) * Math.sqrt(u * c); + } + + /** + * Gaussian distribution, mean is 0 and standard deviation is 1. + *

+ * mu is the mean, and sigma is the standard deviation. + * + * @return A double in Gaussian distribution + */ + public double gauss() { + return nextGaussian(); + } + + /** + * Gaussian distribution, with user-specified mean and standard deviation. + *

+ * mu is the mean, and sigma is the standard deviation. + * + * @return A double in Gaussian distribution + */ + public double gauss(double mu, double sigma) { + return mu + sigma * nextGaussian(); + } + + public double gaussUnsigned(double mu, double sigma) { + double out = gauss(mu, sigma); + return out > 1 ? out : 1; + } + + /** + * Log normal distribution. + *

+ * If you take the natural logarithm of this distribution, you'll get a + * normal distribution with mean mu and standard deviation sigma. + * mu can have any value, and sigma must be greater than zero. + * + * @param mu Mean + * @param sigma Standard deviation + * @return A number from the log normal distribution specified + */ + public double logNormal(double mu, double sigma) { + return Math.exp(gauss(mu, sigma)); + } + + /** + * Exponential distribution. + *

+ * lambda is 1.0 divided by the desired mean. It should be + * nonzero. Returned values range from 0 to positive infinity + * if lambda is positive, and from negative infinity to 0 + * if lambda is negative. + * + * @param lambda A non-zero value + */ + public double exponential(double lambda) { + return -Math.log(1.0 - random()) / lambda; + } + + /** + * Circular data distribution. + *

+ * If kappa is equal to zero, this distribution reduces + * to a uniform random angle over the range 0 to 2*pi. + * + * @param mu the mean angle, expressed in radians between 0 and 2*pi. + * @param kappa the concentration parameter, which must be greater than or + * equal to zero. + * @return A number from the circular data distribution specified + */ + public double circularData(double mu, double kappa) { + if (kappa <= 1e-6) + return TWOPI * nextDouble(); + + double a = 1.0 + Math.sqrt(1.0 + 4.0 * kappa * kappa); + double b = (a - Math.sqrt(2.0 * a)) / (2.0 * kappa); + double r = (1.0 + b * b) / (2.0 * b); + double u1, u2, u3, f, c, z, theta = 0; + + while (true) { + u1 = nextDouble(); + + z = Math.cos(Math.PI * u1); + f = (1.0 + r * z) / (r + z); + c = kappa * (r - f); + + u2 = nextDouble(); + + if (u2 < c * (2.0 - c) || u2 <= c * Math.exp(1.0 - c)) + break; + + u3 = nextDouble(); + if (u3 > 0.5) + theta = (mu % TWOPI) + Math.acos(f); + else + theta = (mu % TWOPI) - Math.acos(f); + } + return theta; + } + + + final double LOG4 = Math.log(4); + final double SG_MAGICCONST = 1.0 + Math.log(4.5); + + /** + * Gamma distribution. Not the gamma function! + * Conditions on the parameters are alpha > 0 and beta > 0. + *

+ * The probability distribution function is: + * pdf(x) = (x ** (alpha - 1) * math.exp(-x / beta)) / (math.gamma(alpha) * beta ** alpha) + * + * @param alpha Alpha + * @param beta Beta + * @return A number from the gamma distribution specified + */ + public double gamma(double alpha, double beta) { + if (alpha <= 0.0 || beta <= 0.0) + throw new IllegalArgumentException("alpha and beta must be > 0.0"); + + if (alpha > 1.0) { + double ainv = Math.sqrt(2.0 * alpha - 1.0); + double bbb = alpha - LOG4; + double ccc = alpha + ainv; + double u1, u2, v, x, z, r; + + while (true) { + u1 = random(); + if (!(1e-7 < u1 && u1 < .9999999)) + continue; + u2 = 1.0 - random(); + v = Math.log(u1 / (1.0 - u1)) / ainv; + x = alpha * Math.exp(v); + z = u1 * u1 * u2; + r = bbb + ccc * v - x; + if (r + SG_MAGICCONST - 4.5 * z >= 0.0 || r >= Math.log(z)) + return x * beta; + } + } else if (alpha == 1.0) { + // exponential(1) + double u; + u = random(); + while (u <= 1e-7) + u = random(); + return -Math.log(u) * beta; + } else { + // alpha is between 0 and 1 (exclusive) + // Uses ALGORITHM GS of Statistical Computing -Kennedy & Gentle + + double u, b, p, x, u1; + while (true) { + u = random(); + b = (Math.E + alpha) / Math.E; + p = b * u; + if (p <= 1.0) + x = Math.pow(p, (1.0 / alpha)); + else + x = -Math.log((b - p) / alpha); + u1 = random(); + if (p > 1.0) { + if (u1 <= Math.pow(x, (alpha - 1.0))) + break; + } else if (u1 <= Math.exp(-x)) + break; + } + return x * beta; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java index e5ea9f27a1936ed9e329e74317c91c5df89b9fbd..89a41d396162a1c2eb2df5192b0d888b08fec6ec 100644 --- a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java +++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java @@ -10,10 +10,11 @@ import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.UUID; +import cc.keyimc.keyi.utils.FastRandom; // KeYi public class PaperLootableInventoryData { - private static final Random RANDOM = new Random(); + private static final Random RANDOM = new FastRandom(); // KeYi - use a faster random private long lastFill = -1; private long nextRefill = -1; diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java index 79bf9c277fe98df176113de39360fb34ad917577..9168e70d7d478254ad45e913885d79391a2338ae 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -53,6 +53,10 @@ import net.minecraft.world.level.material.Fluids; import net.minecraft.world.ticks.LevelChunkTicks; import net.minecraft.world.ticks.TickContainerAccess; import org.slf4j.Logger; +// KeYi start +import cc.keyimc.keyi.utils.FastRandom; +import java.util.Random; // KeYi +// KeYi end public class LevelChunk extends ChunkAccess { @@ -932,7 +936,7 @@ public class LevelChunk extends ChunkAccess { if (this.needsDecoration) { //try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper // Purpur this.needsDecoration = false; - java.util.Random random = new java.util.Random(); + Random random = new FastRandom(); random.setSeed(this.level.getSeed()); long xRand = random.nextLong() / 2L * 2L + 1L; long zRand = random.nextLong() / 2L * 2L + 1L; diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java index dcfe090c269d4cbcc2eb1b6f85392848bb34656c..ef8909c9c13c8f46ec6d452f0d14c9a1119e0edf 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java @@ -26,6 +26,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; import net.minecraft.world.level.ChunkPos; import org.slf4j.Logger; +import cc.keyimc.keyi.utils.FastRandom; // KeYi public class RegionFile implements AutoCloseable { @@ -109,7 +110,7 @@ public class RegionFile implements AutoCloseable { } private void backupRegionFile() { - Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new java.util.Random().nextLong() + ".backup"); + Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new FastRandom().nextLong() + ".backup"); // KeYi - use a faster random this.backupRegionFile(backup); } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 804f81c478c09a5b6d0b10a78dc2b7ae0b88cc18..278bda4058462ac331b67bc9ea297a30b296ce11 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -134,6 +134,7 @@ import org.bukkit.util.Consumer; import org.bukkit.util.RayTraceResult; import org.bukkit.util.StructureSearchResult; import org.bukkit.util.Vector; +import cc.keyimc.keyi.utils.FastRandom; // KeYi public class CraftWorld extends CraftRegionAccessor implements World { public static final int CUSTOM_DIMENSION_OFFSET = 10; @@ -226,7 +227,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } // Paper end - private static final Random rand = new Random(); + private static final Random rand = new FastRandom(); // KeYi - use a faster random public CraftWorld(ServerLevel world, ChunkGenerator gen, BiomeProvider biomeProvider, Environment env) { this.world = world; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java index d1c7ab67cba881d96b7a5e9220130d86d0514304..309d1ce15aace9da4a84819e8511af2cc12b1ea8 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java @@ -12,10 +12,11 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.Firework; import org.bukkit.entity.LivingEntity; import org.bukkit.inventory.meta.FireworkMeta; +import cc.keyimc.keyi.utils.FastRandom; // KeYi public class CraftFirework extends CraftProjectile implements Firework { - private final Random random = new Random(); + private final Random random = new FastRandom(); // KeYi - use a faster random //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item. public CraftFirework(CraftServer server, FireworkRocketEntity entity) { diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java index a9673a804d597599c35c83f4f245510c83005328..08d47eee550e2e9ddfb4dc0ce12945401b16c23c 100644 --- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java +++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java @@ -42,13 +42,14 @@ import org.bukkit.craftbukkit.util.RandomSourceWrapper; import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator.BiomeGrid; import org.bukkit.generator.ChunkGenerator.ChunkData; +import cc.keyimc.keyi.utils.FastRandom; // KeYi public class CustomChunkGenerator extends InternalChunkGenerator { private final net.minecraft.world.level.chunk.ChunkGenerator delegate; private final ChunkGenerator generator; private final ServerLevel world; - private final Random random = new Random(); + private final Random random = new FastRandom(); // KeYi - use a faster random private boolean newApi; private boolean implementBaseHeight = true;