On Fri, Aug 3, 2018 at 10:28 PM Nir Soffer <nirsof@gmail.com> wrote:
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..a2cea4a 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);

Space after the function name is missing here, fixing it in v3.
 
+      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