[
https://issues.apache.org/jira/browse/IGNITE-28836?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18096018#comment-18096018
]
Anton Vinogradov commented on IGNITE-28836:
-------------------------------------------
[Ignite PR Checker|https://ignite-pr-checker.is-a.dev/?pr=13296] verdict for PR
13296 · RunAll build [9206164|https://ci2.ignite.apache.org/build/9206164] ·
147 suites ran, 0 reused
(!) *2 broken suite(s)* (failed without a reliable run):
- Platform C++ CMake (Linux): Number of tests 595 is 42% less than 1020 in
build #42448
- Platform C++ CMake (Win x64 | Release): Number of tests 595 is 43% less than
1046 in build #18193
(/) No test blockers otherwise; 35 pre-existing/flaky filtered out.
⏳ _Auto re-run *#1* in progress — 2 broken suite(s) re-queued (attempt 1/2), *≈
settled by 03:04 MSK*. This comment updates when they settle._
> DirectMessageWriter: reduce per-field overhead and per-message allocations on
> the message serialization hot path
> ----------------------------------------------------------------------------------------------------------------
>
> Key: IGNITE-28836
> URL: https://issues.apache.org/jira/browse/IGNITE-28836
> Project: Ignite
> Issue Type: Task
> Reporter: Anton Vinogradov
> Assignee: Anton Vinogradov
> Priority: Major
> Time Spent: 40m
> Remaining Estimate: 0h
>
> *Motivation*
> DirectMessageWriter is on the critical path of every outgoing message. Two
> inefficiencies:
> # Each of the ~30 write methods re-resolves \{{state.item().stream}} (array
> load + bounds check + field load) on every primitive write.
> # \{{writeCompressedMessage()}} allocates a fresh 10K
> \{{ByteBuffer.allocateDirect()}} per compressed field (plus a doubling
> re-allocation chain for payloads above 10K) and a brand-new
> \{{DirectMessageWriter}} per field.
> *Fix*
> # Cache the current stream in \{{curStream}}, refreshed only when the current
> state item changes (\{{setBuffer}} / \{{beforeNestedWrite}} /
> \{{afterNestedWrite}}).
> # \{{CompressedMessage}} consumes the scratch buffer right in its constructor
> (deflates into its own byte[]), so the buffer never escapes
> \{{writeCompressedMessage()}}: the writer now keeps one reusable heap scratch
> buffer (retained at the largest size seen) and a thread-confined reusable
> \{{tmpWriter}}, and grows the scratch buffer without an intermediate byte[]
> copy.
> No wire-format or public API changes; behavior-preserving, safe to backport.
> *Benchmark* (\{{JmhDirectMessageWriterBenchmark}}, added by the patch: an
> exchange-style message with two compressed map fields sized below/above the
> initial 10K scratch, and a ~35-field primitive message; JDK 17, -prof gc,
> master vs patched)
> ||benchmark||master||patched||delta||
> |compressedMapFields(30), throughput|17 550 ops/s|20 037 ops/s|*+14%*|
> |compressedMapFields(30), allocations|26.7 KB/op heap + 20 KB/op direct|24.3
> KB/op heap, zero direct|-9% heap, no Cleaner churn|
> |compressedMapFields(30), GC time|192 ms|9 ms|*x21 less*|
> |compressedMapFields(500), throughput|2 382 ops/s|2 475 ops/s|+4% (within
> error)|
> |compressedMapFields(500), allocations|365 KB/op heap + ~300 KB/op direct|322
> KB/op heap, zero direct|-12% heap, no Cleaner churn|
> |compressedMapFields(500), GC time|56 ms|11 ms|*x5 less*|
> |primitiveFields, throughput|34.9M ops/s|35.3M ops/s|within error|
> Master's direct-buffer churn is invisible to gc.alloc.rate.norm but shows up
> as GC time: Cleaner processing makes collections an order of magnitude more
> expensive at the same collection counts.
> *Testing*
> * DirectMarshallingMessagesTest — nested containers written through 16-byte
> chunks (multi-pass resume across buffer swaps, exercises the curStream
> refresh points).
> * CompressedMessageTest — >40K compressed payload (exercises the scratch
> growth path), byte-for-byte writer-to-reader round-trip.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)