[pr/191] RFR: 8320761: [Lilliput] Implement compact identity hashcode
Roman Kennke
rkennke at openjdk.org
Tue Jan 21 12:53:29 UTC 2025
This reimplements identity hash-code such that it allocates the space for the i-hash only on-demand. The idea is that most objects never get i-hashed, and therefore we don't want to penalize those objects which don't by carrying 32 unused bits for no reason.
I'm proposing that we only reserve two bits in the header to track the state of an object:
- One bit to indicate that an object has been i-hashed
- Another bit to indicate that an object has been 'expanded' to hold the i-hash in a field.
The transition would then be as follows:
1. Object starts out with state 00: not i-hashed, and not expanded.
2. The first time when Object.identityHashCode() is called, advance the state to 01 (i-hashed, but not expanded). Generate an i-hash based on the object's address, and return that.
3. Whenever Object.identityHashCode() is called in that state, and as long as that object stays at its address, keep generating the i-hash based on its address.
4. As soon as the object gets moved to a new address (by the GC), advance the state to 11 (i-hashed and expanded), generate the i-hash based on the original address one last time, and write it to the hidden field in the expanded object.
5. Whenever Object.identityHashCode() is called in that state, read the hidden field and return the i-hashed that has been stored there.
Transitioning to the 'expanded' state does not necessarily mean that the object actually needs to be expanded. The offset of the hidden field for any class is computed at class-loading-time. That field may fit into any gaps in the field layout, including alignment gaps at the end of an object. Only when no such gaps are found, we need to actually expand the object during GC. That appears to happen approx 50% of the time.
For a discussion why it is not a problem to expand objects during GC, see here: [https://wiki.openjdk.org/display/lilliput/Compact+Identity+Hashcode](https://wiki.openjdk.org/display/lilliput/Compact+Identity+Hashcode)
I ran some I-hash heavy benchmarks (e.g. SPECjvm compiler benchmarks), and have not seen a performance difference. It would be nice to make some micro-benchmarks for the various scenarios (e.g. is it faster to compute the i-hash or read it from memory?), and compare that with the old implementation, but I haven't done that yet. I will do so in a follow-up PR.
Testing:
- tier1 (-UCOH)
- tier2 (-UCOH)
- tier1 (+UCOH)
- tier2 (+UCOH)
-------------
Depends on: https://git.openjdk.org/lilliput/pull/191
Commit messages:
- Merge branch 'JDK-8346011' into JDK-8320761
- 8320761: [Lilliput] Implement compact identity hashcode
Changes: https://git.openjdk.org/lilliput/pull/192/files
Webrev: https://webrevs.openjdk.org/?repo=lilliput&pr=192&range=00
Issue: https://bugs.openjdk.org/browse/JDK-8320761
Stats: 889 lines in 65 files changed: 725 ins; 16 del; 148 mod
Patch: https://git.openjdk.org/lilliput/pull/192.diff
Fetch: git fetch https://git.openjdk.org/lilliput.git pull/192/head:pull/192
PR: https://git.openjdk.org/lilliput/pull/192
More information about the lilliput-dev
mailing list