cluster-devel.redhat.com archive mirror
 help / color / mirror / Atom feed
From: Steven Whitehouse <swhiteho@redhat.com>
To: cluster-devel.redhat.com
Subject: [Cluster-devel] [GFS2 PATCH] GFS2: Convert LRU list to use a hash table to reduce contention
Date: Mon, 3 Jul 2017 10:21:19 +0100	[thread overview]
Message-ID: <7026f0ef-002b-bf83-4658-1cbb2fbfdd82@redhat.com> (raw)
In-Reply-To: <1075213831.27823397.1498846716685.JavaMail.zimbra@redhat.com>

Hi,


On 30/06/17 19:18, Bob Peterson wrote:
> 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@redhat.com>
This is a bit confused I think. There needs to be some more work done 
here to figure out exactly what the problem is, but I'm not very keen on 
this solution for a number of reasons...

  - It still claims to be LRU, but in fact it isn't any more
  - The list might have been broken up, but the counter is still global, 
so that there is still the possibility for contention on that
  - There is no performance data to say whether or not there is any 
improvement or not
  - There should not in general be huge numbers of LRU operations on 
glocks anyway, so why is the LRU list contended in the first place?
  - It introduces a new global counter at glock creation time, so 
something else that might land up being a contention point

The important question is what causes the contention on the LRU list in 
the first place - ideally we'd look at that issue in order to avoid so 
many list operations, rather than making it very complicated and non-LRU 
- at the very least there should be a justification for the new algorithm,

Steve.


> ---
> 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 */
>



  reply	other threads:[~2017-07-03  9:21 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <1321492262.27823235.1498846645921.JavaMail.zimbra@redhat.com>
2017-06-30 18:18 ` [Cluster-devel] [GFS2 PATCH] GFS2: Convert LRU list to use a hash table to reduce contention Bob Peterson
2017-07-03  9:21   ` Steven Whitehouse [this message]
2017-07-03 20:31     ` Bob Peterson
2017-07-04  8:55       ` Steven Whitehouse

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=7026f0ef-002b-bf83-4658-1cbb2fbfdd82@redhat.com \
    --to=swhiteho@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).