RE: Poor thread performance on Linux vs. Solaris

"Hu, Boris" <[email protected]>
Newsgroups gmane.comp.lib.phil
Message-ID <37FBBA5F3A361C41AB7CE44558C3448E01C0B4F4@pdsmsx403.ccr.corp.intel.com>
Try the futex_q_lock-0.2 patch. It is also against linux-2.6.0-test4. 

It does the following things:
* Remove the global futex_lock as the previous futex_q_lock patch did.
* Add bucket spinlock recursively check as Jakub mentioned.
* Move vcache_lock out of lock/unlock_futex_mm() and only to protect the actual vcache operations.
* Shrink some lock/unlock_futex_mm() scopes.

boris

--- linux-2.6.0-test4.orig/kernel/futex.c	2003-08-23 07:53:39.000000000 +0800
+++ linux-2.6.0-test4/kernel/futex.c	2003-09-09 14:15:02.000000000 +0800
@@ -57,9 +57,16 @@
 	struct file *filp;
 };
 
+/* 
+ * Split the global futex_lock into every hash list lock.
+ */ 
+struct futex_hash_bucket {
+	struct list_head	chain;
+	spinlock_t		lock;
+};
+
 /* The key for the hash is the address + index + offset within page */
-static struct list_head futex_queues[1<<FUTEX_HASHBITS];
-static spinlock_t futex_lock = SPIN_LOCK_UNLOCKED;
+static struct futex_hash_bucket futex_queues[1<<FUTEX_HASHBITS];
 
 extern void send_sigio(struct fown_struct *fown, int fd, int band);
 
@@ -73,21 +80,17 @@
 static inline void lock_futex_mm(void)
 {
 	spin_lock(&current->mm->page_table_lock);
-	spin_lock(&vcache_lock);
-	spin_lock(&futex_lock);
 }
 
 static inline void unlock_futex_mm(void)
 {
-	spin_unlock(&futex_lock);
-	spin_unlock(&vcache_lock);
 	spin_unlock(&current->mm->page_table_lock);
 }
 
 /*
  * The physical page is shared, so we can hash on its address:
  */
