Optimize JSON serialization performance#437
Conversation
|
This is basically a reversal of #389. |
|
Just understand a bit better: How does this slowness impact you? (After all, it is at least still in the milliseconds in the benchmark case.) |
I think we somehow need to manage to keep the options for one full de-/serialization operation without having to make it static. That should solve both problems. |
I just noticed that this was very slow while debugging and in production I see small CPU spikes everytime we deserialize an SBOM due to this. Granted it's not a huge issue, but re-creating the JSON options is just wrong, MS even has an analyzer for this.
It would not. We have a web service that consumes SBOMs, so it only deserializes, but across multiple web requests. It shouldn't have to re-create the options each time. I wonder how exactly #389 did "improve downstream testability"? Are the options changed in some test contexts? Maybe an internal API to reset the options would be a better option? |
|
Looking at the code, there are two places where Utils.GetJsonSerializerOptions() is unexpected for me: IMO this should just use the provided options. If we can't avoid the workaround, we should at least use the copy constructor, i.e. var serializerOptions = new JsonSerializerOptions(options);rather than initialising completely new options.
Apart from these two cases, we are already there as far I can see. |
yes, as far as I can tell there is no reason to recreate the options here, fixed that with e3872e4.
Changed that with e3872e4 . This would still be a performance issue though. Properly fixing this would require deserializing the object manually like this: while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
if(reader.ValueTextEquals("components"))
{
toolChoices.Components = JsonSerializer.Deserialize<List<Component>>(ref reader, options);
}
else if(reader.ValueTextEquals("services"))
{
toolChoices.Services = JsonSerializer.Deserialize<List<Service>>(ref reader, options);
}
else
{
throw new JsonException();
}
}
}Not great though since that requires changes with new properties being introduced.
I don't quite follow? There are static now (as they imho need to be). Regarding #391 :
Fixed that with 7c7386f by re-recreating the options when UseUnsafeRelaxedJsonEscaping is changed. edit: had to force push because of missing sign-off. |
Signed-off-by: Marius Thesing <marius.thesing@gmail.com>
Signed-off-by: Marius Thesing <marius.thesing@gmail.com>
Signed-off-by: Marius Thesing <marius.thesing@gmail.com>
Signed-off-by: Marius Thesing <marius.thesing@gmail.com>
This was just a reply to @mtsfoni on his statement: Namely that already in the current main branch, the options are constructed only once for a full de-/serialization operation. |
The JSON serializer currently recreates the JsonSerializerOptions every time, which is a huge waste of ressources since the options act as a cache for the serialization metadata.
This changes the serializer to keep the options in a static field instead.
Also some benchmark fixes:
Results can be reproduced with:
Before:
After: