From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: ishland Date: Tue, 21 Sep 2021 10:37:34 +0200 Subject: [PATCH] C2ME optimizations c2me: opts math Copyright (c) 2021-2022 ishland Original code by RelativityMC, licensed under MIT You can find the original code on https://github.com/RelativityMC/C2ME-fabric (Yarn mappings) c2me: reduce_allocs Copyright (c) 2021-2022 ishland Original code by RelativityMC, licensed under MIT You can find the original code on https://github.com/RelativityMC/C2ME-fabric (Yarn mappings) diff --git a/src/main/java/com/ishland/c2me/opts/allocs/common/ObjectCachingUtils.java b/src/main/java/com/ishland/c2me/opts/allocs/common/ObjectCachingUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..b1229bae2e74d22065c723c6d3eaf9a97d572b04 --- /dev/null +++ b/src/main/java/com/ishland/c2me/opts/allocs/common/ObjectCachingUtils.java @@ -0,0 +1,23 @@ +package com.ishland.c2me.opts.allocs.common; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +import java.util.BitSet; +import java.util.function.IntFunction; + +public class ObjectCachingUtils { + + private static final IntFunction bitSetConstructor = BitSet::new; + + public static ThreadLocal> BITSETS = ThreadLocal.withInitial(Int2ObjectOpenHashMap::new); + + private ObjectCachingUtils() { + } + + public static BitSet getCachedOrNewBitSet(int bits) { + final BitSet bitSet = BITSETS.get().computeIfAbsent(bits, bitSetConstructor); + bitSet.clear(); + return bitSet; + } + +} diff --git a/src/main/java/com/ishland/c2me/opts/allocs/common/PooledFeatureContext.java b/src/main/java/com/ishland/c2me/opts/allocs/common/PooledFeatureContext.java new file mode 100644 index 0000000000000000000000000000000000000000..4c84006c90bda4849b27879d5218f98e6d98f1dc --- /dev/null +++ b/src/main/java/com/ishland/c2me/opts/allocs/common/PooledFeatureContext.java @@ -0,0 +1,68 @@ +package com.ishland.c2me.opts.allocs.common; + +import java.util.Optional; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; + +public class PooledFeatureContext extends FeaturePlaceContext { + + public static final ThreadLocal>> POOL = ThreadLocal.withInitial(() -> new SimpleObjectPool<>(unused -> new PooledFeatureContext<>(), unused -> {}, 2048)); + + private Optional> feature; + private WorldGenLevel world; + private ChunkGenerator generator; + private RandomSource random; + private BlockPos origin; + private FC config; + + public PooledFeatureContext() { + super(null, null, null, null, null, null); + } + + public void reInit(Optional> feature, WorldGenLevel world, ChunkGenerator generator, RandomSource random, BlockPos origin, FC config) { + this.feature = feature; + this.world = world; + this.generator = generator; + this.random = random; + this.origin = origin; + this.config = config; + } + + public void reInit() { + this.feature = null; + this.world = null; + this.generator = null; + this.random = null; + this.origin = null; + this.config = null; + } + + public WorldGenLevel level() { + return this.world; + } + + public ChunkGenerator chunkGenerator() { + return this.generator; + } + + public RandomSource random() { + return this.random; + } + + public BlockPos origin() { + return this.origin; + } + + public FC config() { + return this.config; + } + + public Optional> topFeature() { + return this.feature; + } +} diff --git a/src/main/java/com/ishland/c2me/opts/allocs/common/SimpleObjectPool.java b/src/main/java/com/ishland/c2me/opts/allocs/common/SimpleObjectPool.java new file mode 100644 index 0000000000000000000000000000000000000000..b989019847f73ba3af57f7428699c9c869d6332f --- /dev/null +++ b/src/main/java/com/ishland/c2me/opts/allocs/common/SimpleObjectPool.java @@ -0,0 +1,57 @@ +package com.ishland.c2me.opts.allocs.common; + +import com.google.common.base.Preconditions; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +public class SimpleObjectPool { + + private final Function, T> constructor; + private final Consumer initializer; + private final int size; + + private final Object[] cachedObjects; + private int allocatedCount = 0; + + public SimpleObjectPool(Function, T> constructor, Consumer initializer, int size) { + this.constructor = Objects.requireNonNull(constructor); + this.initializer = Objects.requireNonNull(initializer); + Preconditions.checkArgument(size > 0); + this.cachedObjects = new Object[size]; + this.size = size; + + for (int i = 0; i < size; i++) { + final T object = constructor.apply(this); + this.cachedObjects[i] = object; + } + } + + public T alloc() { + final T object; + synchronized (this) { + if (this.allocatedCount >= this.size) { // oversized, falling back to normal alloc + object = this.constructor.apply(this); + return object; + } + + // get an object from the array + final int ordinal = this.allocatedCount++; + object = (T) this.cachedObjects[ordinal]; + this.cachedObjects[ordinal] = null; + } + + this.initializer.accept(object); // initialize the object + + return object; + } + + public void release(T object) { + synchronized (this) { + if (this.allocatedCount == 0) return; // pool is full + this.cachedObjects[--this.allocatedCount] = object; // store the object into the pool + } + } + +} diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java index 7017dd42f832d928f1008a05f01701667d951644..4e767dd8f9594e8a8f5d71e2bfd8c976c0032f98 100644 --- a/src/main/java/net/minecraft/resources/ResourceLocation.java +++ b/src/main/java/net/minecraft/resources/ResourceLocation.java @@ -27,6 +27,7 @@ public class ResourceLocation implements Comparable { public static final String REALMS_NAMESPACE = "realms"; protected final String namespace; protected final String path; + private String cachedString = null; // Mirai - c2me: opts allocs protected ResourceLocation(String[] id) { this.namespace = StringUtils.isEmpty(id[0]) ? "minecraft" : id[0]; @@ -99,7 +100,16 @@ public class ResourceLocation implements Comparable { @Override public String toString() { - return this.namespace + ":" + this.path; + // Mirai start - c2me: opts allocs + /** + * @author ishland + * @reason cache toString + */ + if (this.cachedString != null) return this.cachedString; + final String s = this.namespace + ":" + this.path; + this.cachedString = s; + return s; + // Mirai end } @Override diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/ConfiguredFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/ConfiguredFeature.java index 2f67705132df06ae6231dd1b89a4f61a90616ef5..1013df19df55316200169f6c3deec8876ea4686a 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/feature/ConfiguredFeature.java +++ b/src/main/java/net/minecraft/world/level/levelgen/feature/ConfiguredFeature.java @@ -12,6 +12,10 @@ import net.minecraft.util.RandomSource; import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +// Mirai start - c2me: opts allocs +import com.ishland.c2me.opts.allocs.common.PooledFeatureContext; +import com.ishland.c2me.opts.allocs.common.SimpleObjectPool; +// Mirai end public record ConfiguredFeature>(F feature, FC config) { public static final Codec> DIRECT_CODEC = Registry.FEATURE.byNameCodec().dispatch((configuredFeature) -> { @@ -21,7 +25,22 @@ public record ConfiguredFeature>> LIST_CODEC = RegistryCodecs.homogeneousList(Registry.CONFIGURED_FEATURE_REGISTRY, DIRECT_CODEC); public boolean place(WorldGenLevel world, ChunkGenerator chunkGenerator, RandomSource random, BlockPos origin) { - return this.feature.place(this.config, world, chunkGenerator, random, origin); + // Mirai start - c2me: opts allocs + /** + * @author ishland + * @reason pool FeatureContext + */ + if (!world.ensureCanWrite(origin)) return false; + final SimpleObjectPool> pool = PooledFeatureContext.POOL.get(); + final PooledFeatureContext context = (PooledFeatureContext) pool.alloc(); + try { + context.reInit(java.util.Optional.empty(), world, chunkGenerator, random, origin, this.config); + return this.feature.place(context); + } finally { + context.reInit(); + pool.release(context); + } + // Mirai end } public Stream> getFeatures() { diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java index b2f36a998437e2a63a3cbc6c3aa95b1402bff2f1..d69a57f67eeb99f3db4f80d13b9f0276ce4603af 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java +++ b/src/main/java/net/minecraft/world/level/levelgen/feature/OreFeature.java @@ -54,7 +54,7 @@ public class OreFeature extends Feature { protected boolean doPlace(WorldGenLevel world, RandomSource randomSource, OreConfiguration config, double startX, double endX, double startZ, double endZ, double startY, double endY, int x, int y, int z, int horizontalSize, int verticalSize) { int i = 0; - BitSet bitSet = new BitSet(horizontalSize * verticalSize * horizontalSize); + BitSet bitSet = com.ishland.c2me.opts.allocs.common.ObjectCachingUtils.getCachedOrNewBitSet(horizontalSize * verticalSize * horizontalSize); // Mirai - c2me: opts allocs BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); int j = config.size; double[] ds = new double[j * 4]; diff --git a/src/main/java/net/minecraft/world/level/levelgen/synth/ImprovedNoise.java b/src/main/java/net/minecraft/world/level/levelgen/synth/ImprovedNoise.java index fb84d703b4461343d50510d7c9be32fc1f09ed22..3da6b30febc98e5392e42d39c5bd69a82116dc2d 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/synth/ImprovedNoise.java +++ b/src/main/java/net/minecraft/world/level/levelgen/synth/ImprovedNoise.java @@ -11,6 +11,27 @@ public final class ImprovedNoise { public final double yo; public final double zo; + // Mirai start - c2me: opts math + private static final double[] FLAT_SIMPLEX_GRAD = new double[]{ + 1, 1, 0, 0, + -1, 1, 0, 0, + 1, -1, 0, 0, + -1, -1, 0, 0, + 1, 0, 1, 0, + -1, 0, 1, 0, + 1, 0, -1, 0, + -1, 0, -1, 0, + 0, 1, 1, 0, + 0, -1, 1, 0, + 0, 1, -1, 0, + 0, -1, -1, 0, + 1, 1, 0, 0, + 0, -1, 1, 0, + -1, 1, 0, 0, + 0, -1, -1, 0, + }; + // Mirai end + public ImprovedNoise(RandomSource random) { this.xo = random.nextDouble() * 256.0D; this.yo = random.nextDouble() * 256.0D; @@ -34,34 +55,38 @@ public final class ImprovedNoise { return this.noise(x, y, z, 0.0D, 0.0D); } + // Mirai start - c2me: opts math + /** + * @author ishland + * @reason optimize: remove frequent type conversions + */ /** @deprecated */ @Deprecated public double noise(double x, double y, double z, double yScale, double yMax) { double d = x + this.xo; double e = y + this.yo; double f = z + this.zo; - int i = Mth.floor(d); - int j = Mth.floor(e); - int k = Mth.floor(f); - double g = d - (double)i; - double h = e - (double)j; - double l = f - (double)k; - double o; - if (yScale != 0.0D) { + double i = Mth.floor(d); + double j = Mth.floor(e); + double k = Mth.floor(f); + double g = d - i; + double h = e - j; + double l = f - k; + double o = 0.0D; + if (yScale != 0.0) { double m; - if (yMax >= 0.0D && yMax < h) { + if (yMax >= 0.0 && yMax < h) { m = yMax; } else { m = h; } - o = (double)Mth.floor(m / yScale + (double)1.0E-7F) * yScale; - } else { - o = 0.0D; + o = Mth.floor(m / yScale + 1.0E-7F) * yScale; } - return this.sampleAndLerp(i, j, k, g, h - o, l, h); + return this.sampleAndLerp((int) i, (int) j, (int) k, g, h - o, l, h); } + // Mirai end public double noiseWithDerivative(double x, double y, double z, double[] ds) { double d = x + this.xo; @@ -84,26 +109,76 @@ public final class ImprovedNoise { return this.p[input & 255] & 255; } - private double sampleAndLerp(int sectionX, int sectionY, int sectionZ, double localX, double localY, double localZ, double fadeLocalY) { - int i = this.p(sectionX); - int j = this.p(sectionX + 1); - int k = this.p(i + sectionY); - int l = this.p(i + sectionY + 1); - int m = this.p(j + sectionY); - int n = this.p(j + sectionY + 1); - double d = gradDot(this.p(k + sectionZ), localX, localY, localZ); - double e = gradDot(this.p(m + sectionZ), localX - 1.0D, localY, localZ); - double f = gradDot(this.p(l + sectionZ), localX, localY - 1.0D, localZ); - double g = gradDot(this.p(n + sectionZ), localX - 1.0D, localY - 1.0D, localZ); - double h = gradDot(this.p(k + sectionZ + 1), localX, localY, localZ - 1.0D); - double o = gradDot(this.p(m + sectionZ + 1), localX - 1.0D, localY, localZ - 1.0D); - double p = gradDot(this.p(l + sectionZ + 1), localX, localY - 1.0D, localZ - 1.0D); - double q = gradDot(this.p(n + sectionZ + 1), localX - 1.0D, localY - 1.0D, localZ - 1.0D); - double r = Mth.smoothstep(localX); - double s = Mth.smoothstep(fadeLocalY); - double t = Mth.smoothstep(localZ); - return Mth.lerp3(r, s, t, d, e, f, g, h, o, p, q); + // Mirai start - c2me: opts math + /** + * @author ishland + * @reason inline math & small optimization: remove frequent type conversions and redundant ops + */ + private double sampleAndLerp(int sectionX, int sectionY, int sectionZ, double localX, double localY, double localZ, double fadeLocalX) { + // TODO [VanillaCopy] but optimized + final int var0 = sectionX & 0xFF; + final int var1 = (sectionX + 1) & 0xFF; + final int var2 = this.p[var0] & 0xFF; + final int var3 = this.p[var1] & 0xFF; + final int var4 = (var2 + sectionY) & 0xFF; + final int var5 = (var3 + sectionY) & 0xFF; + final int var6 = (var2 + sectionY + 1) & 0xFF; + final int var7 = (var3 + sectionY + 1) & 0xFF; + final int var8 = this.p[var4] & 0xFF; + final int var9 = this.p[var5] & 0xFF; + final int var10 = this.p[var6] & 0xFF; + final int var11 = this.p[var7] & 0xFF; + + final int var12 = (var8 + sectionZ) & 0xFF; + final int var13 = (var9 + sectionZ) & 0xFF; + final int var14 = (var10 + sectionZ) & 0xFF; + final int var15 = (var11 + sectionZ) & 0xFF; + final int var16 = (var8 + sectionZ + 1) & 0xFF; + final int var17 = (var9 + sectionZ + 1) & 0xFF; + final int var18 = (var10 + sectionZ + 1) & 0xFF; + final int var19 = (var11 + sectionZ + 1) & 0xFF; + final int var20 = (this.p[var12] & 15) << 2; + final int var21 = (this.p[var13] & 15) << 2; + final int var22 = (this.p[var14] & 15) << 2; + final int var23 = (this.p[var15] & 15) << 2; + final int var24 = (this.p[var16] & 15) << 2; + final int var25 = (this.p[var17] & 15) << 2; + final int var26 = (this.p[var18] & 15) << 2; + final int var27 = (this.p[var19] & 15) << 2; + final double var60 = localX - 1.0; + final double var61 = localY - 1.0; + final double var62 = localZ - 1.0; + final double var87 = FLAT_SIMPLEX_GRAD[(var20) | 0] * localX + FLAT_SIMPLEX_GRAD[(var20) | 1] * localY + FLAT_SIMPLEX_GRAD[(var20) | 2] * localZ; + final double var88 = FLAT_SIMPLEX_GRAD[(var21) | 0] * var60 + FLAT_SIMPLEX_GRAD[(var21) | 1] * localY + FLAT_SIMPLEX_GRAD[(var21) | 2] * localZ; + final double var89 = FLAT_SIMPLEX_GRAD[(var22) | 0] * localX + FLAT_SIMPLEX_GRAD[(var22) | 1] * var61 + FLAT_SIMPLEX_GRAD[(var22) | 2] * localZ; + final double var90 = FLAT_SIMPLEX_GRAD[(var23) | 0] * var60 + FLAT_SIMPLEX_GRAD[(var23) | 1] * var61 + FLAT_SIMPLEX_GRAD[(var23) | 2] * localZ; + final double var91 = FLAT_SIMPLEX_GRAD[(var24) | 0] * localX + FLAT_SIMPLEX_GRAD[(var24) | 1] * localY + FLAT_SIMPLEX_GRAD[(var24) | 2] * var62; + final double var92 = FLAT_SIMPLEX_GRAD[(var25) | 0] * var60 + FLAT_SIMPLEX_GRAD[(var25) | 1] * localY + FLAT_SIMPLEX_GRAD[(var25) | 2] * var62; + final double var93 = FLAT_SIMPLEX_GRAD[(var26) | 0] * localX + FLAT_SIMPLEX_GRAD[(var26) | 1] * var61 + FLAT_SIMPLEX_GRAD[(var26) | 2] * var62; + final double var94 = FLAT_SIMPLEX_GRAD[(var27) | 0] * var60 + FLAT_SIMPLEX_GRAD[(var27) | 1] * var61 + FLAT_SIMPLEX_GRAD[(var27) | 2] * var62; + + final double var95 = localX * 6.0 - 15.0; + final double var96 = fadeLocalX * 6.0 - 15.0; + final double var97 = localZ * 6.0 - 15.0; + final double var98 = localX * var95 + 10.0; + final double var99 = fadeLocalX * var96 + 10.0; + final double var100 = localZ * var97 + 10.0; + final double var101 = localX * localX * localX * var98; + final double var102 = fadeLocalX * fadeLocalX * fadeLocalX * var99; + final double var103 = localZ * localZ * localZ * var100; + + final double var113 = var87 + var101 * (var88 - var87); + final double var114 = var93 + var101 * (var94 - var93); + final double var115 = var91 + var101 * (var92 - var91); + final double var116 = var89 + var101 * (var90 - var89); + final double var117 = var114 - var115; + final double var118 = var102 * (var116 - var113); + final double var119 = var102 * var117; + final double var120 = var113 + var118; + final double var121 = var115 + var119; + return var120 + (var103 * (var121 - var120)); } + // Mirai end private double sampleWithDerivative(int sectionX, int sectionY, int sectionZ, double localX, double localY, double localZ, double[] ds) { int i = this.p(sectionX); diff --git a/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java b/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java index 03c752e4abfe9e1f27c79024af48b31f7e90e555..125ae55681212e902e6e1ed3f82990180d1a56c4 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java +++ b/src/main/java/net/minecraft/world/level/levelgen/synth/PerlinNoise.java @@ -26,6 +26,10 @@ public class PerlinNoise { private final double lowestFreqValueFactor; private final double lowestFreqInputFactor; private final double maxValue; + // Mirai start - c2me: opts math + private int octaveSamplersCount = 0; + private double[] amplitudesArray = null; + // Mirai end /** @deprecated */ @Deprecated @@ -131,6 +135,10 @@ public class PerlinNoise { this.lowestFreqInputFactor = Math.pow(2.0D, (double)(-j)); this.lowestFreqValueFactor = Math.pow(2.0D, (double)(i - 1)) / (Math.pow(2.0D, (double)i) - 1.0D); this.maxValue = this.edgeValue(2.0D); + // Mirai start - c2me: opts math + this.octaveSamplersCount = this.noiseLevels.length; + this.amplitudesArray = this.amplitudes.toDoubleArray(); + // Mirai end } protected double maxValue() { @@ -141,9 +149,33 @@ public class PerlinNoise { random.consumeCount(262); } + // Mirai start - c2me: opts math + /** + * @author ishland + * @reason optimize for common cases + */ public double getValue(double x, double y, double z) { - return this.getValue(x, y, z, 0.0D, 0.0D, false); + double d = 0.0; + double e = this.lowestFreqInputFactor; + double f = this.lowestFreqValueFactor; + + for(int i = 0; i < this.octaveSamplersCount; ++i) { + ImprovedNoise perlinNoiseSampler = this.noiseLevels[i]; + if (perlinNoiseSampler != null) { + @SuppressWarnings("deprecation") + double g = perlinNoiseSampler.noise( + wrap(x * e), wrap(y * e), wrap(z * e), 0.0, 0.0 + ); + d += this.amplitudesArray[i] * g * f; + } + + e *= 2.0; + f /= 2.0; + } + + return d; } + // Mirai end /** @deprecated */ @Deprecated @@ -191,9 +223,15 @@ public class PerlinNoise { return this.noiseLevels[this.noiseLevels.length - 1 - octave]; } + // Mirai start - c2me: opts math + /** + * @author ishland + * @reason remove frequent type conversion + */ public static double wrap(double value) { - return value - (double)Mth.lfloor(value / 3.3554432E7D + 0.5D) * 3.3554432E7D; + return value - Mth.lfloor(value / 3.3554432E7 + 0.5) * 3.3554432E7; } + // Mirai end protected int firstOctave() { return this.firstOctave;