-static inline struct list_head *hash_futex(struct page *page, int offset)
+static inline struct futex_hash_bucket *hash_futex(struct page *page, int offset)
 {
 	return &futex_queues[hash_long((unsigned long)page + offset,
 							FUTEX_HASHBITS)];
@@ -153,6 +156,7 @@
 static inline int futex_wake(unsigned long uaddr, int offset, int num)
 {
 	struct list_head *i, *next, *head;
+	struct futex_hash_bucket *bh;
 	struct page *page;
 	int ret = 0;
 
@@ -164,14 +168,22 @@
 		return -EFAULT;
 	}
 
-	head = hash_futex(page, offset);
+	unlock_futex_mm();
+
+	bh = hash_futex(page, offset);
+	spin_lock(&bh->lock);
+	head = &bh->chain;
 
 	list_for_each_safe(i, next, head) {
 		struct futex_q *this = list_entry(i, struct futex_q, list);
 
 		if (this->page == page && this->offset == offset) {
 			list_del_init(i);
+
+			spin_lock(&vcache_lock);
 			__detach_vcache(&this->vcache);
+			spin_unlock(&vcache_lock);
+
 			wake_up_all(&this->waiters);
 			if (this->filp)
 				send_sigio(&this->filp->f_owner, this->fd, POLL_IN);
@@ -180,8 +192,8 @@
 				break;
 		}
 	}
+	spin_unlock(&bh->lock);
 
-	unlock_futex_mm();
 	put_page(page);
 
 	return ret;
@@ -196,19 +208,19 @@
 static void futex_vcache_callback(vcache_t *vcache, struct page *new_page)
 {
 	struct futex_q *q = container_of(vcache, struct futex_q, vcache);
-	struct list_head *head = hash_futex(new_page, q->offset);
+	struct futex_hash_bucket *head = hash_futex(new_page, q->offset);
 
-	spin_lock(&futex_lock);
+	spin_lock(&head->lock);
 
 	if (!list_empty(&q->list)) {
 		put_page(q->page);
 		q->page = new_page;
 		__pin_page_atomic(new_page);
 		list_del(&q->list);
-		list_add_tail(&q->list, head);
+		list_add_tail(&q->list, &head->chain);
 	}
 
-	spin_unlock(&futex_lock);
+	spin_unlock(&head->lock);
 }
 
 /*
@@ -219,6 +231,7 @@
 	unsigned long uaddr2, int offset2, int nr_wake, int nr_requeue)
 {
 	struct list_head *i, *next, *head1, *head2;
+	struct futex_hash_bucket *bh1, *bh2;
 	struct page *page1 = NULL, *page2 = NULL;
 	int ret = 0;
 
@@ -231,26 +244,38 @@
 	if (!page2)
 		goto out;
 
-	head1 = hash_futex(page1, offset1);
-	head2 = hash_futex(page2, offset2);
+	unlock_futex_mm();
 
+	bh1 = hash_futex(page1, offset1);
+	bh2 = hash_futex(page2, offset2);
+	spin_lock(&bh1->lock);
+	if (bh1 != bh2)
+		spin_lock(&bh2->lock);
+	head1 = &bh1->chain;
+	head2 = &bh2->chain;
+       
 	list_for_each_safe(i, next, head1) {
 		struct futex_q *this = list_entry(i, struct futex_q, list);
 
 		if (this->page == page1 && this->offset == offset1) {
 			list_del_init(i);
+			spin_lock(&vcache_lock);
 			__detach_vcache(&this->vcache);
 			if (++ret <= nr_wake) {
+				spin_unlock(&vcache_lock);
 				wake_up_all(&this->waiters);
 				if (this->filp)
 					send_sigio(&this->filp->f_owner,
 							this->fd, POLL_IN);
 			} else {
 				put_page(this->page);
+				lock_futex_mm();
 				__pin_page_atomic (page2);
 				list_add_tail(i, head2);
 				__attach_vcache(&this->vcache, uaddr2,
 					current->mm, futex_vcache_callback);
+				unlock_futex_mm();
+				spin_unlock(&vcache_lock);
 				this->offset = offset2;
 				this->page = page2;
 				if (ret - nr_wake >= nr_requeue)
@@ -260,7 +285,9 @@
 	}
 
 out:
-	unlock_futex_mm();
+	if (bh1 != bh2)
+		spin_unlock(&bh2->lock);
+	spin_unlock(&bh1->lock);
 
 	if (page1)
 		put_page(page1);
@@ -274,7 +301,7 @@
 				unsigned long uaddr, int offset,
 				int fd, struct file *filp)
 {
-	struct list_head *head = hash_futex(page, offset);
+	struct list_head *head = &hash_futex(page, offset)->chain;
 
 	q->offset = offset;
 	q->fd = fd;
@@ -293,15 +320,16 @@
 static inline int unqueue_me(struct futex_q *q)
 {
 	int ret = 0;
+	struct futex_hash_bucket *bh = hash_futex(q->page, q->offset);
 
 	spin_lock(&vcache_lock);
-	spin_lock(&futex_lock);
+	spin_lock(&bh->lock);
 	if (!list_empty(&q->list)) {
 		list_del(&q->list);
 		__detach_vcache(&q->vcache);
 		ret = 1;
 	}
-	spin_unlock(&futex_lock);
+	spin_unlock(&bh->lock);
 	spin_unlock(&vcache_lock);
 	return ret;
 }
@@ -315,6 +343,7 @@
 	int ret = 0, curval;
 	struct page *page;
 	struct futex_q q;
+	struct futex_hash_bucket *bh;
 
 	init_waitqueue_head(&q.waiters);
 
@@ -325,18 +354,27 @@
 		unlock_futex_mm();
 		return -EFAULT;
 	}
+	
+	bh = hash_futex(page, offset);
+	spin_lock(&bh->lock);
+	spin_lock(&vcache_lock);
+
 	__queue_me(&q, page, uaddr, offset, -1, NULL);
 
+	spin_unlock(&vcache_lock);
+
 	/*
 	 * Page is pinned, but may no longer be in this address space.
 	 * It cannot schedule, so we access it with the spinlock held.
 	 */
 	if (get_user(curval, (int *)uaddr) != 0) {
+		spin_unlock(&bh->lock);
 		unlock_futex_mm();
 		ret = -EFAULT;
 		goto out;
 	}
 	if (curval != val) {
+		spin_unlock(&bh->lock);
 		unlock_futex_mm();
 		ret = -EWOULDBLOCK;
 		goto out;
@@ -351,6 +389,7 @@
 	add_wait_queue(&q.waiters, &wait);
 	set_current_state(TASK_INTERRUPTIBLE);
 	if (!list_empty(&q.list)) {
+		spin_unlock(&bh->lock);
 		unlock_futex_mm();
 		time = schedule_timeout(time);
 	}
@@ -389,13 +428,14 @@
 			       struct poll_table_struct *wait)
 {
 	struct futex_q *q = filp->private_data;
+	struct futex_hash_bucket *bh = hash_futex(q->page, q->offset);
 	int ret = 0;
 
 	poll_wait(filp, &q->waiters, wait);
-	spin_lock(&futex_lock);
+	spin_lock(&bh->lock);
 	if (list_empty(&q->list))
 		ret = POLLIN | POLLRDNORM;
-	spin_unlock(&futex_lock);
+	spin_unlock(&bh->lock);
 
 	return ret;
 }
@@ -411,6 +451,7 @@
 {
 	struct page *page = NULL;
 	struct futex_q *q;
+	struct futex_hash_bucket *bh;
 	struct file *filp;
 	int ret;
 
@@ -466,8 +507,14 @@
 	init_waitqueue_head(&q->waiters);
 	filp->private_data = q;
 
+	bh = hash_futex(page, offset);
+	spin_lock(&bh->lock);
+	spin_lock(&vcache_lock);
+
 	__queue_me(q, page, uaddr, offset, ret, filp);
 
+	spin_unlock(&vcache_lock);
+	spin_unlock(&bh->lock);
 	unlock_futex_mm();
 
 	/* Now we map fd to filp, so userspace can access it */
@@ -563,8 +610,10 @@
 	register_filesystem(&futex_fs_type);
 	futex_mnt = kern_mount(&futex_fs_type);
 
-	for (i = 0; i < ARRAY_SIZE(futex_queues); i++)
-		INIT_LIST_HEAD(&futex_queues[i]);
+	for (i = 0; i < ARRAY_SIZE(futex_queues); i++) {
+		futex_queues[i].lock = SPIN_LOCK_UNLOCKED;
+		INIT_LIST_HEAD(&futex_queues[i].chain);
+	}
 	return 0;
 }
 __initcall(init);

> 
> > From: Bill Soudan [mailto:[email protected]]
> >
> > It applies cleanly and the performance test runs to completion, but
> > unfortunately, it doesn't appear to make much of a difference wrt to
> > system time.  I'm going to spend some time with oprofile again now that
> I
> > have your patch applied, and see if I receive different results.
> 
> It'd be interesting to verify if there is still a lot of contention in
> the locking.
> 
> A question about your app: are there many different locks used at the
> same
> time or the number is actually low?
> 
> No matter how we do it, there is still a bunch of lock acquisition in
> the futex code that would serialize access [specially if they are locks
> within the same thread, like current->mm->page_table_lock].
> 
> And then we also have vcache_lock--this one is also serializing
> everything,
> as it is taken unconditionally whenever we call lock_futex_mm().
> 
> Probably if we took vcache_lock() just before doing any actual vcache
> operation, we'd at least improve the preemptability on that.
> 
> We still need to deal with current->mm->page_table_lock. I am kind of
> more green on that one, but here is a question: wouldn't it be possible
> to drop it once we have pinned the page? This is the one that is going
> to hurt when interlocking between threads of the same process.
> 
> Iñaky Pérez-González -- Not speaking for Intel -- all opinions are my
> own (and my fault)
futex_q_lock-0.2.diff (application/octet-stream, 7.1 KB)
--- linux-2.6.0-test4.orig/kernel/futex.c	2003-08-23 07:53:39.000000000 +0800
+++ linux-2.6.0-test4/kernel/futex.c	2003-09-09 14:15:02.000000000 +0800
@@ -57,9 +57,16 @@
 	struct file *filp;
 };
 
