Module Name:    src
Committed By:   hannken
Date:           Wed Dec 14 15:49:35 UTC 2016

Modified Files:
        src/sys/kern: vfs_vnode.c

Log Message:
Change the freelists to lrulists, all vnodes are always on one
of the lists.  Speeds up namei on cached vnodes by ~3 percent.

Merge "vrele_thread" into "vdrain_thread" so we have one thread
working on the lrulists.  Adapt vfs_drainvnodes() to always wait
for a complete cycle of vdrain_thread().


To generate a diff of this commit:
cvs rdiff -u -r1.62 -r1.63 src/sys/kern/vfs_vnode.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/kern/vfs_vnode.c
diff -u src/sys/kern/vfs_vnode.c:1.62 src/sys/kern/vfs_vnode.c:1.63
--- src/sys/kern/vfs_vnode.c:1.62	Wed Dec 14 15:48:55 2016
+++ src/sys/kern/vfs_vnode.c	Wed Dec 14 15:49:35 2016
@@ -1,4 +1,4 @@
-/*	$NetBSD: vfs_vnode.c,v 1.62 2016/12/14 15:48:55 hannken Exp $	*/
+/*	$NetBSD: vfs_vnode.c,v 1.63 2016/12/14 15:49:35 hannken Exp $	*/
 
 /*-
  * Copyright (c) 1997-2011 The NetBSD Foundation, Inc.
@@ -156,7 +156,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: vfs_vnode.c,v 1.62 2016/12/14 15:48:55 hannken Exp $");
+__KERNEL_RCSID(0, "$NetBSD: vfs_vnode.c,v 1.63 2016/12/14 15:49:35 hannken Exp $");
 
 #include <sys/param.h>
 #include <sys/kernel.h>
@@ -188,22 +188,19 @@ __KERNEL_RCSID(0, "$NetBSD: vfs_vnode.c,
 u_int			numvnodes		__cacheline_aligned;
 
 /*
- * There are two free lists: one is for vnodes which have no buffer/page
- * references and one for those which do (i.e. v_holdcnt is non-zero).
- * Vnode recycling mechanism first attempts to look into the former list.
- */
-static kmutex_t		vnode_free_list_lock	__cacheline_aligned;
-static vnodelst_t	vnode_free_list		__cacheline_aligned;
-static vnodelst_t	vnode_hold_list		__cacheline_aligned;
+ * There are three lru lists: one holds vnodes waiting for async release,
+ * one is for vnodes which have no buffer/page references and
+ * one for those which do (i.e. v_holdcnt is non-zero).
+ */
+static vnodelst_t	lru_vrele_list		__cacheline_aligned;
+static vnodelst_t	lru_free_list		__cacheline_aligned;
+static vnodelst_t	lru_hold_list		__cacheline_aligned;
+static kmutex_t		vdrain_lock		__cacheline_aligned;
 static kcondvar_t	vdrain_cv		__cacheline_aligned;
-
-static vnodelst_t	vrele_list		__cacheline_aligned;
-static kmutex_t		vrele_lock		__cacheline_aligned;
-static kcondvar_t	vrele_cv		__cacheline_aligned;
-static lwp_t *		vrele_lwp		__cacheline_aligned;
-static int		vrele_pending		__cacheline_aligned;
-static int		vrele_gen		__cacheline_aligned;
-
+static int		vdrain_gen;
+static kcondvar_t	vdrain_gen_cv;
+static bool		vdrain_retry;
+static lwp_t *		vdrain_lwp;
 SLIST_HEAD(hashhead, vnode_impl);
 static struct {
 	kmutex_t	lock;
@@ -214,15 +211,15 @@ static struct {
 	pool_cache_t	pool;
 }			vcache			__cacheline_aligned;
 
-static int		cleanvnode(void);
-static vnode_impl_t *vcache_alloc(void);
+static void		lru_requeue(vnode_t *, vnodelst_t *);
+static vnodelst_t *	lru_which(vnode_t *);
+static vnode_impl_t *	vcache_alloc(void);
 static void		vcache_free(vnode_impl_t *);
 static void		vcache_init(void);
 static void		vcache_reinit(void);
 static void		vcache_reclaim(vnode_t *);
 static void		vrelel(vnode_t *, int);
 static void		vdrain_thread(void *);
-static void		vrele_thread(void *);
 static void		vnpanic(vnode_t *, const char *, ...)
     __printflike(2, 3);
 
@@ -357,22 +354,18 @@ vfs_vnode_sysinit(void)
 	KASSERT(dead_rootmount != NULL);
 	dead_rootmount->mnt_iflag = IMNT_MPSAFE;
 
-	mutex_init(&vnode_free_list_lock, MUTEX_DEFAULT, IPL_NONE);
-	TAILQ_INIT(&vnode_free_list);
-	TAILQ_INIT(&vnode_hold_list);
-	TAILQ_INIT(&vrele_list);
+	mutex_init(&vdrain_lock, MUTEX_DEFAULT, IPL_NONE);
+	TAILQ_INIT(&lru_free_list);
+	TAILQ_INIT(&lru_hold_list);
+	TAILQ_INIT(&lru_vrele_list);
 
 	vcache_init();
 
-	mutex_init(&vrele_lock, MUTEX_DEFAULT, IPL_NONE);
 	cv_init(&vdrain_cv, "vdrain");
-	cv_init(&vrele_cv, "vrele");
+	cv_init(&vdrain_gen_cv, "vdrainwt");
 	error = kthread_create(PRI_VM, KTHREAD_MPSAFE, NULL, vdrain_thread,
-	    NULL, NULL, "vdrain");
+	    NULL, &vdrain_lwp, "vdrain");
 	KASSERTMSG((error == 0), "kthread_create(vdrain) failed: %d", error);
-	error = kthread_create(PRI_VM, KTHREAD_MPSAFE, NULL, vrele_thread,
-	    NULL, &vrele_lwp, "vrele");
-	KASSERTMSG((error == 0), "kthread_create(vrele) failed: %d", error);
 }
 
 /*
@@ -420,53 +413,74 @@ vnis_marker(vnode_t *vp)
 }
 
 /*
- * cleanvnode: grab a vnode from freelist, clean and free it.
- *
- * => Releases vnode_free_list_lock.
+ * Return the lru list this node should be on.
  */
-static int
-cleanvnode(void)
+static vnodelst_t *
+lru_which(vnode_t *vp)
 {
-	vnode_t *vp;
-	vnode_impl_t *vi;
-	vnodelst_t *listhd;
-	struct mount *mp;
 
-	KASSERT(mutex_owned(&vnode_free_list_lock));
+	KASSERT(mutex_owned(vp->v_interlock));
 
-	listhd = &vnode_free_list;
-try_nextlist:
-	TAILQ_FOREACH(vi, listhd, vi_lrulist) {
-		vp = VIMPL_TO_VNODE(vi);
-		/*
-		 * It's safe to test v_usecount and v_iflag
-		 * without holding the interlock here, since
-		 * these vnodes should never appear on the
-		 * lists.
-		 */
-		KASSERT(vp->v_usecount == 0);
-		KASSERT(vi->vi_lrulisthd == listhd);
+	if (vp->v_holdcnt > 0)
+		return &lru_hold_list;
+	else
+		return &lru_free_list;
+}
 
-		if (!mutex_tryenter(vp->v_interlock))
-			continue;
-		mp = vp->v_mount;
-		if (fstrans_start_nowait(mp, FSTRANS_SHARED) != 0) {
-			mutex_exit(vp->v_interlock);
-			continue;
-		}
-		break;
-	}
+/*
+ * Put vnode to end of given list.
+ * Both the current and the new list may be NULL, used on vnode alloc/free.
+ * Adjust numvnodes and signal vdrain thread if there is work.
+ */
+static void
+lru_requeue(vnode_t *vp, vnodelst_t *listhd)
+{
+	vnode_impl_t *node;
 
-	if (vi == NULL) {
-		if (listhd == &vnode_free_list) {
-			listhd = &vnode_hold_list;
-			goto try_nextlist;
-		}
-		mutex_exit(&vnode_free_list_lock);
-		return EBUSY;
-	}
+	mutex_enter(&vdrain_lock);
+	node = VNODE_TO_VIMPL(vp);
+	if (node->vi_lrulisthd != NULL)
+		TAILQ_REMOVE(node->vi_lrulisthd, node, vi_lrulist);
+	else
+		numvnodes++;
+	node->vi_lrulisthd = listhd;
+	if (node->vi_lrulisthd != NULL)
+		TAILQ_INSERT_TAIL(node->vi_lrulisthd, node, vi_lrulist);
+	else
+		numvnodes--;
+	if (numvnodes > desiredvnodes || listhd == &lru_vrele_list)
+		cv_broadcast(&vdrain_cv);
+	mutex_exit(&vdrain_lock);
+}
+
+/*
+ * Reclaim a cached vnode.  Used from vdrain_thread only.
+ */
+static __inline void
+vdrain_remove(vnode_t *vp)
+{
+	struct mount *mp;
 
-	mutex_exit(&vnode_free_list_lock);
+	KASSERT(mutex_owned(&vdrain_lock));
+
+	/* Probe usecount (unlocked). */
+	if (vp->v_usecount > 0)
+		return;
+	/* Try v_interlock -- we lock the wrong direction! */
+	if (!mutex_tryenter(vp->v_interlock))
+		return;
+	/* Probe usecount and state. */
+	if (vp->v_usecount > 0 || VSTATE_GET(vp) != VS_ACTIVE) {
+		mutex_exit(vp->v_interlock);
+		return;
+	}
+	mp = vp->v_mount;
+	if (fstrans_start_nowait(mp, FSTRANS_SHARED) != 0) {
+		mutex_exit(vp->v_interlock);
+		return;
+	}
+	vdrain_retry = true;
+	mutex_exit(&vdrain_lock);
 
 	if (vget(vp, 0, true /* wait */) == 0) {
 		if (!vrecycle(vp))
@@ -474,56 +488,94 @@ try_nextlist:
 	}
 	fstrans_done(mp);
 
-	return 0;
+	mutex_enter(&vdrain_lock);
 }
 
 /*
- * Helper thread to keep the number of vnodes below desiredvnodes.
+ * Release a cached vnode.  Used from vdrain_thread only.
  */
