LightRNG for better entity performance

This commit is contained in:
Sotr
2018-07-17 00:36:36 +08:00
parent cede9c99fa
commit 5f917f8253
6 changed files with 3317 additions and 1 deletions

View File

@@ -0,0 +1,240 @@
/*
Written in 2015 by Sebastiano Vigna (vigna@acm.org)
To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.
See <http://creativecommons.org/publicdomain/zero/1.0/>. */
package io.akarin.api.internal.utils.random;
/**
* This is a SplittableRandom-style generator, meant to have a tiny state
* that permits storing many different generators with low overhead.
* It should be rather fast, though no guarantees can be made on all hardware.
* <br>
* Benchmarking on a Windows laptop with an i7-4700MQ processor running OpenJDK 8
* reports generation of 64-bit random long output as 17.8x faster than generating
* an equivalent number of random longs with java.util.Random, and generation of
* 32-bit random int output as 9.8x faster. Specifically, generating 1 billion longs
* took about 1.28 nanoseconds per long (1.277 seconds for the whole group) with
* LightRNG, while java.util.Random (which is meant to produce int, to be fair) took
* about 22.8 nanoseconds per long (22.797 seconds for the whole group). XorRNG
* appears to be occasionally faster on int output than LightRNG, but it isn't clear
* why or what causes that (JIT or GC internals, possibly). XorRNG is slightly
* slower at generating 64-bit random data, including long and double, but not by
* a significant degree (a multiplier between 0.9 and 1.2 times). The only deciding
* factor then is state size, where LightRNG is as small as possible for any JVM
* object with even a single field: 16 bytes (on a 64-bit JVM; 8-byte objects with
* 4 bytes or less of non-static members may be possible on 32-bit JVMs but I can't
* find anything confirming that guess).
* <br>
* So yes, this should be very fast, and with only a single long used per LightRNG,
* it is about as memory-efficient as these generators get.
* <br>
* Written in 2015 by Sebastiano Vigna (vigna@acm.org)
* @author Sebastiano Vigna
* @author Tommy Ettinger
*/
public class LightRNG implements RandomnessSource, StatefulRandomness
{
/** 2 raised to the 53, - 1. */
private static final long DOUBLE_MASK = ( 1L << 53 ) - 1;
/** 2 raised to the -53. */
private static final double NORM_53 = 1. / ( 1L << 53 );
/** 2 raised to the 24, -1. */
private static final long FLOAT_MASK = ( 1L << 24 ) - 1;
/** 2 raised to the -24. */
private static final double NORM_24 = 1. / ( 1L << 24 );
private static final long serialVersionUID = -374415589203474497L;
public long state; /* The state can be seeded with any value. */
/** Creates a new generator seeded using Math.random. */
public LightRNG() {
this((long) Math.floor(Math.random() * Long.MAX_VALUE));
}
public LightRNG( final long seed ) {
setSeed(seed);
}
@Override
public int next( int bits ) {
return (int)( nextLong() & ( 1L << bits ) - 1 );
}
/**
* Can return any long, positive or negative, of any size permissible in a 64-bit signed integer.
* @return any long, all 64 bits are random
*/
@Override
public long nextLong() {
long z = ( state += 0x9E3779B97F4A7C15L );
z = (z ^ (z >>> 30)) * 0xBF58476D1CE4E5B9L;
z = (z ^ (z >>> 27)) * 0x94D049BB133111EBL;
return z ^ (z >>> 31);
}
/**
* Produces a copy of this RandomnessSource that, if next() and/or nextLong() are called on this object and the
* copy, both will generate the same sequence of random numbers from the point copy() was called. This just need to
* copy the state so it isn't shared, usually, and produce a new value with the same exact state.
*
* @return a copy of this RandomnessSource
*/
@Override
public RandomnessSource copy() {
return new LightRNG(state);
}
/**
* Can return any int, positive or negative, of any size permissible in a 32-bit signed integer.
* @return any int, all 32 bits are random
*/
public int nextInt() {
return (int)nextLong();
}
/**
* Exclusive on the upper bound. The lower bound is 0.
* @param bound the upper bound; should be positive
* @return a random int less than n and at least equal to 0
*/
public int nextInt( final int bound ) {
if ( bound <= 0 ) return 0;
int threshold = (0x7fffffff - bound + 1) % bound;
for (;;) {
int bits = (int)(nextLong() & 0x7fffffff);
if (bits >= threshold)
return bits % bound;
}
}
/**
* Inclusive lower, exclusive upper.
* @param lower the lower bound, inclusive, can be positive or negative
* @param upper the upper bound, exclusive, should be positive, must be greater than lower
* @return a random int at least equal to lower and less than upper
*/
public int nextInt( final int lower, final int upper ) {
if ( upper - lower <= 0 ) throw new IllegalArgumentException("Upper bound must be greater than lower bound");
return lower + nextInt(upper - lower);
}
/**
* Exclusive on the upper bound. The lower bound is 0.
* @param bound the upper bound; should be positive
* @return a random long less than n
*/
public long nextLong( final long bound ) {
if ( bound <= 0 ) return 0;
long threshold = (0x7fffffffffffffffL - bound + 1) % bound;
for (;;) {
long bits = nextLong() & 0x7fffffffffffffffL;
if (bits >= threshold)
return bits % bound;
}
}
/**
* Inclusive lower, exclusive upper.
* @param lower the lower bound, inclusive, can be positive or negative
* @param upper the upper bound, exclusive, should be positive, must be greater than lower
* @return a random long at least equal to lower and less than upper
*/
public long nextLong( final long lower, final long upper ) {
if ( upper - lower <= 0 ) throw new IllegalArgumentException("Upper bound must be greater than lower bound");
return lower + nextLong(upper - lower);
}
/**
* Gets a uniform random double in the range [0.0,1.0)
* @return a random double at least equal to 0.0 and less than 1.0
*/
public double nextDouble() {
return ( nextLong() & DOUBLE_MASK ) * NORM_53;
}
/**
* Gets a uniform random double in the range [0.0,outer) given a positive parameter outer. If outer
* is negative, it will be the (exclusive) lower bound and 0.0 will be the (inclusive) upper bound.
* @param outer the exclusive outer bound, can be negative
* @return a random double between 0.0 (inclusive) and outer (exclusive)
*/
public double nextDouble(final double outer) {
return nextDouble() * outer;
}
/**
* Gets a uniform random float in the range [0.0,1.0)
* @return a random float at least equal to 0.0 and less than 1.0
*/
public float nextFloat() {
return (float)( ( nextLong() & FLOAT_MASK ) * NORM_24 );
}
/**
* Gets a random value, true or false.
* Calls nextLong() once.
* @return a random true or false value.
*/
public boolean nextBoolean() {
return ( nextLong() & 1 ) != 0L;
}
/**
* Given a byte array as a parameter, this will fill the array with random bytes (modifying it
* in-place). Calls nextLong() {@code Math.ceil(bytes.length / 8.0)} times.
* @param bytes a byte array that will have its contents overwritten with random bytes.
*/
public void nextBytes( final byte[] bytes ) {
int i = bytes.length, n = 0;
while( i != 0 ) {
n = Math.min( i, 8 );
for ( long bits = nextLong(); n-- != 0; bits >>= 8 ) bytes[ --i ] = (byte)bits;
}
}
/**
* Sets the seed of this generator (which is also the current state).
* @param seed the seed to use for this LightRNG, as if it was constructed with this seed.
*/
public void setSeed( final long seed ) {
state = seed;
}
/**
* Sets the seed (also the current state) of this generator.
* @param seed the seed to use for this LightRNG, as if it was constructed with this seed.
*/
@Override
public void setState( final long seed ) {
state = seed;
}
/**
* Gets the current state of this generator.
* @return the current seed of this LightRNG, changed once per call to nextLong()
*/
@Override
public long getState() {
return state;
}
/**
* Advances or rolls back the LightRNG's state without actually generating numbers. Skip forward
* or backward a number of steps specified by advance, where a step is equal to one call to nextInt().
* @param advance Number of future generations to skip past. Can be negative to backtrack.
* @return the state after skipping.
*/
public long skip(long advance)
{
return state += 0x9E3779B97F4A7C15L * advance;
}
@Override
public String toString() {
return "LightRNG (" + state + ")"; // Akarin
}
}

View File

@@ -0,0 +1,62 @@
package io.akarin.api.internal.utils.random;
import java.util.Random;
/**
* This is a "fake" LightRandom, backed by the LightRNG.
*
* This is useful if you want to quickly replace a Random variable with
* LightRNG without breaking every code.
*/
public class LightRandom extends Random {
public static LightRNG light = new LightRNG(); // LightRNG, static.
private static final long serialVersionUID = 1L;
@Override
public int next(int bits) {
return light.next(bits);
}
@Override
public void nextBytes(byte[] bytes) {
light.nextBytes(bytes);
}
@Override
public int nextInt() {
return light.nextInt();
}
@Override
public int nextInt(int n) {
return light.nextInt(n);
}
@Override
public long nextLong() {
return light.nextLong();
}
@Override
public boolean nextBoolean() {
return light.nextBoolean();
}
@Override
public float nextFloat() {
return light.nextFloat();
}
@Override
public double nextDouble() {
return light.nextDouble();
}
// Akarin start
@Override
public void setSeed(long seed) {
light.setSeed(seed);
}
// Akarin end
}

View File

@@ -0,0 +1,40 @@
package io.akarin.api.internal.utils.random;
import java.io.Serializable;
/**
* This interface defines the interactions required of a random number
* generator. It is a replacement for Java's built-in Random because for
* improved performance.
*
* @author Eben Howard - http://squidpony.com - howard@squidpony.com
*/
public interface RandomnessSource extends Serializable {
/**
* Using this method, any algorithm that might use the built-in Java Random
* can interface with this randomness source.
*
* @param bits the number of bits to be returned
* @return the integer containing the appropriate number of bits
*/
int next(int bits);
/**
*
* Using this method, any algorithm that needs to efficiently generate more
* than 32 bits of random data can interface with this randomness source.
*
* Get a random long between Long.MIN_VALUE and Long.MAX_VALUE (both inclusive).
* @return a random long between Long.MIN_VALUE and Long.MAX_VALUE (both inclusive)
*/
long nextLong();
/**
* Produces a copy of this RandomnessSource that, if next() and/or nextLong() are called on this object and the
* copy, both will generate the same sequence of random numbers from the point copy() was called. This just need to
* copy the state so it isn't shared, usually, and produce a new value with the same exact state.
* @return a copy of this RandomnessSource
*/
RandomnessSource copy();
}

