From 9c11863f3e192b030cdb8231f2b78fc1b07468d2 Mon Sep 17 00:00:00 2001 From: themode Date: Wed, 5 Jan 2022 00:24:32 +0100 Subject: [PATCH] Optimize setAll --- .../jmh/palette/PaletteGetBenchmark.java | 4 +- .../jmh/palette/PaletteSetBenchmark.java | 15 +++- .../server/instance/palette/PaletteImpl.java | 78 +++++++++++++++++-- .../minestom/server/instance/PaletteTest.java | 15 +++- 4 files changed, 97 insertions(+), 15 deletions(-) diff --git a/jmh-benchmarks/src/jmh/java/net/minestom/jmh/palette/PaletteGetBenchmark.java b/jmh-benchmarks/src/jmh/java/net/minestom/jmh/palette/PaletteGetBenchmark.java index 29010c9b5..748945937 100644 --- a/jmh-benchmarks/src/jmh/java/net/minestom/jmh/palette/PaletteGetBenchmark.java +++ b/jmh-benchmarks/src/jmh/java/net/minestom/jmh/palette/PaletteGetBenchmark.java @@ -6,8 +6,8 @@ import org.openjdk.jmh.infra.Blackhole; import java.util.concurrent.TimeUnit; -@Warmup(iterations = 5, time = 1500, timeUnit = TimeUnit.MILLISECONDS) -@Measurement(iterations = 10, time = 1500, timeUnit = TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS) @Fork(3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) diff --git a/jmh-benchmarks/src/jmh/java/net/minestom/jmh/palette/PaletteSetBenchmark.java b/jmh-benchmarks/src/jmh/java/net/minestom/jmh/palette/PaletteSetBenchmark.java index f81f2f2dc..a956f9acc 100644 --- a/jmh-benchmarks/src/jmh/java/net/minestom/jmh/palette/PaletteSetBenchmark.java +++ b/jmh-benchmarks/src/jmh/java/net/minestom/jmh/palette/PaletteSetBenchmark.java @@ -4,9 +4,10 @@ import net.minestom.server.instance.palette.Palette; import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; -@Warmup(iterations = 5, time = 1500, timeUnit = TimeUnit.MILLISECONDS) -@Measurement(iterations = 10, time = 1500, timeUnit = TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS) @Fork(3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -36,6 +37,16 @@ public class PaletteSetBenchmark { } } + @Benchmark + public void incrWriteAll() { + AtomicInteger value = new AtomicInteger(0); + palette.setAll((x, y, z) -> { + final int v = value.getPlain(); + value.setPlain(v + 1); + return v; + }); + } + @Benchmark public void constantWrite() { final int dimension = palette.dimension(); diff --git a/src/main/java/net/minestom/server/instance/palette/PaletteImpl.java b/src/main/java/net/minestom/server/instance/palette/PaletteImpl.java index 019d6217e..46c21f765 100644 --- a/src/main/java/net/minestom/server/instance/palette/PaletteImpl.java +++ b/src/main/java/net/minestom/server/instance/palette/PaletteImpl.java @@ -96,7 +96,6 @@ final class PaletteImpl implements Palette, Cloneable { final int bitsPerEntry = this.bitsPerEntry; final int magicMask = MAGIC_MASKS[bitsPerEntry]; final int valuesPerLong = VALUES_PER_LONG[bitsPerEntry]; - final int dimensionMinus = dimension - 1; for (int i = 0; i < values.length; i++) { @@ -106,8 +105,8 @@ final class PaletteImpl implements Palette, Cloneable { final int y = index >> (dimensionBitCount << 1); final int z = (index >> (dimensionBitCount)) & dimensionMinus; final int x = index & dimensionMinus; - if(y >= dimension) - return; // + if (y >= dimension) + return; // Out of bounds final int bitIndex = j * bitsPerEntry; final short paletteIndex = (short) (value >> bitIndex & magicMask); final int result = hasPalette ? paletteToValueList.getInt(paletteIndex) : paletteIndex; @@ -191,13 +190,76 @@ final class PaletteImpl implements Palette, Cloneable { @Override public void setAll(@NotNull EntrySupplier supplier) { - // TODO optimize - for (int y = 0; y < dimension; y++) { - for (int z = 0; z < dimension; z++) { - for (int x = 0; x < dimension; x++) { - set(x, y, z, supplier.get(x, y, z)); + long[] values = this.values; + int bitsPerEntry = this.bitsPerEntry; + int valuesPerLong = VALUES_PER_LONG[bitsPerEntry]; + if (values.length == 0) { + this.values = values = new long[(size + valuesPerLong - 1) / valuesPerLong]; + } + int magicMask = MAGIC_MASKS[bitsPerEntry]; + final int dimensionMinus = dimension - 1; + + int[] cache = new int[maxSize()]; + + int j = 0; + valueLoop: + for (int i = 0; i < values.length; i++) { + long block = values[i]; + for (; j < valuesPerLong; j++) { + final int index = i * valuesPerLong + j; + final int y = index >> (dimensionBitCount << 1); + final int z = (index >> (dimensionBitCount)) & dimensionMinus; + final int x = index & dimensionMinus; + if (y >= dimension) + continue; // Out of bounds + int value = supplier.get(x, y, z); + cache[index] = value; + final boolean placedAir = value == 0; + if (value != 0) { + value = getPaletteIndex(value); + if (bitsPerEntry != this.bitsPerEntry) { + // Palette has been resized, must update the loop indexes + for (int k = 0; k < j + 1; k++) { // Place previous elements from cache + final int index2 = i * valuesPerLong + k; + final int y2 = index2 >> (dimensionBitCount << 1); + final int z2 = (index2 >> (dimensionBitCount)) & dimensionMinus; + final int x2 = index2 & dimensionMinus; + if (y2 >= dimension) + throw new IllegalStateException("Out of bounds"); + set(x2, y2, z2, cache[index2]); + } + bitsPerEntry = this.bitsPerEntry; + values = this.values; + valuesPerLong = VALUES_PER_LONG[bitsPerEntry]; + magicMask = MAGIC_MASKS[bitsPerEntry]; + if (values.length == 0) { + this.values = values = new long[(size + valuesPerLong - 1) / valuesPerLong]; + } + i = (index) / valuesPerLong - 1; + j = index % valuesPerLong + 1; + continue valueLoop; + } + } + + final int bitIndex = j * bitsPerEntry; + { + final long clear = MAGIC_MASKS[bitsPerEntry]; + + final long oldBlock = block >> bitIndex & magicMask; + if (oldBlock == value) + continue; // Trying to place the same block + final boolean currentAir = oldBlock == 0; + final long indexClear = clear << bitIndex; + block &= ~indexClear; + block |= (long) value << bitIndex; + if (currentAir != placedAir) { + // Block count changed + this.count += currentAir ? 1 : -1; + } } } + j = 0; + values[i] = block; } } diff --git a/src/test/java/net/minestom/server/instance/PaletteTest.java b/src/test/java/net/minestom/server/instance/PaletteTest.java index 0d612695a..7a5e299d6 100644 --- a/src/test/java/net/minestom/server/instance/PaletteTest.java +++ b/src/test/java/net/minestom/server/instance/PaletteTest.java @@ -1,9 +1,13 @@ package net.minestom.server.instance; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Vec; import net.minestom.server.instance.palette.Palette; import org.junit.jupiter.api.Test; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.*; @@ -140,7 +144,8 @@ public class PaletteTest { for (Palette palette : palettes) { // Fill all entries palette.setAll((x, y, z) -> x + y + z + 1); - palette.getAll((x, y, z, value) -> assertEquals(x + y + z + 1, value)); + palette.getAll((x, y, z, value) -> assertEquals(x + y + z + 1, value, + "x: " + x + ", y: " + y + ", z: " + z + ", dimension: " + palette.dimension())); // Replacing palette.replaceAll((x, y, z, value) -> { @@ -164,9 +169,13 @@ public class PaletteTest { // Fill all entries count.set(0); - palette.setAll((x, y, z) -> count.incrementAndGet()); + Set points = new HashSet<>(); + palette.setAll((x, y, z) -> { + assertTrue(points.add(new Vec(x, y, z)), "Duplicate point: " + x + ", " + y + ", " + z + ", dimension " + palette.dimension()); + return count.incrementAndGet(); + }); assertEquals(palette.maxSize(), palette.size()); - assertEquals(count.get(), palette.size()); + assertEquals(palette.size(), count.get()); count.set(0); palette.getAll((x, y, z, value) -> assertEquals(count.incrementAndGet(), value));