Skip to content

Presize object and array literals from compiler size hints - #496

Merged
jow- merged 2 commits into
masterfrom
feat/value-allocation-improvement
Oct 16, 2026
Merged

jow- merged 2 commits into
masterfrom
feat/value-allocation-improvement

Conversation

@jow-

@jow- jow- commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Presize the backing storage of object and array literals from the size hints the compiler already emits.

This supersedes the yet unmerged remainder of #493: the object literal sizing commit from there (44bb9d4) is included here in reworked form, so it can be dropped from #493.

Commits

  • vm: size object literals from the compiler's key count — sizes the json-c table from the I_NOBJ operand instead of always using 16 entries. The table size is derived from LH_LOAD_FACTOR, so small literals no longer rehash right after creation. The commit message now carries measurements.
  • types: start array entry vectors at 2 slots — array capacity is implied by the element count, so I_NARR's hint was already used but rounded up to the generic 8, 12, 18, ... steps. Arrays now use an initial size of 2 (2, 3, 4, 6, 9, 13, 19, ...) via new init-size aware vector helpers; all other vectors keep the default of 8. uc_array_t and the exported API are unchanged.

Measurements

Peak RSS, current master vs. this branch:

Case Before After
200000 3-key objects, 50000 20-key objects, 50000 spread + computed key, 50000 {} 359452 KB 253180 KB
200000 arrays of 1 element 32444 KB 22296 KB
200000 arrays of 3 elements 32376 KB 24608 KB
200000 arrays of 8 elements 38268 KB 31100 KB
200000 arrays of 12 elements 48060 KB 37692 KB
200000 arrays of 20 elements 60732 KB 62648 KB
mix of 1, 3, 5, 8 and 12 element array literals 99192 KB 71400 KB

No measurable slowdown. Arrays of around 20 elements grow by about 3%. The custom test suite (268 tests) passes, and the process total for its 723 test processes moves by only -0.45%, as those scripts are tiny and dominated by startup.

Compatibility note

A module built against older headers that grows array->entries directly through the uc_vector_* macros, instead of ucv_array_push() / ucv_array_set(), would assume 8 slots as the minimum for arrays. Nothing in-tree does this.

🤖 Generated with Claude Code

blogic and others added 2 commits October 16, 2026 23:42
Every object got a json-c table of 16 entries, 40 bytes each on 64-bit,
however few keys it held. The compiler already emits the number of
plain keys of a literal as the operand of I_NOBJ, but the VM ignored it.

Size a literal's table from that count, so a schema or config table of
small objects no longer costs about 700 bytes per object. Spreads are not
counted; an object filled by spreading grows as before, and an empty
literal keeps the 16 entries of ucv_object_new().

Measured with a script building 200000 3-key objects, 50000 20-key
objects, 50000 objects using spread plus a computed key and 50000 empty
literals:

  Case                           Count
  3-key objects                  200000
  20-key objects                  50000
  spread plus computed key        50000
  {}                              50000

              Peak RSS     Time
  Before      359452 KB    0.52 s
  After       253180 KB    0.47 s

Signed-off-by: John Crispin <john@phrozen.org>
[derive table size from LH_LOAD_FACTOR, add measurements]
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Array entry vectors share the generic uc_vector growth policy, which
starts at 8 slots and grows by 1.5x. As the capacity is derived from the
element count, a literal such as [1, 2, 3] or an array receiving its
first push is backed by 8 slots (64 bytes on 64-bit) regardless of how
few elements it holds.

Add init-size aware variants of the extend and reduce helpers and use
them for arrays with an initial size of 2, giving the capacity steps
2, 3, 4, 6, 9, 13, 19, ... The existing uc_vector_extend(), _reduce()
and _grow() helpers keep their 8 slot default, so all other vectors
behave as before. The uc_array_t layout and the exported API are
unchanged.

Peak RSS for 200000 arrays of the given length, before and after:

  Elements    Before       After
   1          32444 KB     22296 KB
   3          32376 KB     24608 KB
   5          32456 KB     28936 KB
   7          32424 KB     31332 KB
   8          38268 KB     31100 KB
  12          48060 KB     37692 KB
  20          60732 KB     62648 KB

A mix of 1, 3, 5, 8 and 12 element literals drops from 99192 KB to
71400 KB, and an empty array receiving a single push from 32288 KB to
22792 KB, with no measurable slowdown. Arrays of about 20 elements grow
by roughly 3%.

Note that the array capacity invariant now differs from modules built
against older headers: such a module growing array->entries directly
through the uc_vector_*() macros, instead of ucv_array_push() or
ucv_array_set(), would assume 8 slots as the minimum.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
@jow-
jow- merged commit ef95e33 into master Oct 16, 2026
3 checks passed
@jow-
jow- deleted the feat/value-allocation-improvement branch October 16, 2026 23:25
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.

2 participants