Advertisement
theosib

Bug report on high CPU usage in AbstractMap::hashCode

Jun 5th, 2018
225
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.74 KB | None | 0 0
  1. AbstractMap::hashCode is a substantial portion of Minecraft CPU overhead, because child classes of PropertyEnum (all of which are interned) are implementing equals and hashCode incorrectly. I discovered this in 1.12.2, but it is still implemented this way in 1.13, and this could help a lot with some of the performance problems.
  2.  
  3.  
  4. I was doing some profiling on Minecraft 1.12.2 using Honest Profiler (https://github.com/jvm-profiling-tools/honest-profiler), and something that kept showing up in the top ranks of CPU users was {{java.util.AbstractMap::hashCode}}. I credit Pokechu22 for recognizing what that means: *A hashmap or hashset was being used as the KEY for another container.*
  5.  
  6. The offender turned out to be {{net.minecraft.block.properties.PropertyEnum}}. This contains both a HashSet and a HashMap:
  7.  
  8. private final ImmutableSet<T> allowedValues;
  9. private final Map<String, T> nameToValue = Maps.<String, T>newHashMap();
  10.  
  11. And then it uses them both to compute the hash:
  12.  
  13. public int hashCode()
  14. {
  15. int i = super.hashCode();
  16. i = 31 * i + this.allowedValues.hashCode();
  17. i = 31 * i + this.nameToValue.hashCode();
  18. return i;
  19. }
  20.  
  21. This gets called from all over the place, and the common code path for them all is:
  22.  
  23. (t 0.1,s 0.0) net.minecraft.block.state.BlockStateContainer$StateImplementation::getValue
  24. (t 0.1,s 0.0) com.google.common.collect.RegularImmutableMap::get
  25. (t 0.1,s 0.0) com.google.common.collect.RegularImmutableMap::get
  26. (t 0.1,s 0.0) net.minecraft.block.properties.PropertyEnum::hashCode
  27. (t 0.1,s 0.0) java.util.AbstractMap::hashCode
  28. (t 0.1,s 0.1) java.util.HashMap$Node::hashCode
  29.  
  30. Most or all blocks have a {{PropertyEnum}} as a member variable. But there are a few critical cases where the PropertyEnum is used as a KEY, and one turns out to be in redstone components, which is acknowledged to need significant optimization.
  31.  
  32. For one example, in {{BlockRedstoneRepeater}}, we have this:
  33.  
  34. protected IBlockState getPoweredState(IBlockState unpoweredState)
  35. {
  36. Integer integer = (Integer)unpoweredState.getValue(DELAY);
  37. Boolean obool = (Boolean)unpoweredState.getValue(LOCKED);
  38. EnumFacing enumfacing = (EnumFacing)unpoweredState.getValue(FACING);
  39. return Blocks.POWERED_REPEATER.getDefaultState().withProperty(FACING, enumfacing).withProperty(DELAY, integer).withProperty(LOCKED, obool);
  40. }
  41.  
  42. {{BlockRedstoneDiode.isSameDiode}} is also an extremely frequent caller to {{BlockRedstoneRepeater.getPoweredState}}:
  43.  
  44. public boolean isSameDiode(IBlockState state)
  45. {
  46. Block block = state.getBlock();
  47. return block == this.getPoweredState(this.getDefaultState()).getBlock() || block == this.getUnpoweredState(this.getDefaultState()).getBlock();
  48. }
  49.  
  50. Here, {{this.getDefaultState()}} leads to this in Block.java:
  51.  
  52. protected final BlockStateContainer blockState;
  53.  
  54. It all leads to one place: net.minecraft.block.state.BlockStateContainer.StateImplementation.getValue
  55.  
  56. That class contains this member variable:
  57.  
  58. private final ImmutableMap < IProperty<?>, Comparable<? >> properties;
  59.  
  60. Accessed this way:
  61.  
  62. public <T extends Comparable<T>> T getValue(IProperty<T> property)
  63. {
  64. Comparable<?> comparable = (Comparable)this.properties.get(property);
  65. ...
  66.  
  67. The only thing that "implements IProperty" was block/properties/PropertyHelper.java, and block/properties/PropertyEnum.java extends {{PropertyHelper}}.
  68.  
  69. To summarize, in most or all cases when you want to get a block property, you end up in code that uses a block property type object as a hashmap key, and computing the hash of a map is *expensive*.
  70.  
  71. Since these objects are supposed to be immutable, one way to fix this is to _cache_ the hash code. That is, compute the hash in the object's constructor and store it in an object variable.
  72.  
  73. However, in a direct discussion with Grum, he pointed out these for these classes, there is only a single instance for any given value, which means that the code should be using {{==}} for comparison and {{System.identityHashCode(this)}} for the hash code. In other words, there are four classes under net/minecraft/block/properties (now net/minecraft/state with different names) that need to be modified:
  74.  
  75. - PropertyHelper.java
  76. - PropertyBool.java
  77. - PropertyEnum.java
  78. - PropertyInteger.java
  79.  
  80. And they should implement equals and hashCode as follows, quoting Grum:
  81.  
  82. @Override
  83. public boolean equals(final Object o) {
  84. // We're singletons.
  85. return this == o;
  86. }
  87.  
  88. @Override
  89. public int hashCode() {
  90. return System.identityHashCode(this); // like object
  91. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement