While performing benchmarks against 6.3.0 with virtual threads, I observed an unexpected degradation in parallelized throughput.
I honed the test down to everything stripped back (no audit, no expiry, async send, etc) and found that since memoryUsage is shared across all destinations the locking here accounted for the majority of wait time. Moving to a lock-free data structure for this metric removes the bottleneck.
LongAdder does use more memory than an AtomicLong (up to ~2KB), but makes sense in this use case.
To see the effect, run the benchmark from the main and then from the patched commit.
Measured on an 11-core Apple M3 Pro, JDK 25, direct dispatch, topic-per-producer:
threads stock (main) [#2275] LongAdder
1 5.19M msgs/sec 5.76M
4 1.49M 6.21M (+317%)
8 1.26M 5.66M (+348%)
22 1.07M 6.05M (+465%)
While performing benchmarks against 6.3.0 with virtual threads, I observed an unexpected degradation in parallelized throughput.
I honed the test down to everything stripped back (no audit, no expiry, async send, etc) and found that since memoryUsage is shared across all destinations the locking here accounted for the majority of wait time. Moving to a lock-free data structure for this metric removes the bottleneck.
LongAdder does use more memory than an AtomicLong (up to ~2KB), but makes sense in this use case.
To see the effect, run the benchmark from the main and then from the patched commit.
Measured on an 11-core Apple M3 Pro, JDK 25, direct dispatch, topic-per-producer: