@samitouri / QOSamiQemu / commits / 9fea80ddf0

qapi/iothread: introduce poll-weight parameter for aio-poll

Introduce a configurable poll-weight parameter for adaptive polling in IOThread. This parameter replaces the hardcoded POLL_WEIGHT_SHIFT constant, allowing runtime control over how much the most recent event interval affects the next polling duration calculation. The poll-weight parameter uses a shift value where larger values decrease the weight of the current interval, enabling more gradual adjustments. When set to 0, a default value of 3 is used (meaning the current interval contributes approximately 1/8 to the weighted average). This patch also removes the hardcoded default value checks from adjust_polling_time(). Instead, poll-grow, poll-shrink, and poll-weight now use default values initialized in iothread.c during IOThread creation. Signed-off-by: Jaehoon Kim <jhkim@linux.ibm.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Acked-by: Markus Armbruster <armbru@redhat.com> Message-ID: <20260423195918.661299-4-jhkim@linux.ibm.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>

Jaehoon Kim committed Apr 23, 2026 at 14:59 UTC 9fea80ddf0f58fed94f7e6d7d5c9e0ccf5cff44a
11 files changed +95 -42
include/qemu/aio.h
+3 -1
@@ -310,6 +310,7 @@ struct AioContext {
310 int64_t poll_max_ns; /* maximum polling time in nanoseconds */
311 int64_t poll_grow; /* polling time growth factor */
312 int64_t poll_shrink; /* polling time shrink factor */
313 + int64_t poll_weight; /* weight of current interval in calculation */
314
315 /* AIO engine parameters */
316 int64_t aio_max_batch; /* maximum number of requests in a batch */
@@ -791,12 +792,13 @@ void aio_context_destroy(AioContext *ctx);
792 * @max_ns: how long to busy poll for, in nanoseconds
793 * @grow: polling time growth factor
794 * @shrink: polling time shrink factor
795 + * @weight: weight factor applied to the current polling interval
796 *
797 * Poll mode can be disabled by setting poll_max_ns to 0.
798 */
799 void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
800 int64_t grow, int64_t shrink,
799 - Error **errp);
801 + int64_t weight, Error **errp);
802
803 /**
804 * aio_context_set_aio_params:
include/system/iothread.h
+18
@@ -21,6 +21,23 @@
21
22 #define TYPE_IOTHREAD "iothread"
23
24 +#ifdef CONFIG_POSIX
25 +/*
26 + * Benchmark results from 2016 on NVMe SSD drives show max polling times around
27 + * 16-32 microseconds yield IOPS improvements for both iodepth=1 and iodepth=32
28 + * workloads.
29 + */
30 +#define IOTHREAD_POLL_MAX_NS_DEFAULT 32768ULL
31 +#define IOTHREAD_POLL_GROW_DEFAULT 2ULL
32 +#define IOTHREAD_POLL_SHRINK_DEFAULT 2ULL
33 +#define IOTHREAD_POLL_WEIGHT_DEFAULT 3ULL
34 +#else
35 +#define IOTHREAD_POLL_MAX_NS_DEFAULT 0ULL
36 +#define IOTHREAD_POLL_GROW_DEFAULT 0ULL
37 +#define IOTHREAD_POLL_SHRINK_DEFAULT 0ULL
38 +#define IOTHREAD_POLL_WEIGHT_DEFAULT 0ULL
39 +#endif
40 +
41 struct IOThread {
42 EventLoopBase parent_obj;
43
@@ -38,6 +55,7 @@ struct IOThread {
55 int64_t poll_max_ns;
56 int64_t poll_grow;
57 int64_t poll_shrink;
58 + int64_t poll_weight;
59 };
60 typedef struct IOThread IOThread;
61
iothread.c
+34 -13
@@ -25,17 +25,6 @@
25 #include "qemu/rcu.h"
26 #include "qemu/main-loop.h"
27
28 -
29 -#ifdef CONFIG_POSIX
30 -/* Benchmark results from 2016 on NVMe SSD drives show max polling times around
31 - * 16-32 microseconds yield IOPS improvements for both iodepth=1 and iodepth=32
32 - * workloads.
33 - */
34 -#define IOTHREAD_POLL_MAX_NS_DEFAULT 32768ULL
35 -#else
36 -#define IOTHREAD_POLL_MAX_NS_DEFAULT 0ULL
37 -#endif
38 -
28 static void *iothread_run(void *opaque)
29 {
30 IOThread *iothread = opaque;
@@ -103,6 +92,10 @@ static void iothread_instance_init(Object *obj)
92 IOThread *iothread = IOTHREAD(obj);
93
94 iothread->poll_max_ns = IOTHREAD_POLL_MAX_NS_DEFAULT;
95 + iothread->poll_grow = IOTHREAD_POLL_GROW_DEFAULT;
96 + iothread->poll_shrink = IOTHREAD_POLL_SHRINK_DEFAULT;
97 + iothread->poll_weight = IOTHREAD_POLL_WEIGHT_DEFAULT;
98 +
99 iothread->thread_id = -1;
100 qemu_sem_init(&iothread->init_done_sem, 0);
101 /* By default, we don't run gcontext */
@@ -164,6 +157,7 @@ static void iothread_set_aio_context_params(EventLoopBase *base, Error **errp)
157 iothread->poll_max_ns,
158 iothread->poll_grow,
159 iothread->poll_shrink,
160 + iothread->poll_weight,
161 errp);
162 if (*errp) {
163 return;
@@ -233,6 +227,9 @@ static IOThreadParamInfo poll_grow_info = {
227 static IOThreadParamInfo poll_shrink_info = {
228 "poll-shrink", offsetof(IOThread, poll_shrink),
229 };
230 +static IOThreadParamInfo poll_weight_info = {
231 + "poll-weight", offsetof(IOThread, poll_weight),
232 +};
233
234 static void iothread_get_param(Object *obj, Visitor *v,
235 const char *name, IOThreadParamInfo *info, Error **errp)
@@ -254,13 +251,31 @@ static bool iothread_set_param(Object *obj, Visitor *v,
251 return false;
252 }
253
257 - if (value < 0) {
254 + if (info->offset == offsetof(IOThread, poll_weight)) {
255 + if (value < 0 || value > 63) {
256 + error_setg(errp, "%s value must be in range [0, 63]",
257 + info->name);
258 + return false;
259 + }
260 + } else if (value < 0) {
261 error_setg(errp, "%s value must be in range [0, %" PRId64 "]",
262 info->name, INT64_MAX);
263 return false;
264 }
265
263 - *field = value;
266 + if (value == 0) {
267 + if (info->offset == offsetof(IOThread, poll_grow)) {
268 + *field = IOTHREAD_POLL_GROW_DEFAULT;
269 + } else if (info->offset == offsetof(IOThread, poll_shrink)) {
270 + *field = IOTHREAD_POLL_SHRINK_DEFAULT;
271 + } else if (info->offset == offsetof(IOThread, poll_weight)) {
272 + *field = IOTHREAD_POLL_WEIGHT_DEFAULT;
273 + } else {
274 + *field = value;
275 + }
276 + } else {
277 + *field = value;
278 + }
279
280 return true;
281 }
@@ -288,6 +303,7 @@ static void iothread_set_poll_param(Object *obj, Visitor *v,
303 iothread->poll_max_ns,
304 iothread->poll_grow,
305 iothread->poll_shrink,
306 + iothread->poll_weight,
307 errp);
308 }
309 }
@@ -311,6 +327,10 @@ static void iothread_class_init(ObjectClass *klass, const void *class_data)
327 iothread_get_poll_param,
328 iothread_set_poll_param,
329 NULL, &poll_shrink_info);
330 + object_class_property_add(klass, "poll-weight", "int",
331 + iothread_get_poll_param,
332 + iothread_set_poll_param,
333 + NULL, &poll_weight_info);
334 }
335
336 static const TypeInfo iothread_info = {
@@ -356,6 +376,7 @@ static int query_one_iothread(Object *object, void *opaque)
376 info->poll_max_ns = iothread->poll_max_ns;
377 info->poll_grow = iothread->poll_grow;
378 info->poll_shrink = iothread->poll_shrink;
379 + info->poll_weight = iothread->poll_weight;
380 info->aio_max_batch = iothread->parent_obj.aio_max_batch;
381
382 QAPI_LIST_APPEND(*tail, info);
monitor/hmp-cmds.c
+1
@@ -206,6 +206,7 @@ void hmp_info_iothreads(Monitor *mon, const QDict *qdict)
206 monitor_printf(mon, " poll-max-ns=%" PRId64 "\n", value->poll_max_ns);
207 monitor_printf(mon, " poll-grow=%" PRId64 "\n", value->poll_grow);
208 monitor_printf(mon, " poll-shrink=%" PRId64 "\n", value->poll_shrink);
209 + monitor_printf(mon, " poll-weight=%" PRId64 "\n", value->poll_weight);
210 monitor_printf(mon, " aio-max-batch=%" PRId64 "\n",
211 value->aio_max_batch);
212 }
qapi/misc.json
+6
@@ -85,6 +85,11 @@
85 # @poll-shrink: how many ns will be removed from polling time, 0 means
86 # that it's not configured (since 2.9)
87 #
88 +# @poll-weight: the weight factor for adaptive polling.
89 +# Determines how much the current event interval contributes to
90 +# the next polling time calculation. Valid values are 1 or
91 +# greater (since 11.1)
92 +#
93 # @aio-max-batch: maximum number of requests in a batch for the AIO
94 # engine, 0 means that the engine will use its default (since 6.1)
95 #
@@ -96,6 +101,7 @@
101 'poll-max-ns': 'int',
102 'poll-grow': 'int',
103 'poll-shrink': 'int',
104 + 'poll-weight': 'int',
105 'aio-max-batch': 'int' } }
106
107 ##
qapi/qom.json
+9 -1
@@ -606,6 +606,13 @@
606 # algorithm detects it is spending too long polling without
607 # encountering events. 0 selects a default behaviour (default: 0)
608 #
609 +# @poll-weight: the weight factor for adaptive polling. Determines
610 +# how much the most recent event interval affects the next
611 +# polling duration calculation. If set to 0, the system default
612 +# value of 3 is used. Typical values: 1 (high weight on recent
613 +# interval), 2-4 (moderate weight on recent interval).
614 +# (default: 0) (since 11.1)
615 +#
616 # The @aio-max-batch option is available since 6.1.
617 #
618 # Since: 2.0
@@ -614,7 +621,8 @@
621 'base': 'EventLoopBaseProperties',
622 'data': { '*poll-max-ns': 'int',
623 '*poll-grow': 'int',
617 - '*poll-shrink': 'int' } }
624 + '*poll-shrink': 'int',
625 + '*poll-weight': 'int' } }
626
627 ##
628 # @MainLoopProperties:
qemu-options.hx
+7 -1
@@ -6443,7 +6443,7 @@ SRST
6443
6444 CN=laptop.example.com,O=Example Home,L=London,ST=London,C=GB
6445
6446 - ``-object iothread,id=id,poll-max-ns=poll-max-ns,poll-grow=poll-grow,poll-shrink=poll-shrink,aio-max-batch=aio-max-batch``
6446 + ``-object iothread,id=id,poll-max-ns=poll-max-ns,poll-grow=poll-grow,poll-shrink=poll-shrink,poll-weight=poll-weight,aio-max-batch=aio-max-batch``
6447 Creates a dedicated event loop thread that devices can be
6448 assigned to. This is known as an IOThread. By default device
6449 emulation happens in vCPU threads or the main event loop thread.
@@ -6479,6 +6479,12 @@ SRST
6479 the polling time when the algorithm detects it is spending too
6480 long polling without encountering events.
6481
6482 + The ``poll-weight`` parameter is the weight factor for adaptive
6483 + polling. It determines how much the most recent event interval
6484 + affects the next polling duration calculation. If set to 0, the
6485 + system default value of 3 is used. Typical values: 1 (high weight
6486 + on recent interval), 2-4 (moderate weight on recent interval).
6487 +
6488 The ``aio-max-batch`` parameter is the maximum number of requests
6489 in a batch for the AIO engine, 0 means that the engine will use
6490 its default.
tests/unit/test-nested-aio-poll.c
+1 -1
@@ -81,7 +81,7 @@ static void test(void)
81 qemu_set_current_aio_context(td.ctx);
82
83 /* Enable polling */
84 - aio_context_set_poll_params(td.ctx, 1000000, 2, 2, &error_abort);
84 + aio_context_set_poll_params(td.ctx, 1000000, 2, 2, 3, &error_abort);
85
86 /* Make the event notifier active (set) right away */
87 event_notifier_init(&td.poll_notifier, 1);
util/aio-posix.c
+13 -24
@@ -29,7 +29,6 @@
29
30 /* Stop userspace polling on a handler if it isn't active for some time */
31 #define POLL_IDLE_INTERVAL_NS (7 * NANOSECONDS_PER_SECOND)
32 -#define POLL_WEIGHT_SHIFT (3)
32
33 static void update_handler_poll_times(AioContext *ctx, int64_t block_ns,
34 int64_t dispatch_time);
@@ -582,28 +581,11 @@ static bool try_poll_mode(AioContext *ctx, AioHandlerList *ready_list,
581
582 static void adjust_polling_time(AioContext *ctx, int64_t block_ns)
583 {
585 - if (block_ns < ctx->poll_ns) {
586 - int64_t old = ctx->poll_ns;
587 - int64_t shrink = ctx->poll_shrink;
588 -
589 - if (shrink == 0) {
590 - shrink = 2;
591 - }
592 -
593 - if (block_ns < (ctx->poll_ns / shrink)) {
594 - ctx->poll_ns /= shrink;
595 - }
596 -
597 - trace_poll_shrink(ctx, old, ctx->poll_ns);
598 - } else if (block_ns > ctx->poll_ns) {
584 + if (block_ns > ctx->poll_ns) {
585 /* There is room to grow, poll longer */
586 int64_t old = ctx->poll_ns;
587 int64_t grow = ctx->poll_grow;
588
603 - if (grow == 0) {
604 - grow = 2;
605 - }
606 -
589 if (block_ns > ctx->poll_ns * grow) {
590 ctx->poll_ns = block_ns;
591 } else {
@@ -615,6 +597,11 @@ static void adjust_polling_time(AioContext *ctx, int64_t block_ns)
597 }
598
599 trace_poll_grow(ctx, old, ctx->poll_ns);
600 + } else if (block_ns < (ctx->poll_ns / ctx->poll_shrink)) {
601 + int64_t old = ctx->poll_ns;
602 + ctx->poll_ns /= ctx->poll_shrink;
603 +
604 + trace_poll_shrink(ctx, old, ctx->poll_ns);
605 }
606 }
607
@@ -632,8 +619,8 @@ static void update_handler_poll_times(AioContext *ctx, int64_t block_ns,
619 * block_ns and previous poll.ns to smooth adjustments.
620 */
621 node->poll.ns = node->poll.ns
635 - ? (node->poll.ns - (node->poll.ns >> POLL_WEIGHT_SHIFT))
636 - + (block_ns >> POLL_WEIGHT_SHIFT) : block_ns;
622 + ? (node->poll.ns - (node->poll.ns >> ctx->poll_weight))
623 + + (block_ns >> ctx->poll_weight) : block_ns;
624
625 if (node->poll.ns > ctx->poll_max_ns) {
626 node->poll.ns = 0;
@@ -819,7 +806,8 @@ void aio_context_destroy(AioContext *ctx)
806 }
807
808 void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
822 - int64_t grow, int64_t shrink, Error **errp)
809 + int64_t grow, int64_t shrink,
810 + int64_t weight, Error **errp)
811 {
812 AioHandler *node;
813
@@ -833,8 +821,9 @@ void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
821 * is used once.
822 */
823 ctx->poll_max_ns = max_ns;
836 - ctx->poll_grow = grow;
837 - ctx->poll_shrink = shrink;
824 + ctx->poll_grow = (grow ? grow : IOTHREAD_POLL_GROW_DEFAULT);
825 + ctx->poll_shrink = (shrink ? shrink : IOTHREAD_POLL_SHRINK_DEFAULT);
826 + ctx->poll_weight = (weight ? weight : IOTHREAD_POLL_WEIGHT_DEFAULT);
827 ctx->poll_ns = 0;
828
829 aio_notify(ctx);
util/aio-win32.c
+2 -1
@@ -429,7 +429,8 @@ void aio_context_destroy(AioContext *ctx)
429 }
430
431 void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
432 - int64_t grow, int64_t shrink, Error **errp)
432 + int64_t grow, int64_t shrink,
433 + int64_t weight, Error **errp)
434 {
435 if (max_ns) {
436 error_setg(errp, "AioContext polling is not implemented on Windows");
util/async.c
+1
@@ -609,6 +609,7 @@ AioContext *aio_context_new(Error **errp)
609 ctx->poll_ns = 0;
610 ctx->poll_grow = 0;
611 ctx->poll_shrink = 0;
612 + ctx->poll_weight = 0;
613
614 ctx->aio_max_batch = 0;
615