Skip to content

[WIP] Optimize json marshaller#6857

Draft
alextwoods wants to merge 2 commits intomasterfrom
alexwoo/json_marshall_opt
Draft

[WIP] Optimize json marshaller#6857
alextwoods wants to merge 2 commits intomasterfrom
alexwoo/json_marshall_opt

Conversation

@alextwoods
Copy link
Copy Markdown
Contributor

@alextwoods alextwoods commented Apr 13, 2026

Optimized JSON marshalling performance for JSON RPC and REST JSON protocols by replacing registry lookup and interface dispatch with a switch-based dispatch on MarshallingKnownType for payload fields, and caching the resolved marshaller on SdkField for non-payload fields.

Motivation and Context

Profiling of JSON RPC marshalling (used by DynamoDB, Lambda, etc.) identified two performance bottlenecks:

  1. Megamorphic interface dispatch (8-16% self-time): The marshaller.marshall(val, ctx, name, field) call site in marshallField dispatches through JsonMarshaller<T>, an interface with 13+ implementations. The JIT sees many concrete types flowing through this single call site across loop iterations, making it megamorphic and preventing inlining.

  2. Redundant registry lookups (3-10% self-time): Every field marshalled goes through a multi-step registry lookup chain (getMarshaller → two EnumMap.get calls), even though SdkField instances are static final and the marshaller for a given (location, marshallingType) pair never changes.

JMH benchmarks on representative DynamoDB workloads show 10-25% improvement in marshalling throughput, with the largest absolute wins on mixed-type and nested-structure payloads.

Modifications

SdkField.java (sdk-core):

  • Added two private volatile Object fields (cachedMarshaller, cachedMarshallerRegistryKey) implementing a single-slot marshaller cache with reference-identity key comparison.
  • Added cachedMarshaller and cacheMarshaller public methods.

JsonProtocolMarshaller.java (aws-json-protocol):

  • Added marshallPayloadField — a switch on MarshallingKnownType that handles all 16 known types. Simple types (STRING, INTEGER, LONG, etc.) are inlined as direct StructuredJsonGenerator.writeValue() calls, giving the JIT monomorphic call sites it can inline. Composite types (SDK_POJO, LIST, MAP, DOCUMENT) and INSTANT delegate to the existing SimpleTypeJsonMarshaller constants to avoid logic duplication. Null getKnownType() and unrecognized values fall back to the registry path.
  • Modified doMarshall routing: non-null PAYLOAD fields → marshallPayloadField (switch dispatch); non-null non-PAYLOAD fields → marshallFieldViaRegistry (cached registry); null non-PAYLOAD fields → marshallFieldViaRegistry (uncached, for null marshaller); null PAYLOAD + RequiredTrait → throws; null PAYLOAD + no RequiredTrait → no-op. Explicit payload handling (binary, string, JSON) is unchanged.

Testing

New and existing unit tests + protocol tests.

Benchmark data

Data is from running standard SERDE tests from: #6860

Summary

Protocol n Mean Change Median Change
RestJson 13 -4.2% -3.9%
awsJsonRpc 21 -8.8% -7.7%
Overall 34 -7.1% -6.1%

The Big wins:

Test Case Protocol Baseline (ns) Optimized (ns) p50 Change Significant
PutItemRequest_MixedItem_L awsJsonRpc 36857.7 29485.8 -19.9% Yes
PutItemRequest_MixedItem_M awsJsonRpc 12728.7 10367.8 -18.2% Yes
PutItemRequest_MixedItem_S awsJsonRpc 2518.0 2193.3 -13.6% Yes
PutItemRequest_Nested_L awsJsonRpc 5269.7 4346.7 -17.7% Yes
PutItemRequest_Nested_M awsJsonRpc 1682.5 1575.7 -8.9% Yes
PutItemRequest_ShallowMap_L awsJsonRpc 39645.9 30773.5 -22.8% Yes
PutItemRequest_ShallowMap_M awsJsonRpc 12953.0 9947.3 -23.2% Yes
PutItemRequest_ShallowMap_S awsJsonRpc 2292.8 1928.7 -15.1% Yes

All data

Test Case Protocol Baseline (ns) Optmized (ns) p50 Change Significant
PutItemRequest_MixedItem_L awsJsonRpc 36857.7 29485.8 -19.9% Yes
PutItemRequest_MixedItem_M awsJsonRpc 12728.7 10367.8 -18.2% Yes
PutItemRequest_MixedItem_S awsJsonRpc 2518.0 2193.3 -13.6% Yes
PutItemRequest_Nested_L awsJsonRpc 5269.7 4346.7 -17.7% Yes
PutItemRequest_Nested_M awsJsonRpc 1682.5 1575.7 -8.9% Yes
PutItemRequest_ShallowMap_L awsJsonRpc 39645.9 30773.5 -22.8% Yes
PutItemRequest_ShallowMap_M awsJsonRpc 12953.0 9947.3 -23.2% Yes
PutItemRequest_ShallowMap_S awsJsonRpc 2292.8 1928.7 -15.1% Yes
GetItemInput_Baseline awsJsonRpc 759.9 701.0 -7.6% Yes
GetMetricDataRequest_L awsJsonRpc 12819.9 13158.6 +1.9% Yes
GetMetricDataRequest_M awsJsonRpc 7169.5 6731.9 -6.6% Yes
GetMetricDataRequest_S awsJsonRpc 2051.0 1942.2 -5.2% Yes
HealthcheckRequest_Example awsJsonRpc 417.5 397.8 -3.4% No
PutItemRequest_Baseline awsJsonRpc 817.6 733.4 -9.9% Yes
PutItemRequest_BinaryData_L awsJsonRpc 101521.7 104445.6 +3.0% Yes
PutItemRequest_BinaryData_M awsJsonRpc 13218.9 14547.4 +9.2% Yes
PutItemRequest_BinaryData_S awsJsonRpc 1058.8 984.9 -6.9% Yes
PutMetricDataRequest_Baseline awsJsonRpc 455.3 455.2 -0.5% No
PutMetricDataRequest_L awsJsonRpc 54927.9 52851.1 -3.5% Yes
PutMetricDataRequest_M awsJsonRpc 6316.0 5739.8 -8.8% Yes
PutMetricDataRequest_S awsJsonRpc 1378.9 1240.4 -9.8% Yes
CopyObjectRequest_Baseline RestJson 1050.4 991.3 -3.7% Yes
CopyObjectRequest_M RestJson 4644.9 4404.4 -5.2% Yes
GetMetricDataRequest_L RestJson 12881.9 13149.0 +1.7% Yes
GetMetricDataRequest_M RestJson 7215.3 6467.2 -9.2% Yes
GetMetricDataRequest_S RestJson 2079.4 1951.3 -6.3% Yes
HealthcheckRequest_Example RestJson 169.0 164.4 -1.4% No
PutMetricDataRequest_Baseline RestJson 464.7 473.3 +0.9% No
PutMetricDataRequest_L RestJson 54541.3 52702.3 -3.0% Yes
PutMetricDataRequest_M RestJson 6318.7 5820.1 -7.9% Yes
PutMetricDataRequest_S RestJson 1344.3 1236.0 -9.6% Yes
PutObject_L RestJson 2501.3 2403.8 -4.0% Yes
PutObject_M RestJson 2544.4 2460.8 -4.8% No
PutObject_S RestJson 2409.9 2363.7 -4.2% No

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the CONTRIBUTING document
  • Local run of mvn install succeeds
  • My code follows the code style of this project
  • I have added tests to cover my changes
  • All new and existing tests passed
  • I have added a changelog entry. Adding a new entry must be accomplished by running the scripts/new-change script and following the instructions. Commit the new file created by the script in .changes/next-release with your changes.

License

  • I confirm that this pull request can be released under the Apache 2 license

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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