File systems not supporting FALLOC_FL_ZERO_RANGE yet fall back to manual
zeroing.
We can avoid this by combining two fallocate calls:
fallocate(FALLOC_FL_PUNCH_HOLE)
fallocate(0)
Based on my tests this is much more efficient compared to manual
zeroing. The idea came from this qemu patch:
https://github.com/qemu/qemu/commit/1cdc3239f1bb
Here is an example run with NFS 4.2 without this change, converting
fedora 27 image created with virt-builder:
$ export SOCK=/tmp/nbd.sock
$ export FILE=/nfs-mount/fedora-27.img
$ src/nbdkit plugins/file/.libs/nbdkit-file-plugin.so file=$FILE -U $SOCK
$ time qemu-img convert -n -f raw -O raw /var/tmp/fedora-27.img nbd:unix:/tmp/nbd.sock
real 0m17.481s
user 0m0.199s
sys 0m0.691s
$ time qemu-img convert -n -f raw -O raw -W /var/tmp/fedora-27.img nbd:unix:/tmp/nbd.sock
real 0m17.072s
user 0m0.191s
sys 0m0.738s
With this change:
$ time qemu-img convert -n -f raw -O raw /var/tmp/fedora-27.img nbd:unix:/tmp/nbd.sock
real 0m6.285s
user 0m0.217s
sys 0m0.829s
$ time qemu-img convert -n -f raw -O raw -W /var/tmp/fedora-27.img nbd:unix:/tmp/nbd.sock
real 0m3.967s
user 0m0.193s
sys 0m0.702s
Note: the image is sparse, but nbdkit creates a fully allocated image.
This may be a bug in nbdkit or qemu-img.
---
plugins/file/file.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/plugins/file/file.c b/plugins/file/file.c
index 5daab63..e96aa86 100644
--- a/plugins/file/file.c
+++ b/plugins/file/file.c
@@ -121,6 +121,7 @@ struct handle {
int fd;
bool can_punch_hole;
bool can_zero_range;
+ bool can_fallocate;
};
/* Create the per-connection handle. */
@@ -161,6 +162,8 @@ file_open (int readonly)
h->can_zero_range = false;
#endif
+ h->can_fallocate = true;
+
return h;
}
@@ -301,6 +304,35 @@ file_zero (void *handle, uint32_t count, uint64_t offset, int
may_trim)
}
#endif
+#ifdef FALLOC_FL_PUNCH_HOLE
+ /* If we can punch hole but may not trim, we can combine punching hole and
+ * fallocate to zero a range. This is expected to be more efficient than
+ * writing zeros manually. */
+ if (h->can_punch_hole && h->can_fallocate) {
+ r = do_fallocate (h->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ offset, count);
+ if (r == 0) {
+ r = do_fallocate (h->fd, 0, offset, count);
+ if (r == 0)
+ return r;
+
+ if (errno != EOPNOTSUPP) {
+ nbdkit_error ("zero: %m");
+ return r;
+ }
+
+ h->can_fallocate = false;
+ } else {
+ if (errno != EOPNOTSUPP) {
+ nbdkit_error ("zero: %m");
+ return r;
+ }
+
+ h->can_punch_hole = false;
+ }
+ }
+#endif
+
/* Trigger a fall back to writing */
errno = EOPNOTSUPP;
return r;
--
2.17.1