[ISSUE #10522] Reduce Remoting header encoding allocation via FastCodesHeader/RocketMQSerializable optimizations#10524
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR optimizes remoting header serialization/deserialization by adding fast-path encoding for numeric values and single-byte strings, and tweaks map allocation to reduce overhead.
Changes:
- Add decimal (ASCII) serialization helpers for
int/longvalues written into aByteBuf. - Add a cached fast-path for reading 1-byte UTF-8 strings (ASCII) to avoid allocations/decoding overhead.
- Update header encoding to use the new decimal writers for
Long/Integervalues and adjustHashMapinitial capacity.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RocketMQSerializable.java | Adds decimal numeric writers, single-byte string cache/fast-path, and updates deserialization map pre-sizing. |
| remoting/src/main/java/org/apache/rocketmq/remoting/protocol/FastCodesHeader.java | Uses new decimal writers for Long/Integer fields and introduces typed writeLong/writeInt helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…astCodesHeader/RocketMQSerializable optimizations - Add writeDecimalLong/writeDecimalInt to RocketMQSerializable for direct primitive decimal encoding - Add writeLong/writeInt helpers to FastCodesHeader - Optimize writeIfNotNull to route Long/Integer through primitive writers - Cache single-byte ASCII strings in RocketMQSerializable.readStr - Right-size HashMap initial capacity in mapDeserialize (128 -> 24)
e7659fa to
9a009d9
Compare
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Review by github-manager-bot
Summary
This PR optimizes the Remoting header encoding/decoding hot path by eliminating transient String allocations for numeric values and adding a single-byte ASCII string cache. The changes are purely additive with impressive benchmark results (+20.6% TPS, -33% p99 RT).
Findings
-
[Positive]
FastCodesHeader.java:41-47— RoutingLong/IntegerthroughwriteDecimalLong/writeDecimalIntinstead ofvalue.toString()+writeStris a clean and effective allocation elimination. Theinstanceofbranching cost is negligible compared to the allocation savings on the hot path. -
[Positive]
RocketMQSerializable.javawriteDecimalLong/writeDecimalInt— TheLong.MIN_VALUE/Integer.MIN_VALUEedge cases are correctly handled by dividing by 10 first and appending the last digit, avoiding the overflow that-valuewould cause. Well done. -
[Positive]
SINGLE_BYTE_STRINGScache — 128-entry ASCII cache is a good trade-off: ~5-6KB static memory vs. avoidingnew String()for the most common single-byte header values. ThereadStrfast path (len == 1 && b >= 0) is correct for UTF-8 single-byte range. -
[Positive]
mapDeserializecapacity 128 → 24 — Good observation. Typical headers have 5-15 entries; 128 was wasteful. With load factor 0.75, capacity 24 supports 18 entries before resize, which covers the common case. -
[Info]
FastCodesHeader.java:41— Consider reordering theinstanceofchecks to testStringfirst (most common header value type), thenLong, thenInteger. This avoids two unnecessary type checks for the majority of calls. Minor optimization, non-blocking.
if (value instanceof String) {
RocketMQSerializable.writeStr(out, false, (String) value);
} else if (value instanceof Long) {
RocketMQSerializable.writeDecimalLong(out, (Long) value);
} else if (value instanceof Integer) {
RocketMQSerializable.writeDecimalInt(out, (Integer) value);
} else {
RocketMQSerializable.writeStr(out, false, value.toString());
}- [Info]
writePositiveDigitsuses a two-pass approach (count digits, then write). An alternative is a pre-allocated digit lookup table indexed by value, but the current approach is clean and the performance difference is negligible for numbers up to 19 digits.
Compatibility
- All changes are additive — no existing method signatures modified.
- New
defaultmethods inFastCodesHeaderinterface do not break existing implementations. SINGLE_BYTE_STRINGSis a private static field with no external visibility.- Wire format is unchanged:
writeDecimalLong/writeDecimalIntproduce the same byte output aswriteStr(out, false, String.valueOf(value)).
Tests
- Unit tests cover
RocketMQSerializableandFastCodesHeaderdirectly. - Benchmark data is well-documented with clear before/after comparison.
- Consider adding an edge-case test for
writeDecimalLong(Long.MIN_VALUE)andwriteDecimalInt(Integer.MIN_VALUE)to prevent regression of the overflow handling.
Automated review by github-manager-bot
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #10524 +/- ##
=============================================
- Coverage 48.19% 48.07% -0.12%
+ Complexity 13394 13376 -18
=============================================
Files 1377 1377
Lines 100730 100801 +71
Branches 13012 13029 +17
=============================================
- Hits 48542 48459 -83
- Misses 46254 46400 +146
- Partials 5934 5942 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Which Issue(s) This PR Fixes
Fixes #10522
Brief Description
Enhance
FastCodesHeaderinterface andRocketMQSerializableutility to reduce allocation and CPU overhead in the Remoting header encoding/decoding hot path.FastCodesHeader.javawriteIfNotNullto routeLong/Integervalues throughwriteDecimalLong/writeDecimalIntinstead ofvalue.toString()+writeStr, eliminating transientStringallocation.writeLong(ByteBuf, String, long)andwriteInt(ByteBuf, String, int)helper methods for direct primitive encoding.RocketMQSerializable.javawriteDecimalLong(ByteBuf, long)andwriteDecimalInt(ByteBuf, int)— write decimal numbers directly toByteBufwithoutString.valueOf()allocation.SINGLE_BYTE_STRINGScache (128 entries) —readStrreturns cachedStringfor single-byte ASCII values instead of creating newStringviareadCharSequence.mapDeserializeinitialHashMapcapacity from 128 to 24 — typical headers have 5-15 entries, 128 causes unnecessary memory waste.All changes are additive — no existing method signatures are modified.
How Did You Test This Change?
1. Unit tests
2. A/B benchmark (256 threads, 1KB sync send, Dragonwell JDK 21, 6g heap)
3. Commercial compatibility
FastCodesHeader.YitianRemotingCommandcallsRocketMQSerializable.rocketMQProtocolDecode/Encodewhich are unchanged.