Skip to content

Generate mappers ahead of time instead of mapping with reflection - #227

Merged
DavidBadura merged 4 commits into
3.0.xfrom
generated-mapper
Oct 17, 2026
Merged

DavidBadura merged 4 commits into
3.0.xfrom
generated-mapper

Conversation

@DavidBadura

Copy link
Copy Markdown
Member

This brings the generator from #219 onto the mapper core of #226 and replaces #219. It is based on #226, so the diff only contains the generator.

The GeneratedMapperExtension registers a MapperFactory which loads the mappers the GeneratedMapperWarmer generated ahead of time. Classes without generated code, or whose fingerprint changed since the warmup, are still mapped with reflection. The runtime never loads the generator, an architecture test makes sure of that.

The resolver, Direction and direct() are gone. A generated mapper asks the hydrator for the chain of a nested class and calls it right away, so nested objects skip the normalizer and the hydrator even when middlewares or a lazy proxy run for the nested class. If the chain is a generated mapper, its nested entry point is called, which skips the checks the parent mapper already did. Other chains are wrapped once in a small adapter, so the generated code has no extra branch per nested object.

Compared to #219, hydrating the benchmark event with three nested objects is as fast without middlewares, about 25% faster with the cryptography extension and about 60% faster with a middleware which runs for every class.

The mutation score on the diff is at 81% for now, below the 90% CI wants. #219 failed that check as well, I'll follow up on the generator tests.

The GeneratedMapperExtension registers a mapper factory which loads mappers
generated by the GeneratedMapperWarmer. Classes without generated code, or
whose fingerprint changed since the warmup, are mapped with reflection.

This is the generator of #219 moved onto the mapper core. Nested objects are
mapped with the chain the hydrator built for the nested class, so they are
also handled right away when middlewares or a lazy proxy run for that class.
If the chain is a generated mapper, its nested entry point is called, which
skips the checks the parent mapper already did. The resolver and Direction
are not needed anymore.
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Hello 👋

here is the most recent benchmark result:

GeneratedHydratorBench
======================

+-------------------------------+--------------------+-----------------+
|                               | time (kde mode)    | memory          |
+-------------------------------+--------------------+-----------------+
| subject                       | Tag: <current>     | Tag: <current>  |
+-------------------------------+--------------------+-----------------+
| benchHydrate1Object ()        | 1.400μs (±0.00%)   | 3.080mb         |
| benchExtract1Object ()        | 1.800μs (±0.00%)   | 3.080mb         |
| benchHydrate1000Objects ()    | 936.667μs (±0.00%) | 3.080mb         |
| benchExtract1000Objects ()    | 634.667μs (±0.00%) | 3.080mb         |
| benchHydrate1000000Objects () | 674.147ms (±0.00%) | 3.080mb         |
| benchExtract1000000Objects () | 519.963ms (±0.00%) | 3.080mb         |
+-------------------------------+--------------------+-----------------+

StackHydratorBench
==================

+-------------------------------+-----------------------------------------+------------------------------+
|                               | time (kde mode)                         | memory                       |
+-------------------------------+--------------------+--------------------+-----------------+------------+
| subject                       | Tag: <current>     | Tag: base          | Tag: <current>  | Tag: base  |
+-------------------------------+--------------------+--------------------+-----------------+------------+
| benchHydrate1Object ()        | 2.800μs (±0.00%)   | 2.600μs (±0.00%)   | 2.832mb         | 3.312mb    |
| benchExtract1Object ()        | 3.000μs (±0.00%)   | 3.000μs (±0.00%)   | 2.832mb         | 3.312mb    |
| benchHydrate1000Objects ()    | 1.931ms (±0.00%)   | 1.890ms (±0.00%)   | 2.912mb         | 3.392mb    |
| benchExtract1000Objects ()    | 1.816ms (±0.00%)   | 1.802ms (±0.00%)   | 2.908mb         | 3.389mb    |
| benchHydrate1000000Objects () | 881.721ms (±0.00%) | 857.018ms (±0.00%) | 2.912mb         | 3.392mb    |
| benchExtract1000000Objects () | 857.407ms (±0.00%) | 879.716ms (±0.00%) | 2.908mb         | 2.908mb    |
+-------------------------------+--------------------+--------------------+-----------------+------------+

HydratorWithLazyBench
=====================

+------------------------------------------+-----------------------------------------+------------------------------+
|                                          | time (kde mode)                         | memory                       |
+------------------------------------------+--------------------+--------------------+-----------------+------------+
| subject                                  | Tag: <current>     | Tag: base          | Tag: <current>  | Tag: base  |
+------------------------------------------+--------------------+--------------------+-----------------+------------+
| benchHydrate1Object ()                   | 1.000μs (±0.00%)   | 7.800μs (±0.00%)   | 2.832mb         | 2.832mb    |
| benchHydrate1ObjectTriggerInit ()        | 3.600μs (±0.00%)   | 3.600μs (±0.00%)   | 2.832mb         | 2.832mb    |
| benchHydrate1000Objects ()               | 390.333μs (±0.00%) | 366.000μs (±0.00%) | 2.832mb         | 2.832mb    |
| benchHydrate1000ObjectsTriggerInit ()    | 2.640ms (±0.00%)   | 2.670ms (±0.00%)   | 2.896mb         | 2.896mb    |
| benchHydrate1000000Objects ()            | 236.956ms (±0.00%) | 227.314ms (±0.00%) | 2.832mb         | 2.832mb    |
| benchHydrate1000000ObjectsTriggerInit () | 1.610s (±0.00%)    | 1.600s (±0.00%)    | 2.896mb         | 2.896mb    |
+------------------------------------------+--------------------+--------------------+-----------------+------------+

