[SPIGOT-3754] Performance of BlockState.hashCode may be improved Created: 10/Jan/18 Updated: 11/Jan/18 Resolved: 10/Jan/18 |
|
| Status: | Resolved |
| Project: | Spigot |
| Component/s: | None |
| Affects Version/s: | None |
| Fix Version/s: | None |
| Type: | New Feature | Priority: | Minor |
| Reporter: | Donald Duck | Assignee: | Unassigned |
| Resolution: | Fixed | Votes: | 1 |
| Labels: | None | ||
| Attachments: |
|
| Description |
|
When I look at a profiler's output then the BlockState.hashCode() method looks suspicious. If the profile can be trusted, it seems to consume a significant share of the server's CPU. A BlockState object has two final fields: a Class and a String object. Therefore, the hashCode() method in BlockState will always return the same value when called on the same object multiple times. Maybe caching helps? |
| Comments |
| Comment by Donald Duck [ 11/Jan/18 ] |
|
Cool stuff. I re-built with |
| Comment by Donald Duck [ 10/Jan/18 ] |
|
I'll go even further. After analyzing BlockStateEnum I think we can prove that once the hash code of any BlockStateEnum has been computed it won't change. The BlockStateEnum objects are constructed with a Collection of enum literals, either taken straight from the class (which doesn't change during the life time of the containing class loader) or upon explicit request as in BlockPortal.AXIS where a constant array of enum literals is provided. The ImmutableSet is initialized at construction time with Enum literals (which don't change at runtime), and the b HashMap is built up only in the constructor, keys being immutable String objects, values being immutable Enum literals. This is why I think it's safe to also cache a BlockStateEnum's hashCode. See the attached profile which goes to show that hashCode calculations has gone way down the stack, no longer being the bottleneck. |
| Comment by Donald Duck [ 10/Jan/18 ] |
|
Regarding Black Hole's comment: subclasses may at best call super.hashCode(), and the hash code of BlockState does not depend on any polymorphic methods that may be overridden in subclasses. Therefore I think this change is safe. Regarding md_5's question: yes, I do have before/after comparisons. I've used a world that we have here on our little private server. It's characterized by quite some redstone mechanics and a few farms that are continuously operating. With the patch installed, Object.hashCode() appears a lot further down the list. The difference also becomes evident when looking at the call hierarchies leading into hashCode. Before the patch it looks like this: It clearly shows that redsone block mechanics are driving CPU effort here. After applying the patch things look like this: So here, redstone doesn't play any role anymore, and the remaining hashCode effort is caused largely by BlockStateEnum.hashCode. For the latter I'm not sure to what degree its "b" Map can change, so I'd rather leave it alone. But for all I see, caching BlockState.hashCode()'s result doesn't have any adverse side effects and according to the profiler used improves performance significantly, at least when some redstone mechanics are involved. |
| Comment by md_5 [ 10/Jan/18 ] |
|
Do you have any before and after for this? |
| Comment by Black Hole [ 10/Jan/18 ] |
|
Since NMS BlockState objects are immutable this would be a great addition. But there are some subclasses: BlockStateEnum, BlockStateBoolean and BlockStateInteger. |
| Comment by Donald Duck [ 10/Jan/18 ] |
|
SeeĀ https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/pull-requests/421/overview. |