From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Thu, 11 Mar 2021 20:05:44 -0800 Subject: [PATCH] Custom table implementation for blockstate state lookups Testing some redstone intensive machines showed to bring about a 10% improvement. diff --git a/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java new file mode 100644 index 0000000000000000000000000000000000000000..57d0cd3ad6f972e986c72a57f1a6e36003f190c2 --- /dev/null +++ b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java @@ -0,0 +1,160 @@ +package io.papermc.paper.util.table; + +import com.google.common.collect.Table; +import net.minecraft.world.level.block.state.StateHolder; +import net.minecraft.world.level.block.state.properties.Property; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public final class ZeroCollidingReferenceStateTable { + + // upper 32 bits: starting index + // lower 32 bits: bitset for contained ids + protected final long[] this_index_table; + protected final Comparable[] this_table; + protected final StateHolder this_state; + + protected long[] index_table; + protected StateHolder[][] value_table; + + public ZeroCollidingReferenceStateTable(final StateHolder state, final Map, Comparable> this_map) { + this.this_state = state; + this.this_index_table = this.create_table(this_map.keySet()); + + int max_id = -1; + for (final Property property : this_map.keySet()) { + final int id = lookup_vindex(property, this.this_index_table); + if (id > max_id) { + max_id = id; + } + } + + this.this_table = new Comparable[max_id + 1]; + for (final Map.Entry, Comparable> entry : this_map.entrySet()) { + this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue(); + } + } + + public void loadInTable(final Table, Comparable, StateHolder> table, + final Map, Comparable> this_map) { + final Set> combined = new HashSet<>(table.rowKeySet()); + combined.addAll(this_map.keySet()); + + this.index_table = this.create_table(combined); + + int max_id = -1; + for (final Property property : combined) { + final int id = lookup_vindex(property, this.index_table); + if (id > max_id) { + max_id = id; + } + } + + this.value_table = new StateHolder[max_id + 1][]; + + final Map, Map, StateHolder>> map = table.rowMap(); + for (final Property property : map.keySet()) { + final Map, StateHolder> propertyMap = map.get(property); + + final int id = lookup_vindex(property, this.index_table); + final StateHolder[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()]; + + for (final Map.Entry, StateHolder> entry : propertyMap.entrySet()) { + if (entry.getValue() == null) { + // TODO what + continue; + } + + states[((Property)property).getIdFor(entry.getKey())] = entry.getValue(); + } + } + + + for (final Map.Entry, Comparable> entry : this_map.entrySet()) { + final Property property = entry.getKey(); + final int index = lookup_vindex(property, this.index_table); + + if (this.value_table[index] == null) { + this.value_table[index] = new StateHolder[property.getPossibleValues().size()]; + } + + this.value_table[index][((Property)property).getIdFor(entry.getValue())] = this.this_state; + } + } + + + protected long[] create_table(final Collection> collection) { + int max_id = -1; + for (final Property property : collection) { + final int id = property.getId(); + if (id > max_id) { + max_id = id; + } + } + + final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32) + + for (final Property property : collection) { + final int id = property.getId(); + + ret[id >>> 5] |= (1L << (id & 31)); + } + + int total = 0; + for (int i = 1, len = ret.length; i < len; ++i) { + ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32; + } + + return ret; + } + + public Comparable get(final Property state) { + final Comparable[] table = this.this_table; + final int index = lookup_vindex(state, this.this_index_table); + + if (index < 0 || index >= table.length) { + return null; + } + return table[index]; + } + + public StateHolder get(final Property property, final Comparable with) { + final int withId = ((Property)property).getIdFor(with); + if (withId < 0) { + return null; + } + + final int index = lookup_vindex(property, this.index_table); + final StateHolder[][] table = this.value_table; + if (index < 0 || index >= table.length) { + return null; + } + + final StateHolder[] values = table[index]; + + if (withId >= values.length) { + return null; + } + + return values[withId]; + } + + protected static int lookup_vindex(final Property property, final long[] index_table) { + final int id = property.getId(); + final long bitset_mask = (1L << (id & 31)); + final long lower_mask = bitset_mask - 1; + final int index = id >>> 5; + if (index >= index_table.length) { + return -1; + } + final long index_value = index_table[index]; + final long contains_check = ((index_value & bitset_mask) - 1) >> (Long.SIZE - 1); // -1L if doesn't contain + + // index = total bits set in lower table values (upper 32 bits of index_value) plus total bits set in lower indices below id + // contains_check is 0 if the bitset had id set, else it's -1: so index is unaffected if contains_check == 0, + // otherwise it comes out as -1. + return (int)(((index_value >>> 32) + Long.bitCount(index_value & lower_mask)) | contains_check); + } +} diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java index 5c30f43ba7db43cc2613ddaf6ea0d0810d3d08d7..5be5eabc222b9e20c083ff83fae52010b19ea854 100644 --- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java +++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java @@ -40,11 +40,13 @@ public abstract class StateHolder { private final ImmutableMap, Comparable> values; private Table, Comparable, S> neighbours; protected final MapCodec propertiesCodec; + protected final io.papermc.paper.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Paper - optimise state lookup protected StateHolder(O owner, ImmutableMap, Comparable> entries, MapCodec codec) { this.owner = owner; this.values = entries; this.propertiesCodec = codec; + this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, entries); // Paper - optimise state lookup } public > S cycle(Property property) { @@ -85,11 +87,11 @@ public abstract class StateHolder { } public > boolean hasProperty(Property property) { - return this.values.containsKey(property); + return this.optimisedTable.get(property) != null; // Paper - optimise state lookup } public > T getValue(Property property) { - Comparable comparable = this.values.get(property); + Comparable comparable = this.optimisedTable.get(property); // Paper - optimise state lookup if (comparable == null) { throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); } else { @@ -98,24 +100,18 @@ public abstract class StateHolder { } public > Optional getOptionalValue(Property property) { - Comparable comparable = this.values.get(property); + Comparable comparable = this.optimisedTable.get(property); // Paper - optimise state lookup return comparable == null ? Optional.empty() : Optional.of(property.getValueClass().cast(comparable)); } public , V extends T> S setValue(Property property, V value) { - Comparable comparable = this.values.get(property); - if (comparable == null) { - throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner); - } else if (comparable == value) { - return (S)this; - } else { - S object = this.neighbours.get(property, value); - if (object == null) { - throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); - } else { - return object; - } + // Paper start - optimise state lookup + final S ret = (S)this.optimisedTable.get(property, value); + if (ret == null) { + throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); } + return ret; + // Paper end - optimise state lookup } public void populateNeighbours(Map, Comparable>, S> states) { @@ -134,7 +130,7 @@ public abstract class StateHolder { } } - this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); + this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); this.optimisedTable.loadInTable((Table)this.neighbours, this.values); // Paper - optimise state lookup } } diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java index ff1a0d125edd2ea10c870cbb62ae9aa23644b6dc..233215280f8494dbc33a2fd0b14e37e59f1cb643 100644 --- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java +++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java @@ -7,6 +7,13 @@ import java.util.Optional; public class BooleanProperty extends Property { private final ImmutableSet values = ImmutableSet.of(true, false); + // Paper start - optimise iblockdata state lookup + @Override + public final int getIdFor(final Boolean value) { + return value.booleanValue() ? 1 : 0; + } + // Paper end - optimise iblockdata state lookup + protected BooleanProperty(String name) { super(name, Boolean.class); } diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java index 0bca0f971dac994bd8b6ecd87e8b33e26c0f18f9..edd3c745efb40ee79a1393199c7a27ddaa2f8026 100644 --- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java +++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java @@ -15,6 +15,15 @@ public class EnumProperty & StringRepresentable> extends Prope private final ImmutableSet values; private final Map names = Maps.newHashMap(); + // Paper start - optimise iblockdata state lookup + private int[] idLookupTable; + + @Override + public final int getIdFor(final T value) { + return this.idLookupTable[value.ordinal()]; + } + // Paper end - optimise iblockdata state lookup + protected EnumProperty(String name, Class type, Collection values) { super(name, type); this.values = ImmutableSet.copyOf(values); @@ -28,6 +37,14 @@ public class EnumProperty & StringRepresentable> extends Prope this.names.put(string, enum_); } + // Paper start - optimise iblockdata state lookup + int id = 0; + this.idLookupTable = new int[type.getEnumConstants().length]; + java.util.Arrays.fill(this.idLookupTable, -1); + for (final T value : this.getPossibleValues()) { + this.idLookupTable[value.ordinal()] = id++; + } + // Paper end - optimise iblockdata state lookup } @Override diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java index 72f508321ebffcca31240fbdd068b4d185454cbc..d16156f8a4a2507e114dc651fd0af9cdffb3c8e0 100644 --- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java +++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java @@ -13,6 +13,16 @@ public class IntegerProperty extends Property { public final int min; public final int max; + // Paper start - optimise iblockdata state lookup + @Override + public final int getIdFor(final Integer value) { + final int val = value.intValue(); + final int ret = val - this.min; + + return ret | ((this.max - ret) >> 31); + } + // Paper end - optimise iblockdata state lookup + protected IntegerProperty(String name, int min, int max) { super(name, Integer.class); this.min = min; diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java index 1d0f0099571e7295f5f83004c45b6e992a4af83a..bf4a39671425b56e7096008a8e43094eebbb2d05 100644 --- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java +++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java @@ -22,6 +22,17 @@ public abstract class Property> { }, this::getName); private final Codec> valueCodec = this.codec.xmap(this::value, Property.Value::value); + // Paper start - optimise iblockdata state lookup + private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); + private final int id = ID_GENERATOR.getAndIncrement(); + + public final int getId() { + return this.id; + } + + public abstract int getIdFor(final T value); + // Paper end - optimise state lookup + protected Property(String name, Class type) { this.clazz = type; this.name = name;