[Cluster-devel] [GFS2 PATCH] GFS2: Convert LRU list to use a hash table to reduce contention
Bob Peterson
rpeterso at redhat.com
Fri Jun 30 18:18:36 UTC 2017
Hi,
This patch changes GFS2's LRU list from a single linked list with
a much-contended spin_lock to a hash table of linked lists, thus
having more locks with less contention. The key for the table is
set round-robin to evenly distribute the glocks into the buckets.
Signed-off-by: Bob Peterson <rpeterso at redhat.com>
---
diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c
index 6cd71c5..e1e16c4 100644
--- a/fs/gfs2/glock.c
+++ b/fs/gfs2/glock.c
@@ -63,10 +63,32 @@ static void do_xmote(struct gfs2_glock *gl, struct gfs2_holder *gh, unsigned int
static struct dentry *gfs2_root;
static struct workqueue_struct *glock_workqueue;
+static atomic_t lru_bucket = ATOMIC_INIT(0);
+
struct workqueue_struct *gfs2_delete_workqueue;
-static LIST_HEAD(lru_list);
+
+#define LRU_HASH_BITS 12
+#define LRU_HASH_BUCKETS BIT(LRU_HASH_BITS)
+#define LRU_HASH_MASK (LRU_HASH_BUCKETS - 1)
+
+struct gfs2_lru_bucket {
+ struct list_head lru_list;
+ rwlock_t lru_lock;
+};
+
+static struct gfs2_lru_bucket lru_locks[LRU_HASH_BUCKETS];
+
+static inline void lock_lru_bucket(unsigned bucket)
+{
+ write_lock(&lru_locks[bucket & LRU_HASH_MASK].lru_lock);
+}
+
+static inline void unlock_lru_bucket(unsigned bucket)
+{
+ write_unlock(&lru_locks[bucket & LRU_HASH_MASK].lru_lock);
+}
+
static atomic_t lru_count = ATOMIC_INIT(0);
-static DEFINE_SPINLOCK(lru_lock);
#define GFS2_GL_HASH_SHIFT 15
#define GFS2_GL_HASH_SIZE BIT(GFS2_GL_HASH_SHIFT)
@@ -126,30 +148,36 @@ static int demote_ok(const struct gfs2_glock *gl)
return 1;
}
-
-void gfs2_glock_add_to_lru(struct gfs2_glock *gl)
+static void remove_from_lru_locked(struct gfs2_glock *gl)
{
- spin_lock(&lru_lock);
-
- if (!list_empty(&gl->gl_lru))
+ if (!list_empty(&gl->gl_lru)) {
list_del_init(&gl->gl_lru);
- else
- atomic_inc(&lru_count);
+ atomic_dec(&lru_count);
+ }
+}
+
+static void glock_remove_from_lru(struct gfs2_glock *gl)
+{
+ if (!test_and_clear_bit(GLF_LRU, &gl->gl_flags))
+ return;
- list_add_tail(&gl->gl_lru, &lru_list);
- set_bit(GLF_LRU, &gl->gl_flags);
- spin_unlock(&lru_lock);
+ lock_lru_bucket(gl->gl_lru_bucket);
+ remove_from_lru_locked(gl);
+ unlock_lru_bucket(gl->gl_lru_bucket);
}
-static void gfs2_glock_remove_from_lru(struct gfs2_glock *gl)
+void gfs2_glock_add_to_lru(struct gfs2_glock *gl)
{
- spin_lock(&lru_lock);
- if (!list_empty(&gl->gl_lru)) {
- list_del_init(&gl->gl_lru);
- atomic_dec(&lru_count);
- clear_bit(GLF_LRU, &gl->gl_flags);
- }
- spin_unlock(&lru_lock);
+ glock_remove_from_lru(gl);
+ if (test_and_set_bit(GLF_LRU, &gl->gl_flags))
+ return;
+
+ lock_lru_bucket(gl->gl_lru_bucket);
+ BUG_ON(!list_empty(&gl->gl_lru));
+ atomic_inc(&lru_count);
+ list_add_tail(&gl->gl_lru,
+ &lru_locks[gl->gl_lru_bucket & LRU_HASH_MASK].lru_list);
+ unlock_lru_bucket(gl->gl_lru_bucket);
}
/*
@@ -182,7 +210,7 @@ static void __gfs2_glock_put(struct gfs2_glock *gl)
lockref_mark_dead(&gl->gl_lockref);
- gfs2_glock_remove_from_lru(gl);
+ glock_remove_from_lru(gl);
spin_unlock(&gl->gl_lockref.lock);
rhashtable_remove_fast(&gl_hash_table, &gl->gl_node, ht_parms);
GLOCK_BUG_ON(gl, !list_empty(&gl->gl_holders));
@@ -746,6 +774,11 @@ int gfs2_glock_get(struct gfs2_sbd *sdp, u64 number,
gl->gl_hold_time = GL_GLOCK_DFT_HOLD;
INIT_DELAYED_WORK(&gl->gl_work, glock_work_func);
INIT_WORK(&gl->gl_delete, delete_work_func);
+ gl->gl_lru_bucket = atomic_inc_return(&lru_bucket);
+ if (gl->gl_lru_bucket >= LRU_HASH_BUCKETS) {
+ gl->gl_lru_bucket = 0;
+ atomic_set(&lru_bucket, gl->gl_lru_bucket);
+ }
mapping = gfs2_glock2aspace(gl);
if (mapping) {
@@ -1010,8 +1043,7 @@ int gfs2_glock_nq(struct gfs2_holder *gh)
if (unlikely(test_bit(SDF_SHUTDOWN, &sdp->sd_flags)))
return -EIO;
- if (test_bit(GLF_LRU, &gl->gl_flags))
- gfs2_glock_remove_from_lru(gl);
+ glock_remove_from_lru(gl);
spin_lock(&gl->gl_lockref.lock);
add_to_queue(gh);
@@ -1343,24 +1375,20 @@ static int glock_cmp(void *priv, struct list_head *a, struct list_head *b)
}
/**
- * gfs2_dispose_glock_lru - Demote a list of glocks
+ * gfs2_dispose_glock_lru - Demote a list of glocks from the same LRU bucket
* @list: The list to dispose of
*
* Disposing of glocks may involve disk accesses, so that here we sort
* the glocks by number (i.e. disk location of the inodes) so that if
* there are any such accesses, they'll be sent in order (mostly).
- *
- * Must be called under the lru_lock, but may drop and retake this
- * lock. While the lru_lock is dropped, entries may vanish from the
- * list, but no new entries will appear on the list (since it is
- * private)
*/
-static void gfs2_dispose_glock_lru(struct list_head *list)
-__releases(&lru_lock)
-__acquires(&lru_lock)
+static long gfs2_dispose_glock_lru(struct list_head *list)
{
- struct gfs2_glock *gl;
+ struct gfs2_glock *gl = NULL;
+ LIST_HEAD(rejects);
+ int reject_count = 0;
+ long disposed = 0;
list_sort(NULL, list, glock_cmp);
@@ -1369,23 +1397,30 @@ __acquires(&lru_lock)
list_del_init(&gl->gl_lru);
if (!spin_trylock(&gl->gl_lockref.lock)) {
add_back_to_lru:
- list_add(&gl->gl_lru, &lru_list);
- atomic_inc(&lru_count);
+ list_add_tail(&gl->gl_lru, &rejects);
+ reject_count++;
continue;
}
if (test_and_set_bit(GLF_LOCK, &gl->gl_flags)) {
spin_unlock(&gl->gl_lockref.lock);
goto add_back_to_lru;
}
- clear_bit(GLF_LRU, &gl->gl_flags);
+ disposed++;
gl->gl_lockref.count++;
if (demote_ok(gl))
handle_callback(gl, LM_ST_UNLOCKED, 0, false);
WARN_ON(!test_and_clear_bit(GLF_LOCK, &gl->gl_flags));
__gfs2_glock_queue_work(gl, 0);
spin_unlock(&gl->gl_lockref.lock);
- cond_resched_lock(&lru_lock);
}
+ if (!list_empty(&rejects)) {
+ gl = list_first_entry(&rejects, struct gfs2_glock, gl_lru);
+ lock_lru_bucket(gl->gl_lru_bucket);
+ list_splice(&rejects, &lru_locks[gl->gl_lru_bucket].lru_list);
+ atomic_add(reject_count, &lru_count);
+ unlock_lru_bucket(gl->gl_lru_bucket);
+ }
+ return disposed;
}
/**
@@ -1399,30 +1434,33 @@ __acquires(&lru_lock)
static long gfs2_scan_glock_lru(int nr)
{
- struct gfs2_glock *gl;
- LIST_HEAD(skipped);
+ struct gfs2_glock *gl, *tmp;
LIST_HEAD(dispose);
+ int start_bucket, bucket;
long freed = 0;
+ static int last_lru_scan = 0;
- spin_lock(&lru_lock);
- while ((nr-- >= 0) && !list_empty(&lru_list)) {
- gl = list_entry(lru_list.next, struct gfs2_glock, gl_lru);
-
- /* Test for being demotable */
- if (!test_bit(GLF_LOCK, &gl->gl_flags)) {
- list_move(&gl->gl_lru, &dispose);
- atomic_dec(&lru_count);
- freed++;
- continue;
+ start_bucket = bucket = (last_lru_scan & LRU_HASH_MASK);
+ do {
+ lock_lru_bucket(bucket);
+ list_for_each_entry_safe(gl, tmp, &lru_locks[bucket].lru_list,
+ gl_lru) {
+ /* Test for being demotable */
+ if (!test_bit(GLF_LOCK, &gl->gl_flags)) {
+ if (test_and_clear_bit(GLF_LRU, &gl->gl_flags))
+ remove_from_lru_locked(gl);
+ list_add_tail(&gl->gl_lru, &dispose);
+ nr--;
+ if (nr == 0)
+ break;
+ }
}
-
- list_move(&gl->gl_lru, &skipped);
- }
- list_splice(&skipped, &lru_list);
- if (!list_empty(&dispose))
- gfs2_dispose_glock_lru(&dispose);
- spin_unlock(&lru_lock);
-
+ unlock_lru_bucket(bucket);
+ if (!list_empty(&dispose))
+ freed += gfs2_dispose_glock_lru(&dispose);
+ bucket = (bucket + 1) & LRU_HASH_MASK;
+ } while (nr && (bucket != start_bucket));
+ last_lru_scan = bucket;
return freed;
}
@@ -1504,7 +1542,7 @@ static void thaw_glock(struct gfs2_glock *gl)
static void clear_glock(struct gfs2_glock *gl)
{
- gfs2_glock_remove_from_lru(gl);
+ glock_remove_from_lru(gl);
spin_lock(&gl->gl_lockref.lock);
if (gl->gl_state != LM_ST_UNLOCKED)
@@ -1704,7 +1742,7 @@ void gfs2_dump_glock(struct seq_file *seq, const struct gfs2_glock *gl)
dtime *= 1000000/HZ; /* demote time in uSec */
if (!test_bit(GLF_DEMOTE, &gl->gl_flags))
dtime = 0;
- gfs2_print_dbg(seq, "G: s:%s n:%u/%llx f:%s t:%s d:%s/%llu a:%d v:%d r:%d m:%ld\n",
+ gfs2_print_dbg(seq, "G: s:%s n:%u/%llx f:%s t:%s d:%s/%llu a:%d v:%d r:%d m:%ld l:%d\n",
state2str(gl->gl_state),
gl->gl_name.ln_type,
(unsigned long long)gl->gl_name.ln_number,
@@ -1713,7 +1751,8 @@ void gfs2_dump_glock(struct seq_file *seq, const struct gfs2_glock *gl)
state2str(gl->gl_demote_state), dtime,
atomic_read(&gl->gl_ail_count),
atomic_read(&gl->gl_revokes),
- (int)gl->gl_lockref.count, gl->gl_hold_time);
+ (int)gl->gl_lockref.count, gl->gl_hold_time,
+ gl->gl_lru_bucket);
list_for_each_entry(gh, &gl->gl_holders, gh_list)
dump_holder(seq, gh);
@@ -1797,6 +1836,12 @@ static int gfs2_sbstats_seq_show(struct seq_file *seq, void *iter_ptr)
int __init gfs2_glock_init(void)
{
int ret;
+ unsigned i;
+
+ for (i = 0; i < LRU_HASH_BUCKETS; i++) {
+ INIT_LIST_HEAD(&lru_locks[i].lru_list);
+ rwlock_init(&lru_locks[i].lru_lock);
+ }
ret = rhashtable_init(&gl_hash_table, &ht_parms);
if (ret < 0)
diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h
index 2ad003b..e8c0f2b 100644
--- a/fs/gfs2/incore.h
+++ b/fs/gfs2/incore.h
@@ -374,6 +374,7 @@ struct gfs2_glock {
} gl_vm;
};
struct rhash_head gl_node;
+ int gl_lru_bucket;
};
#define GFS2_MIN_LVB_SIZE 32 /* Min size of LVB that gfs2 supports */
More information about the Cluster-devel
mailing list