StackHydratorWithCryptographyBench
==================================

+-------------------------------+---------------------------------------+------------------------------+
|                               | time (kde mode)                       | memory                       |
+-------------------------------+-------------------+-------------------+-----------------+------------+
| subject                       | Tag: <current>    | Tag: base         | Tag: <current>  | Tag: base  |
+-------------------------------+-------------------+-------------------+-----------------+------------+
| benchHydrate1Object ()        | 5.800μs (±0.00%)  | 5.600μs (±0.00%)  | 2.909mb         | 2.909mb    |
| benchExtract1Object ()        | 14.600μs (±0.00%) | 15.200μs (±0.00%) | 2.909mb         | 2.909mb    |
| benchHydrate1000Objects ()    | 3.106ms (±0.00%)  | 3.215ms (±0.00%)  | 2.995mb         | 2.995mb    |
| benchExtract1000Objects ()    | 8.075ms (±0.00%)  | 8.027ms (±0.00%)  | 3.201mb         | 3.201mb    |
| benchHydrate1000000Objects () | 1.497s (±0.00%)   | 1.615s (±0.00%)   | 2.995mb         | 2.995mb    |
| benchExtract1000000Objects () | 5.804s (±0.00%)   | 5.749s (±0.00%)   | 3.201mb         | 3.201mb    |
+-------------------------------+-------------------+-------------------+-----------------+------------+

GeneratedHydratorWithCryptographyBench
======================================

+-------------------------------+-------------------+-----------------+
|                               | time (kde mode)   | memory          |
+-------------------------------+-------------------+-----------------+
| subject                       | Tag: <current>    | Tag: <current>  |
+-------------------------------+-------------------+-----------------+
| benchHydrate1Object ()        | 4.000μs (±0.00%)  | 3.011mb         |
| benchExtract1Object ()        | 13.000μs (±0.00%) | 3.011mb         |
| benchHydrate1000Objects ()    | 2.049ms (±0.00%)  | 3.096mb         |
| benchExtract1000Objects ()    | 6.704ms (±0.00%)  | 3.352mb         |
| benchHydrate1000000Objects () | 1.281s (±0.00%)   | 3.081mb         |
| benchExtract1000000Objects () | 5.243s (±0.00%)   | 3.352mb         |
+-------------------------------+-------------------+-----------------+

HydratorBench
=============

+-------------------------------+--------------------+------------+
|                               | time (kde mode)    | memory     |
+-------------------------------+--------------------+------------+
| subject                       | Tag: base          | Tag: base  |
+-------------------------------+--------------------+------------+
| benchHydrate1Object ()        | 2.400μs (±0.00%)   | 2.832mb    |
| benchExtract1Object ()        | 3.000μs (±0.00%)   | 2.832mb    |
| benchHydrate1000Objects ()    | 1.934ms (±0.00%)   | 2.912mb    |
| benchExtract1000Objects ()    | 1.827ms (±0.00%)   | 2.908mb    |
| benchHydrate1000000Objects () | 859.306ms (±0.00%) | 2.912mb    |
| benchExtract1000000Objects () | 891.647ms (±0.00%) | 2.908mb    |
+-------------------------------+--------------------+------------+

HydratorWithCryptographyBench
=============================

+-------------------------------+-------------------+------------+
|                               | time (kde mode)   | memory     |
+-------------------------------+-------------------+------------+
| subject                       | Tag: base         | Tag: base  |
+-------------------------------+-------------------+------------+
| benchHydrate1Object ()        | 5.600μs (±0.00%)  | 2.909mb    |
| benchExtract1Object ()        | 15.200μs (±0.00%) | 2.909mb    |
| benchHydrate1000Objects ()    | 3.210ms (±0.00%)  | 2.995mb    |
| benchExtract1000Objects ()    | 8.050ms (±0.00%)  | 3.201mb    |
| benchHydrate1000000Objects () | 1.511s (±0.00%)   | 2.995mb    |
| benchExtract1000000Objects () | 5.782s (±0.00%)   | 3.201mb    |
+-------------------------------+-------------------+------------+

This comment gets update everytime a new commit comes in!

Base automatically changed from mapper to 3.0.x October 17, 2026 06:52
@DavidBadura
DavidBadura merged commit 8261c0f into 3.0.x Oct 17, 2026
15 of 17 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.

2 participants