Creating a large JSON with Utf8JsonWriter is very slow in a native AOT library

Don Z 60 Reputation points
2026-06-20T10:40:28.79+00:00

Hello,

In a personal project I'm creating a fairly large JSON document, 150MB, using Utf8JsonWriter in C#. It is reasonably fast when compiled to managed, NET 10, code - it finishes in 6 seconds. However, when published to native AOT, the same process takes 21 seconds, so ~3.5 times slower. I would have expected it to be the other way around, and the native version to be significantly faster.

Is there a reason why Utf8JsonWriter is performing worse in native AOT build, as compared to managed NET10? Thank you.

Developer technologies | .NET | .NET Runtime

Answer accepted by question author

Tom Tran (WICLOUD CORPORATION) 5,295 Reputation points Microsoft External Staff Moderator
2026-06-22T06:49:20.3933333+00:00

Hi @Don Z ,

From what I found, I couldn’t find a confirmed Utf8JsonWriter-specific issue in Native AOT.

Native AOT can improve startup time, app size, and memory usage, but it does not always make long-running code faster. For this kind of workload, the managed JIT may still perform better because it can optimize hot code while the application is running. Native AOT code is fixed at publish time. Microsoft documents the AOT speed/size trade-off here: Optimize AOT deployments.

I also found this article showing similar benchmark behavior: Native AOT can win when startup matters, but managed code can match or beat it after startup.

Disclaimer: This is a non-Microsoft article. I found it useful for comparison, but please review any ads or downloads on the site carefully before installing anything.

For your case, I would first try publishing the AOT version with speed optimization and compare again:

<PropertyGroup>
  <PublishAot>true</PublishAot>
  <OptimizationPreference>Speed</OptimizationPreference>
</PropertyGroup>

If the gap is still large, I would compare the GC settings next. Writing a large JSON document can create a lot of allocation and buffer pressure, so it is worth checking whether both builds are using comparable GC behavior. For example, you can test with Server GC enabled:

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

If the AOT build is still around 3.5x slower after those checks, a small reproducible benchmark would be the best next step to narrow this down.

Hope this helps. If the information I provided was helpful to you, I would greatly appreciate it if you could follow the instructions here

Was this answer helpful?

30+ people found this answer helpful.

4 additional answers

Sort by: Most helpful
  1. Bruce (SqlWork.com) 84,251 Reputation points
    2026-06-20T12:55:36.34+00:00

    Be sure you are not using reflection to get the values to write. AOT does not support dynamic code, so if using reflection, you should use the precompiled options.

    Was this answer helpful?

    30+ people found this answer helpful.

  2. Don Z 60 Reputation points
    2026-06-23T16:14:36.1233333+00:00

    The investigation points to the following:

    Loss of Dynamic PGO + tiered re-JIT (the core AOT-vs-JIT gap)

    In .NET 8+, the managed JIT enables Dynamic PGO by default. It observes types at runtime, then devirtualizes/inlines hot virtual & interface calls and biases hot branches. Native AOT does a single static compile with no runtime profile — roughly tier-1 code quality but profile-blind.

    This codebase is almost a worst case for that gap. The hot loop does:

    Many generic dictionary lookups.

    Interface dispatch.

    Lots of struct copies, ReadOnlySpan<char> splits, and EqualityComparer<T> calls.

    A JIT-with-PGO build devirtualizes and inlines much of this after warmup; AOT cannot. For dispatch/lookup-bound loops this alone is commonly ~1.3–2×.

    Was this answer helpful?

    20+ people found this answer helpful.
    0 comments No comments

  3. Deleted

    This answer has been deleted due to a violation of our Code of Conduct. The answer was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.


    Comments have been turned off. Learn more

  4. Deleted

    This answer has been deleted due to a violation of our Code of Conduct. The answer was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.


    Comments have been turned off. Learn more

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.