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 72f51a4..141a32c 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 0; + + if (errno != EOPNOTSUPP) { + nbdkit_error ("zero: %m"); + return -1; + } + + h->can_fallocate = false; + } else { + if (errno != EOPNOTSUPP) { + nbdkit_error ("zero: %m"); + return -1; + } + + h->can_punch_hole = false; + } + } +#endif + /* Trigger a fall back to writing */ errno = EOPNOTSUPP; return -1; -- 2.17.1