Skip to content

Avoid boxing the primitive when iterating ref-keyed/valued maps#1907

Merged
brharrington merged 1 commit into
Netflix:mainfrom
brharrington:avoid-boxing-primitive-map-foreach
Jun 4, 2026
Merged

Avoid boxing the primitive when iterating ref-keyed/valued maps#1907
brharrington merged 1 commit into
Netflix:mainfrom
brharrington:avoid-boxing-primitive-map-foreach

Conversation

@brharrington

Copy link
Copy Markdown
Contributor

IntRefHashMap, RefIntHashMap, and RefDoubleHashMap exposed foreach as a Function2 callback. Because one parameter is a reference type, no specialized apply variant exists, so the generic apply(Object, Object) is used and the primitive (int key / int value / double value) is boxed on every element. This showed up as boxToInteger at IntRefHashMap.foreach being the #2 self-time leaf in a CPU profile, driven by RoaringTagIndex pattern queries (strPattern) scanning a key's value map.

Replace the Function2 callback with a small single-abstract-method Consumer per map whose method carries the primitive directly (e.g. accept(int, T)); it erases to a primitive descriptor, so iteration no longer boxes. Call sites pass lambdas and are unchanged via SAM conversion. After the change RoaringTagIndex compiles with zero boxToInteger.

Only these three maps box: the all-primitive maps (IntIntHashMap, DoubleIntHashMap, LongIntHashMap) and single-arg Function1 callbacks (foreachKey, the int/long sets, InternMap.retain) are already specialized by the compiler and were left unchanged.

Adds a JMH benchmark contrasting the boxing Function2 path with the SAM.

IntRefHashMap, RefIntHashMap, and RefDoubleHashMap exposed foreach as a
Function2 callback. Because one parameter is a reference type, no
specialized apply variant exists, so the generic apply(Object, Object) is
used and the primitive (int key / int value / double value) is boxed on
every element. This showed up as boxToInteger at IntRefHashMap.foreach
being the Netflix#2 self-time leaf in a CPU profile, driven by RoaringTagIndex
pattern queries (strPattern) scanning a key's value map.

Replace the Function2 callback with a small single-abstract-method
Consumer per map whose method carries the primitive directly (e.g.
accept(int, T)); it erases to a primitive descriptor, so iteration no
longer boxes. Call sites pass lambdas and are unchanged via SAM
conversion. After the change RoaringTagIndex compiles with zero
boxToInteger.

Only these three maps box: the all-primitive maps (IntIntHashMap,
DoubleIntHashMap, LongIntHashMap) and single-arg Function1 callbacks
(foreachKey, the int/long sets, InternMap.retain) are already specialized
by the compiler and were left unchanged.

Adds a JMH benchmark contrasting the boxing Function2 path with the SAM.
@brharrington brharrington added this to the 1.9.0 milestone Jun 4, 2026
@brharrington brharrington merged commit 23a15d0 into Netflix:main Jun 4, 2026
5 checks passed
@brharrington brharrington deleted the avoid-boxing-primitive-map-foreach branch June 4, 2026 17:31
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