-static void
-vdrain_thread(void *cookie)
+static __inline void
+vdrain_vrele(vnode_t *vp)
 {
-	int error;
+	vnode_impl_t *node = VNODE_TO_VIMPL(vp);
+	struct mount *mp;
 
-	mutex_enter(&vnode_free_list_lock);
+	KASSERT(mutex_owned(&vdrain_lock));
 
-	for (;;) {
-		cv_timedwait(&vdrain_cv, &vnode_free_list_lock, hz);
-		while (numvnodes > desiredvnodes) {
-			error = cleanvnode();
-			if (error)
-				kpause("vndsbusy", false, hz, NULL);
-			mutex_enter(&vnode_free_list_lock);
-			if (error)
-				break;
-		}
+	/*
+	 * Safe to take v_interlock -- no other thread will
+	 * lock v_interlock -> vdrain_lock as usecount > 0.
+	 */
+	mutex_enter(vp->v_interlock);
+	mp = vp->v_mount;
+	if (fstrans_start_nowait(mp, FSTRANS_SHARED) != 0) {
+		mutex_exit(vp->v_interlock);
+		return;
 	}
+
+	/* First put the vnode back onto its lru list. */
+	KASSERT(node->vi_lrulisthd == &lru_vrele_list);
+	TAILQ_REMOVE(node->vi_lrulisthd, node, vi_lrulist);
+	node->vi_lrulisthd = lru_which(vp);
+	TAILQ_INSERT_TAIL(node->vi_lrulisthd, node, vi_lrulist);
+
+	vdrain_retry = true;
+	mutex_exit(&vdrain_lock);
+
+	vrelel(vp, 0);
+	fstrans_done(mp);
+
+	mutex_enter(&vdrain_lock);
 }
 
 /*
- * Remove a vnode from its freelist.
+ * Helper thread to keep the number of vnodes below desiredvnodes
+ * and release vnodes from asynchronous vrele.
  */
-void
-vremfree(vnode_t *vp)
+static void
+vdrain_thread(void *cookie)
 {
-	vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
+	vnodelst_t *listhd[] = {
+	    &lru_vrele_list, &lru_free_list, &lru_hold_list
+	};
+	int i;
+	u_int target;
+	vnode_impl_t *node, *marker;
 
-	KASSERT(mutex_owned(vp->v_interlock));
-	KASSERT(vp->v_usecount == 0);
+	marker = VNODE_TO_VIMPL(vnalloc_marker(NULL));
 
-	/*
-	 * Note that the reference count must not change until
-	 * the vnode is removed.
-	 */
-	mutex_enter(&vnode_free_list_lock);
-	if (vp->v_holdcnt > 0) {
-		KASSERT(vi->vi_lrulisthd == &vnode_hold_list);
-	} else {
-		KASSERT(vi->vi_lrulisthd == &vnode_free_list);
+	mutex_enter(&vdrain_lock);
+
+	for (;;) {
+		vdrain_retry = false;
+		target = desiredvnodes - desiredvnodes/10;
+
+		for (i = 0; i < __arraycount(listhd); i++) {
+			TAILQ_INSERT_HEAD(listhd[i], marker, vi_lrulist);
+			while ((node = TAILQ_NEXT(marker, vi_lrulist))) {
+				TAILQ_REMOVE(listhd[i], marker, vi_lrulist);
+				TAILQ_INSERT_AFTER(listhd[i], node, marker,
+				    vi_lrulist);
+				if (listhd[i] == &lru_vrele_list)
+					vdrain_vrele(VIMPL_TO_VNODE(node));
+				else if (numvnodes < target)
+					break;
+				else
+					vdrain_remove(VIMPL_TO_VNODE(node));
+			}
+			TAILQ_REMOVE(listhd[i], marker, vi_lrulist);
+		}
+
+		if (vdrain_retry) {
+			mutex_exit(&vdrain_lock);
+			yield();
+			mutex_enter(&vdrain_lock);
+		} else {
+			vdrain_gen++;
+			cv_broadcast(&vdrain_gen_cv);
+			cv_wait(&vdrain_cv, &vdrain_lock);
+		}
 	}
-	TAILQ_REMOVE(vi->vi_lrulisthd, vi, vi_lrulist);
-	vi->vi_lrulisthd = NULL;
-	mutex_exit(&vnode_free_list_lock);
 }
 
 /*
@@ -553,7 +605,6 @@ vget(vnode_t *vp, int flags, bool waitok
 	 * from its freelist.
 	 */
 	if (vp->v_usecount == 0) {
-		vremfree(vp);
 		vp->v_usecount = 1;
 	} else {
 		atomic_inc_uint(&vp->v_usecount);
@@ -623,12 +674,10 @@ vtryrele(vnode_t *vp)
 static void
 vrelel(vnode_t *vp, int flags)
 {
-	vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
 	bool recycle, defer;
 	int error;
 
 	KASSERT(mutex_owned(vp->v_interlock));
-	KASSERT(vi->vi_lrulisthd == NULL);
 
 	if (__predict_false(vp->v_op == dead_vnodeop_p &&
 	    VSTATE_GET(vp) != VS_RECLAIMED)) {
@@ -665,13 +714,13 @@ vrelel(vnode_t *vp, int flags)
 		 * XXX This ugly block can be largely eliminated if
 		 * locking is pushed down into the file systems.
 		 *
-		 * Defer vnode release to vrele_thread if caller
+		 * Defer vnode release to vdrain_thread if caller
 		 * requests it explicitly or is the pagedaemon.
 		 */
 		if ((curlwp == uvm.pagedaemon_lwp) ||
 		    (flags & VRELEL_ASYNC_RELE) != 0) {
 			defer = true;
-		} else if (curlwp == vrele_lwp) {
+		} else if (curlwp == vdrain_lwp) {
 			/*
 			 * We have to try harder.
 			 */
@@ -690,18 +739,14 @@ vrelel(vnode_t *vp, int flags)
 		}
 
 		KASSERT(mutex_owned(vp->v_interlock));
-		KASSERT(! (curlwp == vrele_lwp && defer));
+		KASSERT(! (curlwp == vdrain_lwp && defer));
 
 		if (defer) {
 			/*
 			 * Defer reclaim to the kthread; it's not safe to
 			 * clean it here.  We donate it our last reference.
 			 */
-			mutex_enter(&vrele_lock);
-			TAILQ_INSERT_TAIL(&vrele_list, vi, vi_lrulist);
-			if (++vrele_pending > (desiredvnodes >> 8))
-				cv_signal(&vrele_cv);
-			mutex_exit(&vrele_lock);
+			lru_requeue(vp, &lru_vrele_list);
 			mutex_exit(vp->v_interlock);
 			return;
 		}
@@ -787,14 +832,7 @@ vrelel(vnode_t *vp, int flags)
 		 * can't be destroyed while still associated with
 		 * a file system.
 		 */
-		mutex_enter(&vnode_free_list_lock);
-		if (vp->v_holdcnt > 0) {
-			vi->vi_lrulisthd = &vnode_hold_list;
-		} else {
-			vi->vi_lrulisthd = &vnode_free_list;
-		}
-		TAILQ_INSERT_TAIL(vi->vi_lrulisthd, vi, vi_lrulist);
-		mutex_exit(&vnode_free_list_lock);
+		lru_requeue(vp, lru_which(vp));
 		mutex_exit(vp->v_interlock);
 	}
 }
@@ -824,46 +862,6 @@ vrele_async(vnode_t *vp)
 	vrelel(vp, VRELEL_ASYNC_RELE);
 }
 
-static void
-vrele_thread(void *cookie)
-{
-	vnodelst_t skip_list;
-	vnode_t *vp;
-	vnode_impl_t *vi;
-	struct mount *mp;
-
-	TAILQ_INIT(&skip_list);
-
-	mutex_enter(&vrele_lock);
-	for (;;) {
-		while (TAILQ_EMPTY(&vrele_list)) {
-			vrele_gen++;
-			cv_broadcast(&vrele_cv);
-			cv_timedwait(&vrele_cv, &vrele_lock, hz);
-			TAILQ_CONCAT(&vrele_list, &skip_list, vi_lrulist);
-		}
-		vi = TAILQ_FIRST(&vrele_list);
-		vp = VIMPL_TO_VNODE(vi);
-		mp = vp->v_mount;
-		TAILQ_REMOVE(&vrele_list, vi, vi_lrulist);
-		if (fstrans_start_nowait(mp, FSTRANS_LAZY) != 0) {
-			TAILQ_INSERT_TAIL(&skip_list, vi, vi_lrulist);
-			continue;
-		}
-		vrele_pending--;
-		mutex_exit(&vrele_lock);
-
-		/*
-		 * If not the last reference, then ignore the vnode
-		 * and look for more work.
-		 */
-		mutex_enter(vp->v_interlock);
-		vrelel(vp, 0);
-		fstrans_done(mp);
-		mutex_enter(&vrele_lock);
-	}
-}
-
 /*
  * Vnode reference, where a reference is already held by some other
  * object (for example, a file structure).
@@ -884,18 +882,11 @@ vref(vnode_t *vp)
 void
 vholdl(vnode_t *vp)
 {
-	vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
 
 	KASSERT(mutex_owned(vp->v_interlock));
 
-	if (vp->v_holdcnt++ == 0 && vp->v_usecount == 0) {
-		mutex_enter(&vnode_free_list_lock);
-		KASSERT(vi->vi_lrulisthd == &vnode_free_list);
-		TAILQ_REMOVE(vi->vi_lrulisthd, vi, vi_lrulist);
-		vi->vi_lrulisthd = &vnode_hold_list;
-		TAILQ_INSERT_TAIL(vi->vi_lrulisthd, vi, vi_lrulist);
-		mutex_exit(&vnode_free_list_lock);
-	}
+	if (vp->v_holdcnt++ == 0 && vp->v_usecount == 0)
+		lru_requeue(vp, lru_which(vp));
 }
 
 /*
@@ -905,7 +896,6 @@ vholdl(vnode_t *vp)
 void
 holdrelel(vnode_t *vp)
 {
-	vnode_impl_t *vi = VNODE_TO_VIMPL(vp);
 
 	KASSERT(mutex_owned(vp->v_interlock));
 
@@ -914,14 +904,8 @@ holdrelel(vnode_t *vp)
 	}
 
 	vp->v_holdcnt--;
-	if (vp->v_holdcnt == 0 && vp->v_usecount == 0) {
-		mutex_enter(&vnode_free_list_lock);
-		KASSERT(vi->vi_lrulisthd == &vnode_hold_list);
-		TAILQ_REMOVE(vi->vi_lrulisthd, vi, vi_lrulist);
-		vi->vi_lrulisthd = &vnode_free_list;
-		TAILQ_INSERT_TAIL(vi->vi_lrulisthd, vi, vi_lrulist);
-		mutex_exit(&vnode_free_list_lock);
-	}
+	if (vp->v_holdcnt == 0 && vp->v_usecount == 0)
+		lru_requeue(vp, lru_which(vp));
 }
 
 /*
@@ -1109,12 +1093,6 @@ vcache_alloc(void)
 	/* LIST_INIT(&vp->v_nclist); */
 	/* LIST_INIT(&vp->v_dnclist); */
 
-	mutex_enter(&vnode_free_list_lock);
-	numvnodes++;
-	if (numvnodes > desiredvnodes + desiredvnodes / 10)
-		cv_signal(&vdrain_cv);
-	mutex_exit(&vnode_free_list_lock);
-
 	rw_init(&vp->v_lock);
 	vp->v_usecount = 1;
 	vp->v_type = VNON;
@@ -1122,6 +1100,8 @@ vcache_alloc(void)
 
 	node->vi_state = VS_LOADING;
 
+	lru_requeue(vp, &lru_free_list);
+
 	return node;
 }
 
@@ -1137,11 +1117,8 @@ vcache_free(vnode_impl_t *node)
 
 	KASSERT(vp->v_usecount == 0);
 
+	lru_requeue(vp, NULL);
 	rw_destroy(&vp->v_lock);
-	mutex_enter(&vnode_free_list_lock);
-	numvnodes--;
-	mutex_exit(&vnode_free_list_lock);
-
 	uvm_obj_destroy(&vp->v_uobj, true);
 	cv_destroy(&vp->v_cv);
 	pool_cache_put(vcache.pool, node);
@@ -1600,26 +1577,20 @@ vdead_check(struct vnode *vp, int flags)
 int
 vfs_drainvnodes(void)
 {
-	int error, gen;
-
-	mutex_enter(&vrele_lock);
-	gen = vrele_gen;
-	while (vrele_pending && gen == vrele_gen) {
-		cv_broadcast(&vrele_cv);
-		cv_wait(&vrele_cv, &vrele_lock);
-	}
-	mutex_exit(&vrele_lock);
+	int i, gen;
 
-	mutex_enter(&vnode_free_list_lock);
-
-	while (numvnodes > desiredvnodes) {
-		error = cleanvnode();
-		if (error != 0)
-			return error;
-		mutex_enter(&vnode_free_list_lock);
+	mutex_enter(&vdrain_lock);
+	for (i = 0; i < 2; i++) {
+		gen = vdrain_gen;
+		while (gen == vdrain_gen) {
+			cv_broadcast(&vdrain_cv);
+			cv_wait(&vdrain_gen_cv, &vdrain_lock);
+		}
 	}
+	mutex_exit(&vdrain_lock);
 
-	mutex_exit(&vnode_free_list_lock);
+	if (numvnodes >= desiredvnodes)
+		return EBUSY;
 
 	if (vcache.hashsize != desiredvnodes)
 		vcache_reinit();

Reply via email to