-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Description
The low-level static method hosted in the Jakarta JSON-P Json
object are very inefficient: they are resolving a JsonProvider.provider()
for each call which in turn calls the ServiceLoader
(among other things...) and allocate a new JsonProvider
.
See:
- https://github.com/jakartaee/jsonp-api/blob/master/api/src/main/java/jakarta/json/Json.java#L433-L435
- https://github.com/jakartaee/jsonp-api/blob/master/api/src/main/java/jakarta/json/spi/JsonProvider.java#L109-L145
Meaning that when you are calling Json.createValue("string")
to allocate a simple String value, you are actually making a call to the service loader and instantiating a new JsonProvider
.
I stumbled upon that independently this week-end and after experimenting with it and writing a quick patch, I was like "it's crazy nobody complained about it" so I went to the JSON-P website and stumbled upon:
- #346 - Json should cache the result of JsonProvider.provider() jakartaee/jsonp-api#347 (which is the same patch I wrote)
- and then: Methods in Json re-create JsonProvider on each execution, Json should cache the result of JsonProvider.provider() jakartaee/jsonp-api#346
- and finally: Json factory methods are very inefficient jakartaee/jsonp-api#154
Meaning that I don't expect it to be fixed any time soon.
To give an idea of how things go, I did a simple JMH benchmark.
Json.createValue("test")
i.e. what people (including us) use:
Iteration 1: 153.907 ops/ms
Iteration 2: 155.308 ops/ms
Iteration 3: 154.542 ops/ms
Iteration 4: 155.963 ops/ms
Iteration 5: 156.462 ops/ms
Using a static provider with JSON_PROVIDER.createValue("test")
:
Iteration 1: 1112305.967 ops/ms
Iteration 2: 1114183.052 ops/ms
Iteration 3: 1113783.793 ops/ms
Iteration 4: 1111395.048 ops/ms
Iteration 5: 1112449.325 ops/ms
That's 7200+ times faster.
The createValue()
methods are the ones for which it's the most inefficient as they should be extremely lightweight as called quite often.
While we are mostly using Jackson in Quarkus itself and recommending Jackson, we have several components using JSON-P and JSON-B in the SmallRye world, for instance SmallRye GraphQL and SmallRye Health.
And we have people using JSON-P/JSON-B out there because they want to stay in the MicroProfile or Jakarta world.
I know SmallRye GraphQL is affected by the inefficiencies as I have seen some direct calls to the Json
methods in some runtime code (for instance here: https://github.com/smallrye/smallrye-graphql/blob/main/client/implementation/src/main/java/io/smallrye/graphql/client/impl/RequestImpl.java#L75).
From what I can see SmallRye Health is not affected as it creates a unique JsonProvider
.
Note that I haven't done any benchmarks in SmallRye GraphQL, I have just verified that we were instantiating too many JsonProvider
s.
As for the SmallRye components, I think we should discuss this subject in the context of Quarkus as the solution we want to put in place for Quarkus might be different from the ones we want to use in the general case (for instance in WildFly).
- we might want to have an SPI to push Jackson there. I discussed it a few times with @jmartisk already but it wasn't considered top priority
- even if we do, we probably need to fix the JSON-P/JSON-B case - while fixing our JSON-P calls could be easy (at least in the Quarkus case, I'm not sure if in the case of WildFly, we would want a single provider), I don't know if JSON-B uses the
Json
shortcut under the hood, in which case it might make things a bit harder to handle.