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..0000000000000000000000000000000000000000 --- /dev/null +++ b/src/main/java/io/papermc/paper/util/table/ZeroCollidingReferenceStateTable.java @@ -0,0 +0,0 @@ +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 @@ -0,0 +0,0 @@ public abstract class StateHolder { private final Reference2ObjectArrayMap, 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, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { this.owner = owner; this.values = propertyMap; this.propertiesCodec = codec; + this.optimisedTable = new io.papermc.paper.util.table.ZeroCollidingReferenceStateTable(this, propertyMap); // Paper - optimise state lookup } public > S cycle(Property property) { @@ -0,0 +0,0 @@ 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 { @@ -0,0 +0,0 @@ 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.equals(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 , V extends T> S trySetValue(Property property, V value) { @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 @@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 @@ -0,0 +0,0 @@ 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); @@ -0,0 +0,0 @@ public class EnumProperty & StringRepresentable> extends Prope this.names.put(string, enum_); } + // Paper start - optimise BlockState 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 BlockState 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 @@ -0,0 +0,0 @@ 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); if (min < 0) { 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 @@ -0,0 +0,0 @@ public abstract class Property> { ); 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;