Skip to content

Use batch-draining queues in SingleThreadExecutor#4836

Open
merlimat wants to merge 2 commits into
apache:masterfrom
merlimat:growable-batched-blocking-queue
Open

Use batch-draining queues in SingleThreadExecutor#4836
merlimat wants to merge 2 commits into
apache:masterfrom
merlimat:growable-batched-blocking-queue

Conversation

@merlimat

@merlimat merlimat commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Motivation

SingleThreadExecutor backs every OrderedExecutor thread — including the bookie read/write thread pools — so its queue is on the task-dispatch hot path. Today it uses:

  • GrowableMpScArrayConsumerBlockingQueue (unbounded, the default), draining element-by-element via drainTo(ArrayList)
  • JDK ArrayBlockingQueue (bounded, when maxTasksInQueue is set)

Meanwhile BatchedArrayBlockingQueue — purpose-built in the same package for the many-producers/one-consumer shape, with System.arraycopy batch draining — was unused by the executor. A JMH comparison (8 producers, consumers running the executor's exact drain loops) showed the growable queue is actually the slowest option, and the batched queue the fastest:

Queue put throughput
GrowableMpScArrayConsumerBlockingQueue (current unbounded) 32.7 Mops/s
JDK ArrayBlockingQueue (current bounded) 43.3 Mops/s
GrowableBatchedArrayBlockingQueue (new, unbounded) 48.5 Mops/s (+48%)
BatchedArrayBlockingQueue (bounded) 51.4 Mops/s (+19%)

Changes

  • Add GrowableBatchedArrayBlockingQueue: the unbounded companion of BatchedArrayBlockingQueue. Same single-lock design and two-span System.arraycopy batch operations (takeAll / pollAll / putAll), but with no notFull condition — producers never block; the backing array doubles when full (and never shrinks), like the queue it replaces.
  • SingleThreadExecutor now uses BatchedArrayBlockingQueue for the bounded case and the new growable queue for the unbounded case, and the run loop drains with takeAll into a reusable Runnable[] (capped at 1024) instead of per-element drainTo(ArrayList).
  • Remove GrowableMpScArrayConsumerBlockingQueue (and its test): the executor was its only production user, so it is fully superseded by the new queue. The stray usage in BatchedArrayBlockingQueueTest.blockingTake now tests BatchedArrayBlockingQueue itself, as intended.
  • New unit test covering the growth paths (including growth and batch-insert with wrapped indexes) and the batched operations.
  • New JMH benchmark (SingleThreadExecutorQueueBenchmark) comparing the queue options with executor-style consumers.

Verified: new queue test (9/9), BatchedArrayBlockingQueueTest, TestSingleThreadExecutor (10/10), TestOrderedExecutor, TestOrderedExecutorDecorators, plus BookieJournalTest as an integration smoke through the bookie path (16/16). Checkstyle clean.


@SuppressWarnings("unchecked")
public GrowableBatchedArrayBlockingQueue(int initialCapacity) {
data = (T[]) new Object[initialCapacity];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need check initialcapacity non-negative here.

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.

2 participants