Skip to content

Conversation

@RanVaknin
Copy link
Contributor

@RanVaknin RanVaknin commented Oct 23, 2025

This PR addresses a performance degradation issue between v1 and v2. After considering multiple implementation approaches, the proposed optimization approach yielded roughly 10% reduction in latency from the v1 performance.

toJson() Benchmarks

Metrics p0.90 p0.95 p0.99 Change (p90)
v1 611µs 634µs 739µs baseline
v2 Current 2,953µs 2,527µs 2,404µs +383%
v2 SDKJsonGenerator - writes to byteArrayOutputStream (enum based dispatch) 550µs 564µs 613µs - 10%
v2 JsonGenerator - writes to stringWriter (enum based dispatch) 680µs 719µs 1541µs +11%
v2 SDKJsonGenerator (static method dispatch) 918µs 935µs 1,118µs +50%

getJson() Benchmarks

2KB payload

Metrics p0.50 p0.90 p0.99 Change (p90)
v1 1.780µs 1.834µs 2.332µs baseline
v2 Current 8.640µs 9.360µs 11.248µs +410%
v2 Optimized 2.231µs 2.332µs 2.872µs +27%

50KB payload

Metrics p0.50 p0.90 p0.99 Change (p90)
v1 593µs 610µs 699µs baseline
v2 Current 2,442µs 2,744µs 3,498µs +350%
v2 Optimized 548µs 572µs 690µs -6%

Key Design Decisions

1. Enum based dispatch outperforms static methods through JIT optimization

When the JVM encounters the enum approach, it sees strategy.serialize(generator, av) calls where each enum constant (STRING, NUMBER, etc.) is a singleton instance. The JIT compiler recognizes these as monomorphic call sites - meaning each location in the code consistently calls the same concrete method.

// Enum approach - JIT sees monomorphic call sites
JsonSerializationStrategy strategy = getStrategy(av);
strategy.serialize(generator, av); // Inlines to direct method call

In contrast, the static method approach forces all serialization logic through a single serializeAttributeValue() method containing if-else branches. While the JIT can optimize the branching logic, it cannot eliminate the method call itself or the internal conditional overhead.

// Static approach - retains method call overhead
serializeAttributeValue(generator, av); // Must evaluate all conditions

The JFR data confirms that the enum implementation approach generated 1,333 Jackson Generator method calls compared to 2,159 in the static approach.

2. SdkJsonGenerator (ByteArrayOutputStream) over JsonGenerator (StringWriter)

Tested both serialization approaches with identical enum dispatch logic:
SdkJsonGenerator wraps JsonFactory.createGenerator(ByteArrayOutputStream) → creates UTF8JsonGenerator - 19% faster (550µs vs 680µs at p90)
JsonGenerator with JsonFactory.createGenerator(StringWriter) → creates WriterBasedJsonGenerator

⚠️ Potential behavior change

UTF8JsonGenerator escapes non ASCII as \uXXXX by default. Jackson 2.18+ provides COMBINE_UNICODE_SURROGATES_IN_UTF8 feature to output literal UTF-8 (verified via test), but the SDK bundles older shaded Jackson version that lacks this feature.
see FasterXML/jackson-core#223

@sonarqubecloud
Copy link

@RanVaknin RanVaknin force-pushed the rvaknin/optimize-to-json-performance branch from c0c396a to 6b2036a Compare October 27, 2025 06:45
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.

1 participant