Skip to content

perf: replace anonymous Member classes with concrete ExprFieldMember/MethodFieldMember#756

Merged
stephenamar-db merged 1 commit intomasterfrom
obj-opt/concrete-member-class
Apr 11, 2026
Merged

perf: replace anonymous Member classes with concrete ExprFieldMember/MethodFieldMember#756
stephenamar-db merged 1 commit intomasterfrom
obj-opt/concrete-member-class

Conversation

@stephenamar-db
Copy link
Copy Markdown
Collaborator

Summary

  • Replace the two anonymous Val.Obj.Member subclasses in visitMemberList with concrete ExprFieldMember and MethodFieldMember classes, giving the JIT a monomorphic type at every invoke() call site for better inlining.
  • Extract the shared scope-caching logic (makeNewScope/createNewScope closures) into an ObjectScopeFactory class. All fields from the same object literal share one factory, preserving per-object scope caching while eliminating per-field closure allocations.

Benchmark results

JMH regression suite (bench.runRegressions), JDK 21.0.10, single fork, 1 warmup + 1 measurement iteration (10s):

Benchmark Before After Change
bench.07 (object fibonacci) 9.948 ms 5.049 ms -49.2%
comparison 6.941 ms 4.201 ms -39.5%
large_string_template 5.591 ms 3.830 ms -31.5%
base64 1.150 ms 0.812 ms -29.4%
large_string_join 1.476 ms 1.053 ms -28.7%
base64_byte_array 2.925 ms 2.160 ms -26.2%
bench.04 0.486 ms 0.372 ms -23.5%
realistic1 4.592 ms 3.550 ms -22.7%
base64DecodeBytes 17.514 ms 13.667 ms -22.0%
foldl 0.277 ms 0.219 ms -20.9%
gen_big_object 2.345 ms 1.947 ms -17.0%
realistic2 115.444 ms 97.620 ms -15.4%
assertions 0.532 ms 0.464 ms -12.8%
bench.09 0.079 ms 0.070 ms -11.4%

No regressions observed (remaining benchmarks within noise).

Test plan

  • ./mill 'sjsonnet.jvm[3.3.7]'.test — all tests pass
  • ./mill __.checkFormat — formatting verified
  • JMH regression benchmarks compared to baseline

@stephenamar-db
Copy link
Copy Markdown
Collaborator Author

@He-Pin

…MethodFieldMember

Every field in visitMemberList previously created an anonymous Val.Obj.Member
subclass, generating N distinct classes per object expression. This pollutes
the JIT profile with megamorphic dispatch at invoke() call sites.

Replace with two concrete classes (ExprFieldMember, MethodFieldMember) and a
shared ObjectScopeFactory. All fields from the same object literal share one
factory which encapsulates the scope-caching logic (previously in the
makeNewScope/createNewScope closures). Benefits:

- Monomorphic dispatch at Member.invoke() -> better JIT inlining
- Explicit fields instead of synthetic closure captures
- Scope caching preserved via shared ObjectScopeFactory
- One factory allocation per object replaces N closure allocations
@stephenamar-db stephenamar-db force-pushed the obj-opt/concrete-member-class branch from c021e1d to 0bf1602 Compare April 11, 2026 21:16
@He-Pin
Copy link
Copy Markdown
Contributor

He-Pin commented Apr 11, 2026

We need more pattern-driven AST transformation, too.

@stephenamar-db stephenamar-db merged commit b35cde7 into master Apr 11, 2026
5 checks passed
@stephenamar-db stephenamar-db deleted the obj-opt/concrete-member-class branch April 11, 2026 21:28
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