From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Tue, 18 Jul 2023 13:14:15 +0800 Subject: [PATCH] Faster chunk serialization This patch is Powered by Gale(https://github.com/GaleMC/Gale) diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java index 8bafd5fd7499ba4a04bf706cfd1e156073716e21..7082022412dc75e3d84c74acd19bb8452e477d0c 100644 --- a/src/main/java/net/minecraft/util/BitStorage.java +++ b/src/main/java/net/minecraft/util/BitStorage.java @@ -1,6 +1,7 @@ package net.minecraft.util; import java.util.function.IntConsumer; +import net.minecraft.world.level.chunk.Palette; public interface BitStorage { int getAndSet(int index, int value); @@ -31,4 +32,6 @@ public interface BitStorage { } // Paper end + + void compact(Palette srcPalette, Palette dstPalette, short[] out); // Leaves - faster chunk serialization } diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java index e4d0d7e8fc58b8f9f614d74a141e452166e0364c..efe0a8b008d25a4c396bc9945d6466b672bc209b 100644 --- a/src/main/java/net/minecraft/util/SimpleBitStorage.java +++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java @@ -3,6 +3,7 @@ package net.minecraft.util; import java.util.function.IntConsumer; import javax.annotation.Nullable; import org.apache.commons.lang3.Validate; +import net.minecraft.world.level.chunk.Palette; public class SimpleBitStorage implements BitStorage { private static final int[] MAGIC = new int[]{-1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE, 0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756, 0, Integer.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0, 390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378, 306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, 252645135, 0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0, 204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970, 178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862, 0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0, 138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567, 126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197, 0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0, 104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893, 97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282, 0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0, 84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431, 79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303, 0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0, 70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE, 0, 5}; @@ -201,4 +202,44 @@ public class SimpleBitStorage implements BitStorage { super(message); } } + + // Leaves start - faster chunk serialization + @Override + public void compact(Palette srcPalette, Palette dstPalette, short[] out) { + if (this.size >= Short.MAX_VALUE) { + throw new IllegalStateException("Array too large"); + } + + if (this.size != out.length) { + throw new IllegalStateException("Array size mismatch"); + } + + short[] mappings = new short[(int) (this.mask + 1)]; + + int idx = 0; + + for (long word : this.data) { + long bits = word; + + for (int elementIdx = 0; elementIdx < this.valuesPerLong; ++elementIdx) { + int value = (int) (bits & this.mask); + int remappedId = mappings[value]; + + if (remappedId == 0) { + remappedId = dstPalette.idFor(srcPalette.valueFor(value)) + 1; + mappings[value] = (short) remappedId; + } + + out[idx] = (short) (remappedId - 1); + bits >>= this.bits; + + ++idx; + + if (idx >= this.size) { + return; + } + } + } + } + // Leaves end - faster chunk serialization } diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java index 311625277a26c9c187025a1036978229241b965f..e23995acb97100830079677aab0896487a705416 100644 --- a/src/main/java/net/minecraft/util/ZeroBitStorage.java +++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java @@ -3,6 +3,7 @@ package net.minecraft.util; import java.util.Arrays; import java.util.function.IntConsumer; import org.apache.commons.lang3.Validate; +import net.minecraft.world.level.chunk.Palette; public class ZeroBitStorage implements BitStorage { public static final long[] RAW = new long[0]; @@ -72,4 +73,6 @@ public class ZeroBitStorage implements BitStorage { public BitStorage copy() { return this; } + + @Override public void compact(Palette srcPalette, Palette dstPalette, short[] out) {} // Leaves - faster chunk serialization } diff --git a/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java b/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java index acae3eb30e0689048937f479dc3070f0688abdad..029b62acf1d9f8479ab64a55c12f00ba467249e3 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java +++ b/src/main/java/net/minecraft/world/level/chunk/PaletteResize.java @@ -1,5 +1,5 @@ package net.minecraft.world.level.chunk; -interface PaletteResize { +public interface PaletteResize { // Leaves - package -> public int onResize(int newBits, T object); } diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java index 16fbc633de3a1d9e5e8c65ae107397a6f0e50811..deef0363ce82d221726e8e4a6b11683e511f3049 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java @@ -23,8 +23,25 @@ import net.minecraft.util.Mth; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.ThreadingDetector; import net.minecraft.util.ZeroBitStorage; +import top.leavesmc.leaves.lithium.common.world.chunk.LithiumHashPalette; public class PalettedContainer implements PaletteResize, PalettedContainerRO { + + // Leaves start - faster chunk serialization + private static final ThreadLocal CACHED_ARRAY_4096 = ThreadLocal.withInitial(() -> new short[4096]); + private static final ThreadLocal CACHED_ARRAY_64 = ThreadLocal.withInitial(() -> new short[64]); + private Optional asOptional(long[] data) { + return Optional.of(Arrays.stream(data)); + } + private short[] getOrCreate(int size) { + return switch (size) { + case 64 -> CACHED_ARRAY_64.get(); + case 4096 -> CACHED_ARRAY_4096.get(); + default -> new short[size]; + }; + } + // Leaves end - faster chunk serialization + private static final int MIN_PALETTE_BITS = 0; private final PaletteResize dummyPaletteResize = (newSize, added) -> { return 0; @@ -302,30 +319,78 @@ public class PalettedContainer implements PaletteResize, PalettedContainer public synchronized PalettedContainerRO.PackedData pack(IdMap idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize this.acquire(); - PalettedContainerRO.PackedData var12; - try { - HashMapPalette hashMapPalette = new HashMapPalette<>(idList, this.data.storage.getBits(), this.dummyPaletteResize); - int i = paletteProvider.size(); - int[] is = new int[i]; - this.data.storage.unpack(is); - swapPalette(is, (id) -> { - return hashMapPalette.idFor(this.data.palette.valueFor(id)); - }); - int j = paletteProvider.calculateBitsForSerialization(idList, hashMapPalette.getSize()); - Optional optional; - if (j != 0) { - SimpleBitStorage simpleBitStorage = new SimpleBitStorage(j, i, is); - optional = Optional.of(Arrays.stream(simpleBitStorage.getRaw())); - } else { - optional = Optional.empty(); + // Leaves start - faster chunk serialization + if (!top.leavesmc.leaves.LeavesConfig.fasterChunkSerialization) { + PalettedContainerRO.PackedData var12; + try { + HashMapPalette hashMapPalette = new HashMapPalette<>(idList, this.data.storage.getBits(), this.dummyPaletteResize); + int i = paletteProvider.size(); + int[] is = new int[i]; + this.data.storage.unpack(is); + swapPalette(is, (id) -> { + return hashMapPalette.idFor(this.data.palette.valueFor(id)); + }); + int j = paletteProvider.calculateBitsForSerialization(idList, hashMapPalette.getSize()); + Optional optional; + if (j != 0) { + SimpleBitStorage simpleBitStorage = new SimpleBitStorage(j, i, is); + optional = Optional.of(Arrays.stream(simpleBitStorage.getRaw())); + } else { + optional = Optional.empty(); + } + + var12 = new PalettedContainerRO.PackedData<>(hashMapPalette.getEntries(), optional); + } finally { + this.release(); } - var12 = new PalettedContainerRO.PackedData<>(hashMapPalette.getEntries(), optional); - } finally { - this.release(); - } + return var12; + } else { + Optional data = Optional.empty(); + List elements = null; + try { + // The palette that will be serialized + LithiumHashPalette hashPalette = null; + + final Palette palette = this.data.palette(); + final BitStorage storage = this.data.storage(); + if (storage instanceof ZeroBitStorage || palette.getSize() == 1) { + // If the palette only contains one entry, don't attempt to repack it. + elements = List.of(palette.valueFor(0)); + } else if (palette instanceof LithiumHashPalette lithiumHashPalette) { + hashPalette = lithiumHashPalette; + } - return var12; + if (elements == null) { + LithiumHashPalette compactedPalette = new LithiumHashPalette<>(idList, storage.getBits(), this.dummyPaletteResize); + short[] array = this.getOrCreate(paletteProvider.size()); + + storage.compact(this.data.palette(), compactedPalette, array); + + // If the palette didn't change during compaction, do a simple copy of the data array + if (hashPalette != null && hashPalette.getSize() == compactedPalette.getSize() && storage.getBits() == paletteProvider.calculateBitsForSerialization(idList, hashPalette.getSize())) { // paletteSize can de-sync from palette - see https://github.com/CaffeineMC/lithium-fabric/issues/279 + data = this.asOptional(storage.getRaw().clone()); + elements = hashPalette.getElements(); + } else { + int bits = paletteProvider.calculateBitsForSerialization(idList, compactedPalette.getSize()); + if (bits != 0) { + // Re-pack the integer array as the palette has changed size + SimpleBitStorage copy = new SimpleBitStorage(bits, array.length); + for (int i = 0; i < array.length; ++i) { + copy.set(i, array[i]); + } + // We don't need to clone the data array as we are the sole owner of it + data = this.asOptional(copy.getRaw()); + } + } + elements = compactedPalette.getElements(); + } + } finally { + this.release(); + } + return new PalettedContainerRO.PackedData<>(elements, data); + } + // Leaves end - faster chunk serialization } private static void swapPalette(int[] is, IntUnaryOperator applier) { @@ -365,17 +430,47 @@ public class PalettedContainer implements PaletteResize, PalettedContainer @Override public void count(PalettedContainer.CountConsumer counter) { - if (this.data.palette.getSize() == 1) { - counter.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); + // Leaves start - faster chunk serialization + if (!top.leavesmc.leaves.LeavesConfig.fasterChunkSerialization) { + if (this.data.palette.getSize() == 1) { + counter.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); + } else { + Int2IntOpenHashMap int2IntOpenHashMap = new Int2IntOpenHashMap(); + this.data.storage.getAll((key) -> { + int2IntOpenHashMap.addTo(key, 1); + }); + int2IntOpenHashMap.int2IntEntrySet().forEach((entry) -> { + counter.accept(this.data.palette.valueFor(entry.getIntKey()), entry.getIntValue()); + }); + } } else { - Int2IntOpenHashMap int2IntOpenHashMap = new Int2IntOpenHashMap(); - this.data.storage.getAll((key) -> { - int2IntOpenHashMap.addTo(key, 1); - }); - int2IntOpenHashMap.int2IntEntrySet().forEach((entry) -> { - counter.accept(this.data.palette.valueFor(entry.getIntKey()), entry.getIntValue()); - }); + int len = this.data.palette().getSize(); + + // Do not allocate huge arrays if we're using a large palette + if (len > 4096) { + if (this.data.palette.getSize() == 1) { + counter.accept(this.data.palette.valueFor(0), this.data.storage.getSize()); + } else { + Int2IntOpenHashMap int2IntOpenHashMap = new Int2IntOpenHashMap(); + this.data.storage.getAll((key) -> { + int2IntOpenHashMap.addTo(key, 1); + }); + int2IntOpenHashMap.int2IntEntrySet().forEach((entry) -> { + counter.accept(this.data.palette.valueFor(entry.getIntKey()), entry.getIntValue()); + }); + } + } + + short[] counts = new short[len]; + this.data.storage().getAll(i -> counts[i]++); + for (int i = 0; i < counts.length; i++) { + T obj = this.data.palette().valueFor(i); + if (obj != null) { + counter.accept(obj, counts[i]); + } + } } + // Leaves end - faster chunk serialization } static record Configuration(Palette.Factory factory, int bits) { diff --git a/src/main/java/top/leavesmc/leaves/lithium/common/world/chunk/LithiumHashPalette.java b/src/main/java/top/leavesmc/leaves/lithium/common/world/chunk/LithiumHashPalette.java new file mode 100644 index 0000000000000000000000000000000000000000..e9573d57ef70ea43be59fd1474399ad48eb4e43a --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/lithium/common/world/chunk/LithiumHashPalette.java @@ -0,0 +1,197 @@ +package top.leavesmc.leaves.lithium.common.world.chunk; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import net.minecraft.core.IdMap; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.VarInt; +import net.minecraft.world.level.chunk.Palette; +import net.minecraft.world.level.chunk.PaletteResize; +import org.jetbrains.annotations.NotNull; + +import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR; + +// Powered by Gale(https://github.com/GaleMC/Gale) + +/** + * Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling + * {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing. + */ +public class LithiumHashPalette implements Palette { + private static final int ABSENT_VALUE = -1; + + private final IdMap idList; + private final PaletteResize resizeHandler; + private final int indexBits; + + private final Reference2IntMap table; + private T[] entries; + private int size = 0; + + public LithiumHashPalette(IdMap idList, PaletteResize resizeHandler, int indexBits, T[] entries, Reference2IntMap table, int size) { + this.idList = idList; + this.resizeHandler = resizeHandler; + this.indexBits = indexBits; + this.entries = entries; + this.table = table; + this.size = size; + } + + public LithiumHashPalette(IdMap idList, int bits, PaletteResize resizeHandler, @NotNull List list) { + this(idList, bits, resizeHandler); + + for (T t : list) { + this.addEntry(t); + } + } + + @SuppressWarnings("unchecked") + public LithiumHashPalette(IdMap idList, int bits, PaletteResize resizeHandler) { + this.idList = idList; + this.indexBits = bits; + this.resizeHandler = resizeHandler; + + int capacity = 1 << bits; + + this.entries = (T[]) new Object[capacity]; + this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR); + this.table.defaultReturnValue(ABSENT_VALUE); + } + + @Override + public int idFor(@NotNull T obj) { + int id = this.table.getInt(obj); + + if (id == ABSENT_VALUE) { + id = this.computeEntry(obj); + } + + return id; + } + + @Override + public boolean maybeHas(@NotNull Predicate predicate) { + for (int i = 0; i < this.size; ++i) { + if (predicate.test(this.entries[i])) { + return true; + } + } + + return false; + } + + private int computeEntry(T obj) { + int id = this.addEntry(obj); + + if (id >= 1 << this.indexBits) { + if (this.resizeHandler == null) { + throw new IllegalStateException("Cannot grow"); + } else { + id = this.resizeHandler.onResize(this.indexBits + 1, obj); + } + } + + return id; + } + + private int addEntry(T obj) { + int nextId = this.size; + + if (nextId >= this.entries.length) { + this.resize(this.size); + } + + this.table.put(obj, nextId); + this.entries[nextId] = obj; + + this.size++; + + return nextId; + } + + private void resize(int neededCapacity) { + this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1)); + } + + @Override + public T valueFor(int id) { + T[] entries = this.entries; + + if (id >= 0 && id < entries.length) { + return entries[id]; + } + + return null; + } + + @Override + public void read(FriendlyByteBuf buf) { + this.clear(); + + int entryCount = buf.readVarInt(); + + for (int i = 0; i < entryCount; ++i) { + this.addEntry(this.idList.byId(buf.readVarInt())); + } + } + + @Override + public void write(FriendlyByteBuf buf) { + int size = this.size; + buf.writeVarInt(size); + + for (int i = 0; i < size; ++i) { + buf.writeVarInt(this.idList.getId(this.valueFor(i))); + } + } + + @Override + public int getSerializedSize() { + int size = VarInt.getByteSize(this.size); + + for (int i = 0; i < this.size; ++i) { + size += VarInt.getByteSize(this.idList.getId(this.valueFor(i))); + } + + return size; + } + + @Override + public int getSize() { + return this.size; + } + + @NotNull + @Override + public Palette copy() { + return new LithiumHashPalette<>(this.idList, this.resizeHandler, this.indexBits, this.entries.clone(), new Reference2IntOpenHashMap<>(this.table), this.size); + } + + private void clear() { + Arrays.fill(this.entries, null); + this.table.clear(); + this.size = 0; + } + + public List getElements() { + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (T entry : this.entries) { + if (entry != null) { + builder.add(entry); + } + } + return builder.build(); + } + + public static Palette create(int bits, IdMap idList, PaletteResize listener, List list) { + return new LithiumHashPalette<>(idList, bits, listener, list); + } +} +