+/* 
+ * Split the global futex_lock into every hash list lock.
+ */ 
+struct futex_hash_bucket {
+	struct list_head	chain;
+	spinlock_t		lock;
+};
+
 /* The key for the hash is the address + index + offset within page */
-static struct list_head futex_queues[1<<FUTEX_HASHBITS];
-static spinlock_t futex_lock = SPIN_LOCK_UNLOCKED;
+static struct futex_hash_bucket futex_queues[1<<FUTEX_HASHBITS];
 
 extern void send_sigio(struct fown_struct *fown, int fd, int band);
 
@@ -73,21 +80,17 @@
 static inline void lock_futex_mm(void)
 {
 	spin_lock(&current->mm->page_table_lock);
-	spin_lock(&vcache_lock);
-	spin_lock(&futex_lock);
 }
 
 static inline void unlock_futex_mm(void)
 {
-	spin_unlock(&futex_lock);
-	spin_unlock(&vcache_lock);
 	spin_unlock(&current->mm->page_table_lock);
 }
 
 /*
  * The physical page is shared, so we can hash on its address:
  */
-static inline struct list_head *hash_futex(struct page *page, int offset)
+static inline struct futex_hash_bucket *hash_futex(struct page *page, int offset)
 {
 	return &futex_queues[hash_long((unsigned long)page + offset,
 							FUTEX_HASHBITS)];
@@ -153,6 +156,7 @@
 static inline int futex_wake(unsigned long uaddr, int offset, int num)
 {
 	struct list_head *i, *next, *head;
+	struct futex_hash_bucket *bh;
 	struct page *page;
 	int ret = 0;
 
@@ -164,14 +168,22 @@
 		return -EFAULT;
 	}
 
-	head = hash_futex(page, offset);
+	unlock_futex_mm();
+
+	bh = hash_futex(page, offset);
+	spin_lock(&bh->lock);
+	head = &bh->chain;
 
 	list_for_each_safe(i, next, head) {
 		struct futex_q *this = list_entry(i, struct futex_q, list);
 
 		if (this->page == page && this->offset == offset) {
 			list_del_init(i);
+
+			spin_lock(&vcache_lock);
 			__detach_vcache(&this->vcache);
+			spin_unlock(&vcache_lock);
+
 			wake_up_all(&this->waiters);
 			if (this->filp)
 				send_sigio(&this->filp->f_owner, this->fd, POLL_IN);
@@ -180,8 +192,8 @@
 				break;
 		}
 	}
+	spin_unlock(&bh->lock);
 
-	unlock_futex_mm();
 	put_page(page);
 
 	return ret;
@@ -196,19 +208,19 @@
 static void futex_vcache_callback(vcache_t *vcache, struct page *new_page)
 {
 	struct futex_q *q = container_of(vcache, struct futex_q, vcache);
-	struct list_head *head = hash_futex(new_page, q->offset);
+	struct futex_hash_bucket *head = hash_futex(new_page, q->offset);
 
-	spin_lock(&futex_lock);
+	spin_lock(&head->lock);
 
 	if (!list_empty(&q->list)) {
 		put_page(q->page);
 		q->page = new_page;
 		__pin_page_atomic(new_page);
 		list_del(&q->list);
-		list_add_tail(&q->list, head);
+		list_add_tail(&q->list, &head->chain);
 	}
 
-	spin_unlock(&futex_lock);
+	spin_unlock(&head->lock);
 }
 
 /*
@@ -219,6 +231,7 @@
 	unsigned long uaddr2, int offset2, int nr_wake, int nr_requeue)
 {
 	struct list_head *i, *next, *head1, *head2;
+	struct futex_hash_bucket *bh1, *bh2;
 	struct page *page1 = NULL, *page2 = NULL;
 	int ret = 0;
 
@@ -231,26 +244,38 @@
 	if (!page2)
 		goto out;
 
-	head1 = hash_futex(page1, offset1);
-	head2 = hash_futex(page2, offset2);
+	unlock_futex_mm();
 
+	bh1 = hash_futex(page1, offset1);
+	bh2 = hash_futex(page2, offset2);
+	spin_lock(&bh1->lock);
+	if (bh1 != bh2)
+		spin_lock(&bh2->lock);
+	head1 = &bh1->chain;
+	head2 = &bh2->chain;
+       
 	list_for_each_safe(i, next, head1) {
 		struct futex_q *this = list_entry(i, struct futex_q, list);
 
 		if (this->page == page1 && this->offset == offset1) {
 			list_del_init(i);
+			spin_lock(&vcache_lock);
 			__detach_vcache(&this->vcache);
 			if (++ret <= nr_wake) {
+				spin_unlock(&vcache_lock);
 				wake_up_all(&this->waiters);
 				if (this->filp)
 					send_sigio(&this->filp->f_owner,
 							this->fd, POLL_IN);
 			} else {
 				put_page(this->page);
+				lock_futex_mm();
 				__pin_page_atomic (page2);
 				list_add_tail(i, head2);
 				__attach_vcache(&this->vcache, uaddr2,
 					current->mm, futex_vcache_callback);
+				unlock_futex_mm();
+				spin_unlock(&vcache_lock);
 				this->offset = offset2;
 				this->page = page2;
 				if (ret - nr_wake >= nr_requeue)
@@ -260,7 +285,9 @@
 	}
 
 out:
-	unlock_futex_mm();
+	if (bh1 != bh2)
+		spin_unlock(&bh2->lock);
+	spin_unlock(&bh1->lock);
 
 	if (page1)
 		put_page(page1);
@@ -274,7 +301,7 @@
 				unsigned long uaddr, int offset,
 				int fd, struct file *filp)
 {
-	struct list_head *head = hash_futex(page, offset);
+	struct list_head *head = &hash_futex(page, offset)->chain;
 
 	q->offset = offset;
 	q->fd = fd;
@@ -293,15 +320,16 @@
 static inline int unqueue_me(struct futex_q *q)
 {
 	int ret = 0;
+	struct futex_hash_bucket *bh = hash_futex(q->page, q->offset);
 
 	spin_lock(&vcache_lock);
-	spin_lock(&futex_lock);
+	spin_lock(&bh->lock);
 	if (!list_empty(&q->list)) {
 		list_del(&q->list);
 		__detach_vcache(&q->vcache);
 		ret = 1;
 	}
-	spin_unlock(&futex_lock);
+	spin_unlock(&bh->lock);
 	spin_unlock(&vcache_lock);
 	return ret;
 }
@@ -315,6 +343,7 @@
 	int ret = 0, curval;
 	struct page *page;
 	struct futex_q q;
+	struct futex_hash_bucket *bh;
 
 	init_waitqueue_head(&q.waiters);
 
@@ -325,18 +354,27 @@
 		unlock_futex_mm();
 		return -EFAULT;
 	}
+	
+	bh = hash_futex(page, offset);
+	spin_lock(&bh->lock);
+	spin_lock(&vcache_lock);
+
 	__queue_me(&q, page, uaddr, offset, -1, NULL);
 
+	spin_unlock(&vcache_lock);
+
 	/*
 	 * Page is pinned, but may no longer be in this address space.
 	 * It cannot schedule, so we access it with the spinlock held.
 	 */
 	if (get_user(curval, (int *)uaddr) != 0) {
+		spin_unlock(&bh->lock);
 		unlock_futex_mm();
 		ret = -EFAULT;
 		goto out;
 	}
 	if (curval != val) {
+		spin_unlock(&bh->lock);
 		unlock_futex_mm();
 		ret = -EWOULDBLOCK;
 		goto out;
@@ -351,6 +389,7 @@
 	add_wait_queue(&q.waiters, &wait);
 	set_current_state(TASK_INTERRUPTIBLE);
 	if (!list_empty(&q.list)) {
+		spin_unlock(&bh->lock);
 		unlock_futex_mm();
 		time = schedule_timeout(time);
 	}
@@ -389,13 +428,14 @@
 			       struct poll_table_struct *wait)
 {
 	struct futex_q *q = filp->private_data;
+	struct futex_hash_bucket *bh = hash_futex(q->page, q->offset);
 	int ret = 0;
 
 	poll_wait(filp, &q->waiters, wait);
-	spin_lock(&futex_lock);
+	spin_lock(&bh->lock);
 	if (list_empty(&q->list))
 		ret = POLLIN | POLLRDNORM;
-	spin_unlock(&futex_lock);
+	spin_unlock(&bh->lock);
 
 	return ret;
 }
@@ -411,6 +451,7 @@
 {
 	struct page *page = NULL;
 	struct futex_q *q;
+	struct futex_hash_bucket *bh;
 	struct file *filp;
 	int ret;
 
@@ -466,8 +507,14 @@
 	init_waitqueue_head(&q->waiters);
 	filp->private_data = q;
 
+	bh = hash_futex(page, offset);
+	spin_lock(&bh->lock);
+	spin_lock(&vcache_lock);
+
 	__queue_me(q, page, uaddr, offset, ret, filp);
 
+	spin_unlock(&vcache_lock);
+	spin_unlock(&bh->lock);
 	unlock_futex_mm();
 
 	/* Now we map fd to filp, so userspace can access it */
@@ -563,8 +610,10 @@
 	register_filesystem(&futex_fs_type);
 	futex_mnt = kern_mount(&futex_fs_type);
 
-	for (i = 0; i < ARRAY_SIZE(futex_queues); i++)
-		INIT_LIST_HEAD(&futex_queues[i]);
+	for (i = 0; i < ARRAY_SIZE(futex_queues); i++) {
+		futex_queues[i].lock = SPIN_LOCK_UNLOCKED;
+		INIT_LIST_HEAD(&futex_queues[i].chain);
+	}
 	return 0;
 }
 __initcall(init);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.