View File

@@ -0,0 +1,20 @@
package io.akarin.api.internal.utils.random;
/**
* A simple interface for RandomnessSources that have the additional property of a state that can be re-set.
* Created by Tommy Ettinger on 9/15/2015.
*/
public interface StatefulRandomness extends RandomnessSource {
/**
* Get the current internal state of the StatefulRandomness as a long.
* @return the current internal state of this object.
*/
long getState();
/**
* Set the current internal state of this StatefulRandomness with a long.
*
* @param state a 64-bit long. You should avoid passing 0, even though some implementations can handle that.
*/
void setState(long state);
}

View File

@@ -21,7 +21,7 @@ public abstract class MixinEntity {
@Overwrite // PAIL: isInLava @Overwrite // PAIL: isInLava
public boolean au() { public boolean au() {
/* /*
* This is originally comes from Migot (https://github.com/Poweruser/Migot/commit/cafbf1707107d2a3aa6232879f305975bb1f0285) * This originally comes from Migot (https://github.com/Poweruser/Migot/commit/cafbf1707107d2a3aa6232879f305975bb1f0285)
* Thanks @Poweruser * Thanks @Poweruser
*/ */
int currentTick = MinecraftServer.currentTick; int currentTick = MinecraftServer.currentTick;

File diff suppressed because it is too large Load Diff