Skip to content

Shrink RubyHashLinkedBuckets#9381

Merged
headius merged 4 commits into
jruby:masterfrom
headius:no_hash_generation
May 7, 2026
Merged

Shrink RubyHashLinkedBuckets#9381
headius merged 4 commits into
jruby:masterfrom
headius:no_hash_generation

Conversation

@headius

@headius headius commented Apr 19, 2026

Copy link
Copy Markdown
Member

This started out as an experiment to remove the "generation" count that appeared to only be used for some rehashing heuristics, and expanded to an overall reduction of Hash size (based on the RubyHashLinkedBuckets implementation).

Changes

  • generation count (4 bytes) is removed, as it did not appear to be doing anything useful that other fields didn't handle. See previous discussion below.
  • threshold (4 bytes) was removed, as it was only used by the "Javasoft" hash strategy that has been disabled forever.
  • iteratorCount (4 bytes) is reduced to 2 bytes, as there's no reason to be able to accommodate 2^32 concurrent iterators.
  • The "head" entry is reduced by 32 bytes by introducing a base entry link type, since it only needed to track next and previous added entries.

These changes reduce the retained size of an empty hash from 160 bytes (in 10.1.0.0) to 136 bytes.

On generation count

This generation count did not appear to actually be doing anything useful, only being incremented on rehash and only used to follow a slightly different path for clearing the Hash instance. Removing it does not appear to affect functionality, and it was a 32-bit integer increasing the size of every linked-bucket Hash object in the system.

It is a relic of old ported code from CRuby and I'm not sure we need it now (or perhaps ever needed it).

This generation count does not appear to actually be doing anything
useful, only being incremented on rehash and only used to follow a
slightly different path for clearing the Hash instance. Removing it
does not appear to affect functionality, and it is a 32-bit integer
increasing the size of every linked-bucket Hash object in the
system.

This patch removes it.
@headius headius added this to the JRuby 10.1.1.0 milestone Apr 19, 2026
headius added 3 commits May 6, 2026 12:09
At one point we thought we might want to switch Hash from using
MRI/CRuby semantics to using "Javasoft" semantics, but we never
ended up using that logic and have been paying for an extra
"threshold" field this entire time. This patch removes all of the
"Javasoft" logic along with the "threshold" field that was not
actually being used.
iteratorCount is only used to determine whether there's iteration
happening at the time we want to rehash. Dropping this to 16 bits
still allows for up to 64k concurrent iterators before we might
wrap around to zero improperly; this is extremely unlikely.

This patch also switches to VarHandle for atomic updates and
removes the synchronized logic that never was used anyway (because
almost nothing prevents us from creating the old atomic updater or
the new VarHandle updater).
The "head" entry here only needs to track nextAdded and prevAdded,
with all other state only used after walking past this "head". We
can shrink the "head" entry by moving prevAdded and nextAdded into
a smaller base object, reducing the retained size of all hash
instances by 32 bytes.
@headius
headius force-pushed the no_hash_generation branch from 93e2727 to ef5520f Compare May 6, 2026 17:15
@headius headius changed the title Remove or rework generation count from linked bucket Hash Shrink RubyHashLinkedBuckets May 6, 2026
@headius
headius marked this pull request as ready for review May 6, 2026 17:25
@headius

headius commented May 6, 2026

Copy link
Copy Markdown
Member Author

Two additional changes were attempted but did not produce expected results:

  • Combining the flags and iteratorCount into a single 16-bit value would allow Hotspot to shrink the base object size another 8 bytes (eliminating internal and external padding) but led to much more complicated code for managing these values. It also reduces the safe number of concurrent iterators to 2^13, 8192, which gets closer to theoretically possible with many fibers and threads.
  • The prevAdded and nextAdded on the head link could be moved directly into RubyHashLinkedBuckets, eliminating that object altogether. In practice, making the access of those fields bimorphic increased the execution time of Hash.new by 2x.

At this point the remaining tricks to shrink the implementation are less productive than introducing a new space-efficient implementation (see #9394).

@headius
headius merged commit 7ce8e7c into jruby:master May 7, 2026
210 of 211 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant