Skip to content

Allocate FlatHashtable tables with an array constructor, not reflection - #13058

Draft
dougqh wants to merge 1 commit into
masterfrom
dougqh/flathashtable-typed-array-factory
Draft

dougqh wants to merge 1 commit into
masterfrom
dougqh/flathashtable-typed-array-factory

Conversation

@dougqh

@dougqh dougqh commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

FlatHashtable now creates and grows its tables with an array constructor instead of Array.newInstance.

  • Factories: they take an array constructor instead of a Class, last per the functional-parameter convention. For example, FlatHashtable.D1.createFixed(capacity, MyEntry[]::new), or for the raw core, FlatHashtable.create(capacity, MyEntry[]::new).
  • Growth: D1 and D2 keep the constructor and use it when they grow. The raw core's resize and resizingInsert take it as their last argument.
  • Type precision is unchanged. MyEntry[]::new compiles to a plain new MyEntry[n], so tables keep a precise runtime component type: typed reads, real array-store checks, and a precise element type for the JIT. That precision was the reason for passing a Class, and a new test pins it through growth.

Motivation

GraalVM 17's native-image rejects Array.newInstance unless each entry array type is registered for reflection. The failure surfaces in a class initializer, which poisons the class for the life of the process.

The first production user of FlatHashtable, the dense store's sizing-hint table (#12926), hit exactly this: a native app sent no traces at all on GraalVM 17. GraalVM 21 and 25 were unaffected, most likely because they fold a constant class argument. The growth path's class comes from table.getClass(), which isn't a constant, so a growable table would probably fail on newer versions too; that part is untested.

Fixing it here means no FlatHashtable user needs a per-type native-image registration.

Additional Notes

  • No production callers change. The callers updated here are tests and benchmarks; the only production user is in Presize dense tag stores and seed span prototypes in bulk (dense store - part 9) #12926, which moves to the new factory.
  • Other toolbox reflection, unchanged: the raw Hashtable.create(Class, int) and StringIndex.mapValues(String[], Class, Function) also allocate reflectively, but have no production callers. ConcurrentHashtable and Hashtable.D1/D2, which do have production users, already allocate without reflection.

🤖 Generated with Claude Code

FlatHashtable minted its tables with Array.newInstance(entryClass, n),
and grew them with Array.newInstance(table.getClass().getComponentType(),
n), to keep a precise component type: typed reads, real array-store
checks and a precise element type for the JIT. GraalVM 17's native-image
rejects that call unless each entry array type is registered for
reflection; the failure surfaces in a class initializer and poisons the
class for the life of the process.

The factories now take an array constructor instead of a Class, last
per the functional-parameter convention, e.g.
FlatHashtable.D1.createFixed(capacity, MyEntry[]::new). An array
constructor reference compiles to a plain new MyEntry[n], so the table
keeps the same precise runtime type with no reflection. D1 and D2 keep
the constructor to grow, and the raw core's resize / resizingInsert take
it too, so growth stays precisely typed as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dougqh dougqh added comp: core Tracer core tag: no release notes Changes to exclude from release notes type: refactoring tag: ai generated Largely based on code generated by an AI or LLM labels Oct 26, 2026
@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

🎯 Code Coverage (details)
• Patch Coverage: 100.00%
• Overall Coverage: 59.06% (-0.39%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 9bbd442 | Docs | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.97 s 13.95 s [-0.5%; +0.9%] (no difference)
startup:insecure-bank:tracing:Agent 12.92 s 12.97 s [-1.0%; +0.2%] (no difference)
startup:petclinic:appsec:Agent 17.13 s 17.02 s [-0.2%; +1.4%] (no difference)
startup:petclinic:iast:Agent 16.95 s 17.05 s [-1.3%; +0.1%] (no difference)
startup:petclinic:profiling:Agent 16.58 s 16.83 s [-2.6%; -0.4%] (maybe better)
startup:petclinic:sca:Agent 17.00 s 16.74 s [+0.8%; +2.3%] (maybe worse)
startup:petclinic:tracing:Agent 15.60 s 16.20 s [-7.8%; +0.4%] (no difference)

Commit: 9bbd4423 · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant