Skip to content

Hoist UTF-8 selection and specialize Timestamp/Duration typed accessors; add regression test and perf baseline - #5

Draft
Fyzu wants to merge 1 commit into
mainfrom
codex/analyze-protobuf-encode-performance-improvements
Draft

Fyzu wants to merge 1 commit into
mainfrom
codex/analyze-protobuf-encode-performance-improvements

Conversation

@Fyzu

@Fyzu Fyzu commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Motivation

  • Reduce per-field dispatch and avoid repeated JSONWriter.isUTF8() checks by hoisting the UTF-8 selection once per message and enable faster typed runtime encoding.
  • Improve runtime-typed encoding for well-known Timestamp and Duration fields by using direct seconds/nanos formatting matching generated encoders and avoid descriptor-based generic WKT dispatch in hot paths.
  • Add a regression to ensure repeated Timestamp/Duration arrays serialize identically across all encoder paths and record a Java 25 runtime encode/decode baseline to guide further runtime specialization.

Description

  • Hoist UTF-8 selection into TypedMessageSchema.writeFields and change FieldName.writeTo to accept an isUtf8 boolean so callers pass the chosen form once per message.
  • Add a boolean utf8 parameter to TypedFieldAccessor.write and update all accessor implementations to use name.writeTo(jw, utf8).
  • Introduce typed handling for Timestamp/Duration values and collections by adding writeTimestampValue, writeDurationValue, PresenceTimestampAccessor, PresenceDurationAccessor, RepeatedTimestampAccessor, and RepeatedDurationAccessor, and wire them through TypedFieldAccessorFactory via createPresenceMessageAccessor and createRepeatedMessageAccessor.
  • Ensure safe fallback to generic WellKnownTypes.write for non-standard Message implementations and update a few javadocs/comments related to typed accessors and well-known types.
  • Add a unit regression repeatedTimestampAndDurationMatchAcrossAllPaths in BuffJsonEncodingRegressionTest and include Java 25 runtime encode/decode baseline artifacts: a measurement report (.md) and CSV results.

Testing

  • Ran the project unit test suite (mvn test) and the tests completed successfully, including the new repeatedTimestampAndDurationMatchAcrossAllPaths regression.
  • No new benchmark jobs were executed in CI; the PR includes measured JMH baseline artifacts (docs/performance-results/* and docs/java25-runtime-encode-performance.md) for reference.
  • Existing automated tests covering protobuf JSON encoding were preserved and passed after the changes.

Codex Task

@github-actions

Copy link
Copy Markdown
Contributor

Performance comparison

Workflow and raw JMH artifacts
Commit: e80f7bb

Java 21 performance

Base: ad10fe5 → candidate: e80f7bb
Shared benchmark source: ddb898473de6f0f66081d1bdef7adf18cd447119

Throughput alerts are advisory. Existing allocation budgets are enforced separately.
A timing signal needs at least 10% change and separated JMH 99.9% intervals; otherwise it is inconclusive.
Allocation alerts need both >5% and >16 B/op growth (or >16 B/op from zero).

Benchmark Base ops/s Candidate ops/s Change Timing B/op base → candidate Allocation
simpleCodegenUtf16 10,524,751 ±258,149 10,621,331 ±149,459 +0.9% inconclusive 295.5 → 295.5 within alert threshold
simpleCodegenUtf8 10,923,849 ±88,253 11,076,167 ±294,085 +1.4% inconclusive 271.5 → 271.5 within alert threshold
simpleTypedUtf16 6,450,336 ±1,250,305 7,133,990 ±90,270 +10.6% inconclusive 295.5 → 295.5 within alert threshold
simpleTypedUtf8 7,483,660 ±214,447 7,457,128 ±215,664 -0.4% inconclusive 271.5 → 271.5 within alert threshold
simpleReflectionUtf16 3,544,336 ±340,150 3,653,461 ±317,299 +3.1% inconclusive 340.0 → 340.0 within alert threshold
simpleReflectionUtf8 3,880,537 ±32,086 3,700,673 ±326,704 -4.6% inconclusive 316.0 → 316.0 within alert threshold
complexCodegenUtf16 698,155 ±12,150 698,716 ±7,847 +0.1% inconclusive 1481.9 → 1481.9 within alert threshold
complexCodegenUtf8 730,587 ±48,018 716,738 ±4,796 -1.9% inconclusive 1433.9 → 1457.9 within alert threshold
complexTypedUtf16 658,380 ±23,456 647,915 ±12,597 -1.6% inconclusive 1289.9 → 1289.9 within alert threshold
complexTypedUtf8 673,596 ±4,501 698,535 ±4,167 +3.7% inconclusive 1265.9 → 1265.9 within alert threshold
complexReflectionUtf16 371,311 ±8,936 377,562 ±2,628 +1.7% inconclusive 1353.9 → 1353.9 within alert threshold
complexReflectionUtf8 397,916 ±2,319 394,998 ±11,964 -0.7% inconclusive 1329.9 → 1329.9 within alert threshold
mapCodegenUtf16 101,645 ±3,962 104,495 ±3,174 +2.8% inconclusive 5025.2 → 5025.3 within alert threshold
mapCodegenUtf8 103,839 ±9,318 101,559 ±4,477 -2.2% inconclusive 5001.3 → 5001.3 within alert threshold
mapTypedUtf16 75,397 ±6,172 73,923 ±4,418 -2.0% inconclusive 4805.5 → 4805.6 within alert threshold
mapTypedUtf8 76,050 ±4,781 76,764 ±4,162 +0.9% inconclusive 4781.6 → 4781.6 within alert threshold
mapReflectionUtf16 48,565 ±1,676 49,431 ±2,801 +1.8% inconclusive 7634.1 → 7634.0 within alert threshold
mapReflectionUtf8 51,398 ±3,398 50,001 ±848 -2.7% inconclusive 7610.1 → 7610.1 within alert threshold
structCodegenUtf16 732,411 ±4,441 753,058 ±15,089 +2.8% inconclusive 735.7 → 735.7 within alert threshold
structCodegenUtf8 710,290 ±13,610 703,799 ±3,314 -0.9% inconclusive 711.7 → 711.7 within alert threshold
structTypedUtf16 740,686 ±44,751 739,977 ±6,656 -0.1% inconclusive 735.7 → 735.7 within alert threshold
structTypedUtf8 709,858 ±18,798 692,814 ±18,926 -2.4% inconclusive 711.7 → 711.7 within alert threshold
structReflectionUtf16 706,195 ±5,312 716,799 ±5,201 +1.5% inconclusive 735.7 → 735.7 within alert threshold
structReflectionUtf8 670,283 ±6,391 675,229 ±23,056 +0.7% inconclusive 711.7 → 711.7 within alert threshold
timestampCodegenUtf16 4,192,168 ±125,961 4,273,229 ±18,862 +1.9% inconclusive 464.0 → 464.0 within alert threshold
timestampCodegenUtf8 4,555,874 ±35,002 4,551,910 ±19,672 -0.1% inconclusive 440.0 → 440.0 within alert threshold
timestampTypedUtf16 3,471,225 ±81,448 3,769,463 ±43,491 +8.6% inconclusive 464.0 → 464.0 within alert threshold
timestampTypedUtf8 3,696,916 ±66,703 4,005,862 ±33,470 +8.4% inconclusive 440.0 → 440.0 within alert threshold
timestampReflectionUtf16 2,617,309 ±26,866 2,604,524 ±6,940 -0.5% inconclusive 464.0 → 464.0 within alert threshold
timestampReflectionUtf8 2,720,237 ±118,686 2,785,666 ±29,537 +2.4% inconclusive 440.0 → 440.0 within alert threshold

Java 25 performance

Base: ad10fe5 → candidate: e80f7bb
Shared benchmark source: ddb898473de6f0f66081d1bdef7adf18cd447119

Throughput alerts are advisory. Existing allocation budgets are enforced separately.
A timing signal needs at least 10% change and separated JMH 99.9% intervals; otherwise it is inconclusive.
Allocation alerts need both >5% and >16 B/op growth (or >16 B/op from zero).

Benchmark Base ops/s Candidate ops/s Change Timing B/op base → candidate Allocation
simpleCodegenUtf16 13,118,749 ±1,586,071 12,557,705 ±588,887 -4.3% inconclusive 295.5 → 295.5 within alert threshold
simpleCodegenUtf8 16,177,686 ±1,660,980 16,749,920 ±89,915 +3.5% inconclusive 271.5 → 271.5 within alert threshold
simpleTypedUtf16 10,786,153 ±1,043,315 10,309,971 ±1,136,207 -4.4% inconclusive 295.5 → 295.5 within alert threshold
simpleTypedUtf8 10,571,036 ±1,377,098 12,122,861 ±442,342 +14.7% inconclusive 271.5 → 271.5 within alert threshold
simpleReflectionUtf16 5,276,987 ±309,030 5,803,090 ±267,011 +10.0% inconclusive 340.0 → 340.0 within alert threshold
simpleReflectionUtf8 5,992,004 ±342,975 6,157,083 ±347,784 +2.8% inconclusive 316.0 → 316.0 within alert threshold
complexCodegenUtf16 1,063,763 ±105,648 1,083,825 ±18,229 +1.9% inconclusive 1481.9 → 1481.9 within alert threshold
complexCodegenUtf8 1,207,612 ±8,568 1,196,020 ±92,869 -1.0% inconclusive 1457.9 → 1457.9 within alert threshold
complexTypedUtf16 1,054,163 ±65,797 1,105,327 ±27,044 +4.9% inconclusive 1289.9 → 1289.9 within alert threshold
complexTypedUtf8 1,157,791 ±11,565 1,156,285 ±67,695 -0.1% inconclusive 1265.9 → 1265.9 within alert threshold
complexReflectionUtf16 669,442 ±57,182 698,211 ±6,561 +4.3% inconclusive 1353.9 → 1353.9 within alert threshold
complexReflectionUtf8 715,658 ±37,198 701,358 ±48,673 -2.0% inconclusive 1329.9 → 1329.9 within alert threshold
mapCodegenUtf16 167,580 ±1,159 167,006 ±2,158 -0.3% inconclusive 4589.5 → 4589.5 within alert threshold
mapCodegenUtf8 160,430 ±11,155 170,239 ±4,259 +6.1% inconclusive 4565.5 → 4565.5 within alert threshold
mapTypedUtf16 129,449 ±8,420 134,331 ±2,195 +3.8% inconclusive 4805.5 → 4805.5 within alert threshold
mapTypedUtf8 143,170 ±3,369 142,421 ±7,042 -0.5% inconclusive 4781.5 → 4781.5 within alert threshold
mapReflectionUtf16 87,772 ±4,045 83,325 ±9,189 -5.1% inconclusive 7634.1 → 7634.1 within alert threshold
mapReflectionUtf8 88,346 ±1,971 85,708 ±7,451 -3.0% inconclusive 7610.1 → 7610.1 within alert threshold
structCodegenUtf16 750,893 ±23,094 748,362 ±34,444 -0.3% inconclusive 735.7 → 735.7 within alert threshold
structCodegenUtf8 762,038 ±7,642 756,872 ±3,464 -0.7% inconclusive 711.7 → 711.7 within alert threshold
structTypedUtf16 743,061 ±54,784 748,861 ±58,241 +0.8% inconclusive 735.7 → 735.7 within alert threshold
structTypedUtf8 752,760 ±33,540 757,077 ±12,555 +0.6% inconclusive 711.7 → 711.7 within alert threshold
structReflectionUtf16 743,233 ±20,202 715,432 ±56,228 -3.7% inconclusive 735.7 → 735.7 within alert threshold
structReflectionUtf8 746,101 ±12,439 717,819 ±66,698 -3.8% inconclusive 711.7 → 711.7 within alert threshold
timestampCodegenUtf16 5,898,912 ±512,835 5,877,493 ±416,266 -0.4% inconclusive 464.0 → 464.0 within alert threshold
timestampCodegenUtf8 6,171,915 ±287,104 6,120,851 ±170,957 -0.8% inconclusive 440.0 → 440.0 within alert threshold
timestampTypedUtf16 5,405,782 ±485,355 5,844,804 ±99,141 +8.1% inconclusive 464.0 → 464.0 within alert threshold
timestampTypedUtf8 6,535,873 ±259,808 5,916,549 ±402,825 -9.5% inconclusive 440.0 → 440.0 within alert threshold
timestampReflectionUtf16 4,292,367 ±35,377 4,153,841 ±413,081 -3.2% inconclusive 464.0 → 464.0 within alert threshold
timestampReflectionUtf8 4,829,771 ±89,879 4,740,780 ±355,300 -1.8% inconclusive 440.0 → 440.0 within alert threshold

@Fyzu Fyzu removed the codex label Sep 23, 2026
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