From: Andrey Drobyshev via <qemu-devel@nongnu.org>
To: qemu-block@nongnu.org
Cc: qemu-devel@nongnu.org, hreitz@redhat.com, kwolf@redhat.com,
eblake@redhat.com, andrey.drobyshev@virtuozzo.com,
den@virtuozzo.com
Subject: [PATCH v2 5/8] qemu-img: rebase: avoid unnecessary COW operations
Date: Fri, 15 Sep 2023 19:20:13 +0300 [thread overview]
Message-ID: <20230915162016.141771-6-andrey.drobyshev@virtuozzo.com> (raw)
In-Reply-To: <20230915162016.141771-1-andrey.drobyshev@virtuozzo.com>
When rebasing an image from one backing file to another, we need to
compare data from old and new backings. If the diff between that data
happens to be unaligned to the target cluster size, we might end up
doing partial writes, which would lead to copy-on-write and additional IO.
Consider the following simple case (virtual_size == cluster_size == 64K):
base <-- inc1 <-- inc2
qemu-io -c "write -P 0xaa 0 32K" base.qcow2
qemu-io -c "write -P 0xcc 32K 32K" base.qcow2
qemu-io -c "write -P 0xbb 0 32K" inc1.qcow2
qemu-io -c "write -P 0xcc 32K 32K" inc1.qcow2
qemu-img rebase -f qcow2 -b base.qcow2 -F qcow2 inc2.qcow2
While doing rebase, we'll write a half of the cluster to inc2, and block
layer will have to read the 2nd half of the same cluster from the base image
inc1 while doing this write operation, although the whole cluster is already
read earlier to perform data comparison.
In order to avoid these unnecessary IO cycles, let's make sure every
write request is aligned to the overlay subcluster boundaries. Using
subcluster size is universal as for the images which don't have them
this size equals to the cluster size, so in any case we end up aligning
to the smallest unit of allocation.
Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
qemu-img.c | 76 ++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 56 insertions(+), 20 deletions(-)
diff --git a/qemu-img.c b/qemu-img.c
index fcd31d7b5b..83950af42b 100644
--- a/qemu-img.c
+++ b/qemu-img.c
@@ -3523,6 +3523,7 @@ static int img_rebase(int argc, char **argv)
uint8_t *buf_new = NULL;
BlockDriverState *bs = NULL, *prefix_chain_bs = NULL;
BlockDriverState *unfiltered_bs;
+ BlockDriverInfo bdi = {0};
char *filename;
const char *fmt, *cache, *src_cache, *out_basefmt, *out_baseimg;
int c, flags, src_flags, ret;
@@ -3533,6 +3534,7 @@ static int img_rebase(int argc, char **argv)
bool quiet = false;
Error *local_err = NULL;
bool image_opts = false;
+ int64_t write_align;
/* Parse commandline parameters */
fmt = NULL;
@@ -3656,6 +3658,20 @@ static int img_rebase(int argc, char **argv)
}
}
+ /*
+ * We need overlay subcluster size to make sure write requests are
+ * aligned.
+ */
+ ret = bdrv_get_info(unfiltered_bs, &bdi);
+ if (ret < 0) {
+ error_report("could not get block driver info");
+ goto out;
+ } else if (bdi.subcluster_size == 0) {
+ bdi.subcluster_size = 1;
+ }
+
+ write_align = bdi.subcluster_size;
+
/* For safe rebasing we need to compare old and new backing file */
if (!unsafe) {
QDict *options = NULL;
@@ -3753,7 +3769,7 @@ static int img_rebase(int argc, char **argv)
int64_t old_backing_size = 0;
int64_t new_backing_size = 0;
uint64_t offset;
- int64_t n;
+ int64_t n, n_old = 0, n_new = 0;
float local_progress = 0;
if (blk_old_backing && bdrv_opt_mem_align(blk_bs(blk)) >
@@ -3799,7 +3815,8 @@ static int img_rebase(int argc, char **argv)
}
for (offset = 0; offset < size; offset += n) {
- bool buf_old_is_zero = false;
+ bool old_backing_eof = false;
+ int64_t n_alloc;
/* How many bytes can we handle with the next read? */
n = MIN(IO_BUF_SIZE, size - offset);
@@ -3844,33 +3861,48 @@ static int img_rebase(int argc, char **argv)
}
}
+ /*
+ * At this point we know that the region [offset; offset + n)
+ * is unallocated within the target image. This region might be
+ * unaligned to the target image's (sub)cluster boundaries, as
+ * old backing may have smaller clusters (or have subclusters).
+ * We extend it to the aligned boundaries to avoid CoW on
+ * partial writes in blk_pwrite(),
+ */
+ n += offset - QEMU_ALIGN_DOWN(offset, write_align);
+ offset = QEMU_ALIGN_DOWN(offset, write_align);
+ n += QEMU_ALIGN_UP(offset + n, write_align) - (offset + n);
+ n = MIN(n, size - offset);
+ assert(!bdrv_is_allocated(unfiltered_bs, offset, n, &n_alloc) &&
+ n_alloc == n);
+
+ /*
+ * Much like the with the target image, we'll try to read as much
+ * of the old and new backings as we can.
+ */
+ n_old = MIN(n, MAX(0, old_backing_size - (int64_t) offset));
+ if (blk_new_backing) {
+ n_new = MIN(n, MAX(0, new_backing_size - (int64_t) offset));
+ }
+
/*
* Read old and new backing file and take into consideration that
* backing files may be smaller than the COW image.
*/
- if (offset >= old_backing_size) {
- memset(buf_old, 0, n);
- buf_old_is_zero = true;
+ memset(buf_old + n_old, 0, n - n_old);
+ if (!n_old) {
+ old_backing_eof = true;
} else {
- if (offset + n > old_backing_size) {
- n = old_backing_size - offset;
- }
-
- ret = blk_pread(blk_old_backing, offset, n, buf_old, 0);
+ ret = blk_pread(blk_old_backing, offset, n_old, buf_old, 0);
if (ret < 0) {
error_report("error while reading from old backing file");
goto out;
}
}
- if (offset >= new_backing_size || !blk_new_backing) {
- memset(buf_new, 0, n);
- } else {
- if (offset + n > new_backing_size) {
- n = new_backing_size - offset;
- }
-
- ret = blk_pread(blk_new_backing, offset, n, buf_new, 0);
+ memset(buf_new + n_new, 0, n - n_new);
+ if (blk_new_backing && n_new) {
+ ret = blk_pread(blk_new_backing, offset, n_new, buf_new, 0);
if (ret < 0) {
error_report("error while reading from new backing file");
goto out;
@@ -3884,11 +3916,12 @@ static int img_rebase(int argc, char **argv)
int64_t pnum;
if (compare_buffers(buf_old + written, buf_new + written,
- n - written, 0, &pnum))
+ n - written, write_align, &pnum))
{
- if (buf_old_is_zero) {
+ if (old_backing_eof) {
ret = blk_pwrite_zeroes(blk, offset + written, pnum, 0);
} else {
+ assert(written + pnum <= IO_BUF_SIZE);
ret = blk_pwrite(blk, offset + written, pnum,
buf_old + written, 0);
}
@@ -3900,6 +3933,9 @@ static int img_rebase(int argc, char **argv)
}
written += pnum;
+ if (offset + written >= old_backing_size) {
+ old_backing_eof = true;
+ }
}
qemu_progress_print(local_progress, 100);
}
--
2.39.3
next prev parent reply other threads:[~2023-09-15 16:22 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-09-15 16:20 [PATCH v2 0/8] qemu-img: rebase: add compression support Andrey Drobyshev via
2023-09-15 16:20 ` [PATCH v2 1/8] qemu-img: rebase: stop when reaching EOF of old backing file Andrey Drobyshev via
2023-09-15 16:20 ` [PATCH v2 2/8] qemu-iotests: 024: add rebasing test case for overlay_size > backing_size Andrey Drobyshev via
2023-09-15 16:20 ` [PATCH v2 3/8] qemu-img: rebase: use backing files' BlockBackend for buffer alignment Andrey Drobyshev via
2023-09-15 18:39 ` Eric Blake
2023-09-15 19:27 ` Andrey Drobyshev
2023-09-19 8:18 ` Hanna Czenczek
2023-09-19 9:26 ` Andrey Drobyshev
2023-09-15 16:20 ` [PATCH v2 4/8] qemu-img: add chunk size parameter to compare_buffers() Andrey Drobyshev via
2023-09-15 19:28 ` Eric Blake
2023-09-19 8:32 ` Hanna Czenczek
2023-09-15 16:20 ` Andrey Drobyshev via [this message]
2023-09-15 21:52 ` [PATCH v2 5/8] qemu-img: rebase: avoid unnecessary COW operations Eric Blake
2023-09-16 17:45 ` Andrey Drobyshev
2023-09-19 10:46 ` Hanna Czenczek
2023-09-19 16:21 ` Andrey Drobyshev
2023-09-15 16:20 ` [PATCH v2 6/8] iotests/{024, 271}: add testcases for qemu-img rebase Andrey Drobyshev via
2023-09-19 11:08 ` Hanna Czenczek
2023-09-15 16:20 ` [PATCH v2 7/8] qemu-img: add compression option to rebase subcommand Andrey Drobyshev via
2023-09-15 16:20 ` [PATCH v2 8/8] iotests: add tests for "qemu-img rebase" with compression Andrey Drobyshev via
2023-09-19 11:14 ` Hanna Czenczek
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230915162016.141771-6-andrey.drobyshev@virtuozzo.com \
--to=qemu-devel@nongnu.org \
--cc=andrey.drobyshev@virtuozzo.com \
--cc=den@virtuozzo.com \
--cc=eblake@redhat.com \
--cc=hreitz@redhat.com \
--cc=kwolf@redhat.com \
--cc=qemu-block@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).