Repository navigation
Set values directly for no-arg constructors (plan item 3.2) - #4403
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The focused optimization has no identified blocking issues in constructor selection, value conversion, or instance isolation.
No open issues 🎉
What changed in this PR
Optimizes Morphia’s no-argument entity construction to reduce decoding allocations while preserving value conversion.
Changes:
- Assigns no-arg entity values immediately and allocates pending buffers lazily.
- Reuses an empty parameter array, precompiles parameter-name matching, and removes the unused creator.
- Adds assignment/conversion coverage and marks plan item 3.2 complete.
| File | Description |
|---|---|
| design/mapping-game-plan.md | Marks the optimization complete. |
| core/src/test/java/dev/morphia/test/mapping/internal/ConstructorCreatorTest.java | Tests instance reuse and value conversion. |
| core/src/main/java/dev/morphia/mapping/NoArgCreator.java | Removes the unused creator. |
| core/src/main/java/dev/morphia/mapping/internal/ConstructorCreator.java | Adds immediate assignment and reduces allocations. |
| core/src/main/java/dev/morphia/mapping/InstanceCreatorFactoryImpl.java | Removes the unreachable fallback and logging. |
Review effort: Balanced
evanchooly
added a commit
that referenced
this pull request
Oct 20, 2026
…start on new kernels (#4404) Unblocks CI. Every PR's Build job, #4403 included, is failing in `MongoExtension.beforeAll` with `Container startup failed for image mongo:8 … Container exited with code 1`. ## Cause - **`mongo:8` moved to MongoDB 8.3.13 on 2026-10-17.** That release refuses to start on Linux 6.19 and newer: *"MongoDB cannot start: Linux kernel versions 6.19 and newer has a known incompatibility with this version of MongoDB"* ([SERVER-121912](https://jira.mongodb.org/browse/SERVER-121912)). - **The current 8.0 release, 8.0.34, has the same check.** 7.0.45, 8.2.12 and 9.0.4 start fine. I checked each image locally on a 7.0 kernel. - **The version flag was being ignored.** `MongoExtension` and `TestBase.withSharding` built the image tag from the **major version only**. So the matrix's `8.3.13`, `8.2.12` and `8.0.34` entries, and the `8.0.0` local default, all ran `mongo:8`. The 8.x entries weren't testing the versions they're named for. ## Changes - **Exact image tags.** `MongoExtension.imageName(version)` uses `-Dmongodb` as given: `mongo:8.2.12` for a full version, or `mongo:8.2` for that line's latest release. `withSharding` uses the same helper. - **New default.** The default changes from `8.0.0` (which ran `mongo:8`) to `8.2`. - **Matrix filter.** `BuildMatrix` skips exactly 8.0.34 and 8.3.13, with a comment pointing at SERVER-121912, so the matrix is now `['9.0.4', '8.2.12', '7.0.45']`. A later 8.0 or 8.3 patch release is picked up automatically. If one of those is still broken, CI fails until it's added to the list. ## Verified - The core and Kotlin suites pass with the new default (`mongo:8.2`) under `-Dmorphia.mapper=critter` and reflection: 1485 + 5 tests, 0 failures. - `TestSharding`'s sharded cluster starts on `mongo:8.2`. - `-Dmongodb=7.0.45` runs exactly `mongo:7.0.45`, and `TestQuery` passes on it. ## Decisions to review 1. **The skip lists exact versions (8.0.34, 8.3.13), not whole release lines.** This was changed in review. Fixed patch releases come back into the matrix on their own, at the cost of a failing build if a new patch release is still affected.
Entities with a no-arg constructor, which bestConstructor picks whenever one exists, went through ConstructorCreator buffering every decoded property in two lists and replaying them into the instance at the end. With no constructor parameters there is nothing to wait for, so the creator now constructs on the first value and sets each one through model.setValue as it arrives, which keeps Conversions. The pending lists are allocated only when a partial constructor needs them, a no-arg creator shares one empty parameter array, and the unnamed-parameter check uses a precompiled pattern. NoArgCreator and the factory branch that created it were unreachable: bestConstructor already returns any no-arg constructor. Both are removed, along with the branch's log line.
evanchooly
force-pushed
the
noarg-creator
branch
from
October 20, 2026 03:53
f57d3a4 to
4c8bd11
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan item 3.2 (review A2/B4).
The problem
bestConstructorpicks a no-arg constructor whenever one exists, so most entities (every POJO-style one) get aConstructorCreatorwith an empty parameter plan. That creator still buffered every decoded property in twoArrayLists and replayed them into the instance throughsetValueonce the document was done. It also cloned a zero-length parameter array per instance. TheInstanceCreatorFactoryImplbranch that would have usedNoArgCreatorwas unreachable, becausebestConstructorhad already returned any no-arg constructor.Change
ConstructorCreator.setconstructs the instance on the first value and sets each value throughmodel.setValueas it arrives.Conversionsstill apply, unlike in the deletedNoArgCreator.arg[0-9]+is a precompiledPattern, instead ofString.matchescompiling it once per constructor parameter.NoArgCreatordeleted (@MorphiaInternal), along with the unreachable factory branch and itsLOG.info("using old creator approach").Tests
ConstructorCreatorTest.noArgConstructorSetsValuesAsTheyArrivechecks that a no-arg creator is eager-safe, that values land on the one instance, and that a value needing conversion (42.0into anint) still goes throughConversions. It pins behavior. It would also pass onmaster, because the old path replayed the same values, so the speedup itself is shown by the benchmark below.-Dmorphia.mapper=critterand with reflection: 1486 + 5 tests, 0 failures.JMH:
CodecBenchmark,master(67dc8c46533, after 3.0 + 3.1) vs this branchSame machine as the baseline (i9-11900H, Temurin 17),
-prof gc, the benchmark's own settings (3 forks × 5 × 1 s). Before and after ran back to back with nothing else running. Critter is the AOT variant.reflection
critter
Reading it:
decodeInCursor. Models whose entities use full constructors barely move, as expected.decodecells looked slower (SIMPLE +7.4%, COLLECTIONS +4.7%). I reran both twice, alternating master and this branch: SIMPLE 1,348 vs 1,373 and 1,374 vs 1,392 ns/op, within the error bars; COLLECTIONS 10,247 vs 10,205 and 10,467 vs 10,330 ns/op, slightly faster. So both were noise.Decisions to review
setValuein both versions. If decoding fails partway through a document, an instance has already been constructed and is then discarded.EntityDecoder's eager path for reference cycles already did this.ConstructorCreator, keyed on the plan having no parameters. I didn't add a separate creator class. That keeps one creator type and one factory path, which matters because 3.17 has to hold values that arrive before_tfor exactly these creators.