aio-poll: refine iothread polling using weighted handler intervals
Improve adaptive polling by updating each AioHandler's poll.ns every loop iteration using weighted averages. This reduces CPU consumption while minimizing performance impact. Background: Starting from QEMU 10.0, poll.ns was introduced per event handler to mitigate excessive fluctuations in IOThread polling times observed in earlier versions (QEMU 9.x). However, the current design has limitations: 1. poll.ns is updated only when an event occurs, making it difficult to treat block_ns as a reliable event interval. 2. The IOThread's next polling time is determined by the maximum poll.ns among all AioHandlers, meaning idle AioHandlers with high poll.ns can have an outsized impact on polling duration. 3. For io_uring, idle AioHandlers are cleared after POLL_IDLE_INTERVAL_NS (7s), but for ppoll/epoll there is no such mechanism, leading to increased CPU consumption from idle nodes. Implementation: This patch treats block_ns as an event interval and updates each AioHandler's poll.ns in every loop iteration: - Active handlers (with events): poll.ns is updated using a weighted average of the current block_ns and previous poll.ns, smoothing out adjustments and preventing excessive fluctuations. - Inactive handlers (no events): poll.ns accumulates block_ns without weighting, allowing rapid isolation of idle nodes. When poll.ns exceeds poll_max_ns, it resets to 0, preventing sporadically active handlers from unnecessarily prolonging iothread polling. - The iothread polling duration is set based on the largest poll.ns among active handlers. The shrink divider defaults to 2, matching the grow rate, to reduce frequent poll_ns resets for slow devices. The implementation renames poll_idle_timeout to last_dispatch_timestamp for use as an active handler identifier. Testing: POLL_WEIGHT_SHIFT=3 (12.5% weight) was selected based on testing comparing baseline vs weight=2/3 across various workloads: Performance results (RHEL 10.1 + QEMU 10.0.0, FCP/FICON, 1-8 iothreads, numjobs 1/4/8 averaged): | poll-weight=2 | poll-weight=3 --------------------|--------------------|----------------- Throughput avg | -2.4% (all tests) | -2.2% (all tests) CPU consumption avg | -10.9% (all tests) | -9.4% (all tests) Both configurations achieve ~10% CPU reduction with minimal throughput impact (~2%). Weight=3 is chosen as default for slightly better throughput while maintaining substantial CPU savings. Additional validation testing on s390x SSD with fio (bs=8k, iodepth=8, numjobs=1) shows how poll_weight affects polling time (poll.ns) behavior: RandRead workload: +-------------+-----------+-----------+-------------+-------------+ | poll_weight | #samples | Mean (ns) | 50th % (ns) | 90th % (ns) | +-------------+-----------+-----------+-------------+-------------+ | 1 | 4.79M | 8,034 | 5,116 | 20,509 | | 2 | 5.01M | 12,584 | 11,078 | 24,693 | | 3 | 5.01M | 15,647 | 14,863 | 28,695 | | 4 | 5.12M | 16,430 | 15,556 | 30,848 | | 5 | 5.14M | 16,461 | 15,306 | 32,123 | +-------------+-----------+-----------+-------------+-------------+ RandWrite workload: +-------------+-----------+-----------+-------------+-------------+ | poll_weight | #samples | Mean (ns) | 50th % (ns) | 90th % (ns) | +-------------+-----------+-----------+-------------+-------------+ | 1 | 6.37M | 2,049 | 1,262 | 4,301 | | 2 | 7.46M | 4,118 | 3,226 | 7,476 | | 3 | 7.97M | 7,034 | 5,984 | 11,645 | | 4 | 7.96M | 12,789 | 11,362 | 20,040 | | 5 | 7.82M | 22,992 | 20,644 | 32,768 | +-------------+-----------+-----------+-------------+-------------+ Signed-off-by: Jaehoon Kim <jhkim@linux.ibm.com> Message-ID: <20260423195918.661299-3-jhkim@linux.ibm.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>