kernel - Add vm.read_shortcut_enable
authorMatthew Dillon <dillon@apollo.backplane.com>
Sun, 16 Sep 2012 03:52:45 +0000 (20:52 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Sun, 16 Sep 2012 05:08:40 +0000 (22:08 -0700)
* Add vm.read_shortcut_enable (disabled by default for now).  Set to 1 to
  enable this feature.

  This enables a helper function which HAMMER1 now uses to short-cut read()
  operations on files.  This feature only works on x86-64.

* When enabled this feature allows file read() requests to be satisfied
  directly from the VM page cache using lwbuf's, completely bypassing the
  buffer cache and also bypassing most of the VFS's VOP_READ code.

  The result is an approximate doubling of read() performance in cases
  where the buffer cache is too small to fit the hot data set, but the VM
  page cache is not.

  This feature is able to avoid the buffer cache and thus prevent buffer
  cycling within it which, due to the constant installation and
  deinstallation of pages in KVM cause a great deal of SMP page table
  page invalidations.

sys/cpu/x86_64/include/lwbuf.h
sys/kern/vfs_helper.c
sys/kern/vfs_vnops.c
sys/sys/mount.h
sys/vfs/hammer/hammer_vnops.c

index 6bfd664..b9ce4da 100644 (file)
@@ -93,6 +93,9 @@ lwbuf_free(struct lwbuf *lwb)
 
 #define lwbuf_set_global(lwb)
 
+/* tell the kernel that lwbufs are cheap */
+#define LWBUF_IS_OPTIMAL
+
 #if defined(_KERNEL)
 
 #endif
index 23b4651..80efceb 100644 (file)
 #include <sys/proc.h>
 #include <sys/priv.h>
 #include <sys/jail.h>
+#include <sys/sysctl.h>
+#include <sys/sfbuf.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_object.h>
+
+#ifdef LWBUF_IS_OPTIMAL
+
+static int vm_read_shortcut_enable = 0;
+static long vm_read_shortcut_count;
+static long vm_read_shortcut_failed;
+SYSCTL_INT(_vm, OID_AUTO, read_shortcut_enable, CTLFLAG_RW,
+         &vm_read_shortcut_enable, 0, "Direct vm_object vop_read shortcut");
+SYSCTL_LONG(_vm, OID_AUTO, read_shortcut_count, CTLFLAG_RW,
+         &vm_read_shortcut_count, 0, "Statistics");
+SYSCTL_LONG(_vm, OID_AUTO, read_shortcut_failed, CTLFLAG_RW,
+         &vm_read_shortcut_failed, 0, "Statistics");
+
+#endif
 
 /*
  * vop_helper_access()
@@ -276,3 +294,107 @@ vop_helper_chown(struct vnode *vp, uid_t new_uid, gid_t new_gid,
        return(0);
 }
 
+#ifdef LWBUF_IS_OPTIMAL
+
+/*
+ * A VFS can call this function to try to dispose of a read request
+ * directly from the VM system, pretty much bypassing almost all VFS
+ * overhead except for atime updates.
+ *
+ * If 0 is returned some or all of the uio was handled.  The caller must
+ * check the uio and handle the remainder.
+ *
+ * The caller must fail on a non-zero error.
+ */
+int
+vop_helper_read_shortcut(struct vop_read_args *ap)
+{
+       struct vnode *vp;
+       struct uio *uio;
+       struct lwbuf *lwb;
+       struct lwbuf lwb_cache;
+       vm_object_t obj;
+       vm_page_t m;
+       int offset;
+       int n;
+       int error;
+
+       vp = ap->a_vp;
+       uio = ap->a_uio;
+
+       /*
+        * We can't short-cut if there is no VM object or this is a special
+        * UIO_NOCOPY read (typically from VOP_STRATEGY()).  We also can't
+        * do this if we cannot extract the filesize from the vnode.
+        */
+       if (vm_read_shortcut_enable == 0)
+               return(0);
+       if (vp->v_object == NULL || uio->uio_segflg == UIO_NOCOPY)
+               return(0);
+       if (vp->v_filesize == NOOFFSET)
+               return(0);
+       if (uio->uio_resid == 0)
+               return(0);
+
+       /*
+        * Iterate the uio on a page-by-page basis
+        *
+        * XXX can we leave the object held shared during the uiomove()?
+        */
+       ++vm_read_shortcut_count;
+       obj = vp->v_object;
+       vm_object_hold_shared(obj);
+
+       error = 0;
+       while (uio->uio_resid && error == 0) {
+               offset = (int)uio->uio_offset & PAGE_MASK;
+               n = PAGE_SIZE - offset;
+               if (n > uio->uio_resid)
+                       n = uio->uio_resid;
+               if (vp->v_filesize < uio->uio_offset)
+                       break;
+               if (uio->uio_offset + n > vp->v_filesize)
+                       n = vp->v_filesize - uio->uio_offset;
+               if (n == 0)
+                       break;  /* hit EOF */
+
+               m = vm_page_lookup(obj, OFF_TO_IDX(uio->uio_offset));
+               if (m == NULL) {
+                       ++vm_read_shortcut_failed;
+                       break;
+               }
+               vm_page_hold(m);
+               if (m->flags & PG_BUSY) {
+                       ++vm_read_shortcut_failed;
+                       vm_page_unhold(m);
+                       break;
+               }
+               if ((m->valid & VM_PAGE_BITS_ALL) != VM_PAGE_BITS_ALL) {
+                       ++vm_read_shortcut_failed;
+                       vm_page_unhold(m);
+                       break;
+               }
+               lwb = lwbuf_alloc(m, &lwb_cache);
+               error = uiomove((char *)lwbuf_kva(lwb) + offset, n, uio);
+               vm_page_flag_set(m, PG_REFERENCED);
+               lwbuf_free(lwb);
+               vm_page_unhold(m);
+       }
+       vm_object_drop(obj);
+
+       return (error);
+}
+
+#else
+
+/*
+ * If lwbuf's aren't optimal then it's best to just use the buffer
+ * cache.
+ */
+int
+vop_helper_read_shortcut(struct vop_read_args *ap)
+{
+       return(0);
+}
+
+#endif
index 90ae370..bc74f95 100644 (file)
@@ -637,8 +637,6 @@ vn_rdwr_inchunks(enum uio_rw rw, struct vnode *vp, caddr_t base, int len,
  * we don't need to lock access to the vp.
  *
  * f_offset updates are not guaranteed against multiple readers
- *
- * MPSAFE
  */
 static int
 vn_read(struct file *fp, struct uio *uio, struct ucred *cred, int flags)
index 27e28cf..05fec44 100644 (file)
@@ -762,6 +762,7 @@ int vop_helper_chmod(struct vnode *vp, mode_t new_mode, struct ucred *cred,
 int vop_helper_chown(struct vnode *vp, uid_t new_uid, gid_t new_gid,
                        struct ucred *cred,
                        uid_t *cur_uidp, gid_t *cur_gidp, mode_t *cur_modep);
+int vop_helper_read_shortcut(struct vop_read_args *ap);
 
 void   add_bio_ops(struct bio_ops *ops);
 void   rem_bio_ops(struct bio_ops *ops);
index 22f49d6..20277e9 100644 (file)
@@ -335,8 +335,19 @@ hammer_vop_read(struct vop_read_args *ap)
        ip = VTOI(ap->a_vp);
        hmp = ip->hmp;
        error = 0;
+       got_fstoken = 0;
        uio = ap->a_uio;
 
+       /*
+        * Attempt to shortcut directly to the VM object using lwbufs.
+        * This is much faster than instantiating buffer cache buffers.
+        */
+       error = vop_helper_read_shortcut(ap);
+       if (error)
+               return (error);
+       if (uio->uio_resid == 0)
+               goto finished;
+
        /*
         * Allow the UIO's size to override the sequential heuristic.
         */
@@ -352,7 +363,6 @@ hammer_vop_read(struct vop_read_args *ap)
         * or it can DOS the machine.
         */
        bigread = (uio->uio_resid > 100 * 1024 * 1024);
-       got_fstoken = 0;
 
        /*
         * Access the data typically in HAMMER_BUFSIZE blocks via the
@@ -462,6 +472,8 @@ skip:
                hammer_stats_file_read += n;
        }
 
+finished:
+
        /*
         * Try to update the atime with just the inode lock for maximum
         * concurrency.  If we can't shortcut it we have to get the full