Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
coroutine: resize pool periodically instead of limiting size
It was reported that enabling SafeStack reduces IOPS significantly (>25%) with the following fio benchmark on virtio-blk using a NVMe host block device: # fio --rw=randrw --bs=4k --iodepth=64 --runtime=1m --direct=1 \ --filename=/dev/vdb --name=job1 --ioengine=libaio --thread \ --group_reporting --numjobs=16 --time_based \ --output=/tmp/fio_result Serge Guelton and I found that SafeStack is not really at fault, it just increases the cost of coroutine creation. This fio workload exhausts the coroutine pool and coroutine creation becomes a bottleneck. Previous work by Honghao Wang also pointed to excessive coroutine creation. Creating new coroutines is expensive due to allocating new stacks with mmap(2) and mprotect(2). Currently there are thread-local and global pools that recycle old Coroutine objects and their stacks but the hardcoded size limit of 64 for thread-local pools and 128 for the global pool is insufficient for the fio benchmark shown above. This patch changes the coroutine pool algorithm to a simple thread-local pool without a maximum size limit. Threads periodically shrink the pool down to a size sufficient for the maximum observed number of coroutines. The global pool is removed by this patch. It can help to hide the fact that local pools are easily exhausted, but it's doesn't fix the root cause. I don't think there is a need for a global pool because QEMU's threads are long-lived, so let's keep things simple. Performance of the above fio benchmark is as follows: Before After IOPS 60k 97k Memory usage varies over time as needed by the workload: VSZ (KB) RSS (KB) Before fio 4705248 843128 During fio 5747668 (+ ~100 MB) 849280 After fio 4694996 (- ~100 MB) 845184 This confirms that coroutines are indeed being freed when no longer needed. Thanks to Serge Guelton for working on identifying the bottleneck with me! Reported-by: Tingting Mao <timao@redhat.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> Message-id: 20210913153524.1190696-1-stefanha@redhat.com Cc: Serge Guelton <sguelton@redhat.com> Cc: Honghao Wang <wanghonghao@bytedance.com> Cc: Paolo Bonzini <pbonzini@redhat.com> Cc: Daniele Buono <dbuono@linux.vnet.ibm.com> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> [Moved atexit notifier to coroutine_delete() after GitLab CI reported a memory leak in tests/unit/test-aio-multithread because the Coroutine object was created in the main thread but runs in an IOThread (where it's also deleted). --Stefan]
- Loading branch information
1 parent
afc9fcd
commit 4b2b3d2
Showing
7 changed files
with
125 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* QEMU coroutine pool timer | ||
* | ||
* Copyright (c) 2021 Red Hat, Inc. | ||
* | ||
* SPDX-License-Identifier: LGPL-2.1-or-later | ||
* | ||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later. | ||
* See the COPYING.LIB file in the top-level directory. | ||
* | ||
*/ | ||
#ifndef COROUTINE_POOL_TIMER_H | ||
#define COROUTINE_POOL_TIMER_H | ||
|
||
#include "qemu/osdep.h" | ||
#include "block/aio.h" | ||
|
||
/** | ||
* A timer that periodically resizes this thread's coroutine pool, freeing | ||
* memory if there are too many unused coroutines. | ||
* | ||
* Threads that make heavy use of coroutines should use this. Failure to resize | ||
* the coroutine pool can lead to large amounts of memory sitting idle and | ||
* never being used after the first time. | ||
*/ | ||
typedef struct { | ||
QEMUTimer *timer; | ||
} CoroutinePoolTimer; | ||
|
||
/* Call this before the thread runs the AioContext */ | ||
void coroutine_pool_timer_init(CoroutinePoolTimer *pt, AioContext *ctx); | ||
|
||
/* Call this before the AioContext from the init function is destroyed */ | ||
void coroutine_pool_timer_cleanup(CoroutinePoolTimer *pt); | ||
|
||
#endif /* COROUTINE_POOL_TIMER_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* QEMU coroutine pool timer | ||
* | ||
* Copyright (c) 2021 Red Hat, Inc. | ||
* | ||
* SPDX-License-Identifier: LGPL-2.1-or-later | ||
* | ||
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later. | ||
* See the COPYING.LIB file in the top-level directory. | ||
* | ||
*/ | ||
#include "qemu/coroutine-pool-timer.h" | ||
|
||
static void coroutine_pool_timer_cb(void *opaque) | ||
{ | ||
CoroutinePoolTimer *pt = opaque; | ||
int64_t expiry_time_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + | ||
15 * NANOSECONDS_PER_SECOND; | ||
|
||
qemu_coroutine_pool_periodic_resize(); | ||
timer_mod(pt->timer, expiry_time_ns); | ||
} | ||
|
||
void coroutine_pool_timer_init(CoroutinePoolTimer *pt, AioContext *ctx) | ||
{ | ||
pt->timer = aio_timer_new(ctx, QEMU_CLOCK_REALTIME, SCALE_NS, | ||
coroutine_pool_timer_cb, pt); | ||
coroutine_pool_timer_cb(pt); | ||
} | ||
|
||
void coroutine_pool_timer_cleanup(CoroutinePoolTimer *pt) | ||
{ | ||
timer_free(pt->timer); | ||
pt->timer = NULL; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters