Skip to content

Fold _canHavePrefixUri into Xobj._bits to shrink AttrXobj by 8 bytes - #115

Merged
pjfanning merged 1 commit into
apache:trunkfrom
pjfanning:xobj-prefix-uri-bit-flag
Sep 7, 2026
Merged

Fold _canHavePrefixUri into Xobj._bits to shrink AttrXobj by 8 bytes#115
pjfanning merged 1 commit into
apache:trunkfrom
pjfanning:xobj-prefix-uri-bit-flag

Conversation

@pjfanning

Copy link
Copy Markdown
Member

Refs apache/poi#992, where a 600MB workbook was reported to hold 2.35 million AttrXobj (216MB) and 1.17 million ElementXobj (108MB).

NamedNodeXobj carries one boolean, _canHavePrefixUri, recording whether a node came from the DOM Level 1 factory methods (createElement/createAttribute), which must report null for localName, namespaceURI and prefix. Xobj already ends exactly on an 8-byte boundary at 88 bytes, so that single byte costs AttrXobj a whole slot — 89 bytes of fields padded out to a 96-byte object.

Xobj._bits already packs kind (0xF), domType (0xF0) and three flags, the highest being INHIBIT_DISCONNECT = 0x400. Moving the boolean there as CAN_HAVE_PREFIX_URI = 0x800 removes the field entirely.

Measured with Unsafe.objectFieldOffset on the compiled classes, JDK 17 and JDK 21:

trunk this change
Xobj 88 88
AttrXobj 96 (7 bytes padding) 88
ElementXobj 96 96

The saving also holds with -XX:-UseCompressedOops (152 → 144). For the reported workload that is roughly 19MB, about 3%.

ElementXobj stays at 96 because it adds a 4-byte _attributes reference on top of 88; nothing in this change can help there.

Relation to the patch proposed on the issue

The issue proposes narrowing _bits from int to short, which produces exactly the same layout — I measured both, and combining them gains nothing further. This approach reaches the same result without changing the type, so there is no compound-assignment narrowing or sign-extension behaviour to reason about and no need for the range assertions that patch adds; _bits keeps all 32 bits available for future flags. Either is correct; this one is the smaller change to reason about.

Behaviour

Identical. The bit is set in the NamedNodeXobj constructor, cleared at the two DOM Level 1 factory sites, and set again on rename in Xobj.setName — the same three writes as before, and NamedNodeXobj remains the only reader. No other Xobj sets 0x800.

Added NodePrefixUriFlagTest, covering all three write paths. It passes on trunk as well — it is a characterisation test for the invariant this refactor must preserve, not a bug fix.

Full suite: 3182 tests pass (170 skipped), including the W3C DOM conformance suite. forbiddenApisMain passes.

🤖 Generated with Claude Code

NamedNodeXobj carries a single boolean, _canHavePrefixUri, to record whether a
node was created through the DOM Level 1 factory methods. Xobj already ends on
an 8-byte boundary, so that one byte costs AttrXobj a whole 8-byte slot: 88
bytes of fields become a 96-byte object with 7 bytes of padding.

Xobj._bits already holds kind, domType and three flags, with the highest bit in
use at 0x400. Moving the boolean there as CAN_HAVE_PREFIX_URI = 0x800 removes
the field, and AttrXobj drops to 88 bytes. Measured with Unsafe.objectFieldOffset
on JDK 17 and 21, with and without compressed oops.

ElementXobj is unchanged at 96 - it adds a 4-byte _attributes reference on top
of 88.

Reported against POI as a 600MB workbook holding 2.35 million AttrXobj, where
this is roughly 19MB.

Refs apache/poi#992

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pjfanning
pjfanning merged commit dfef1b7 into apache:trunk Sep 7, 2026
3 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