diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2013-05-07 12:22:03 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-05-07 12:22:03 -0400 |
| commit | c8de2fa4dc2778ae3605925c127b3deac54b2b3a (patch) | |
| tree | 3b123a360b3b118d0f43efdb9180d205f8d4ccd7 /lib | |
| parent | 0f47c9423c0fe468d0b5b153f9b9d6e8e20707eb (diff) | |
| parent | b5f541810ea9fb98d93c0ee0e00e07a22874856f (diff) | |
Merge branch 'rwsem-optimizations'
Merge rwsem optimizations from Michel Lespinasse:
"These patches extend Alex Shi's work (which added write lock stealing
on the rwsem slow path) in order to provide rwsem write lock stealing
on the fast path (that is, without taking the rwsem's wait_lock).
I have unfortunately been unable to push this through -next before due
to Ingo Molnar / David Howells / Peter Zijlstra being busy with other
things. However, this has gotten some attention from Rik van Riel and
Davidlohr Bueso who both commented that they felt this was ready for
v3.10, and Ingo Molnar has said that he was OK with me pushing
directly to you. So, here goes :)
Davidlohr got the following test results from pgbench running on a
quad-core laptop:
| db_size | clients | tps-vanilla | tps-rwsem |
+---------+----------+----------------+--------------+
| 160 MB | 1 | 5803 | 6906 | + 19.0%
| 160 MB | 2 | 13092 | 15931 |
| 160 MB | 4 | 29412 | 33021 |
| 160 MB | 8 | 32448 | 34626 |
| 160 MB | 16 | 32758 | 33098 |
| 160 MB | 20 | 26940 | 31343 | + 16.3%
| 160 MB | 30 | 25147 | 28961 |
| 160 MB | 40 | 25484 | 26902 |
| 160 MB | 50 | 24528 | 25760 |
------------------------------------------------------
| 1.6 GB | 1 | 5733 | 7729 | + 34.8%
| 1.6 GB | 2 | 9411 | 19009 | + 101.9%
| 1.6 GB | 4 | 31818 | 33185 |
| 1.6 GB | 8 | 33700 | 34550 |
| 1.6 GB | 16 | 32751 | 33079 |
| 1.6 GB | 20 | 30919 | 31494 |
| 1.6 GB | 30 | 28540 | 28535 |
| 1.6 GB | 40 | 26380 | 27054 |
| 1.6 GB | 50 | 25241 | 25591 |
------------------------------------------------------
| 7.6 GB | 1 | 5779 | 6224 |
| 7.6 GB | 2 | 10897 | 13611 | + 24.9%
| 7.6 GB | 4 | 32683 | 33108 |
| 7.6 GB | 8 | 33968 | 34712 |
| 7.6 GB | 16 | 32287 | 32895 |
| 7.6 GB | 20 | 27770 | 31689 | + 14.1%
| 7.6 GB | 30 | 26739 | 29003 |
| 7.6 GB | 40 | 24901 | 26683 |
| 7.6 GB | 50 | 17115 | 25925 | + 51.5%
------------------------------------------------------
(Davidlohr also has one additional patch which further improves
throughput, though I will ask him to send it directly to you as I have
suggested some minor changes)."
* emailed patches from Michel Lespinasse <walken@google.com>:
rwsem: no need for explicit signed longs
x86 rwsem: avoid taking slow path when stealing write lock
rwsem: do not block readers at head of queue if other readers are active
rwsem: implement support for write lock stealing on the fastpath
rwsem: simplify __rwsem_do_wake
rwsem: skip initial trylock in rwsem_down_write_failed
rwsem: avoid taking wait_lock in rwsem_down_write_failed
rwsem: use cmpxchg for trying to steal write lock
rwsem: more agressive lock stealing in rwsem_down_write_failed
rwsem: simplify rwsem_down_write_failed
rwsem: simplify rwsem_down_read_failed
rwsem: move rwsem_down_failed_common code into rwsem_down_{read,write}_failed
rwsem: shorter spinlocked section in rwsem_down_failed_common()
rwsem: make the waiter type an enumeration rather than a bitmask
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/rwsem-spinlock.c | 38 | ||||
| -rw-r--r-- | lib/rwsem.c | 240 |
2 files changed, 132 insertions, 146 deletions
diff --git a/lib/rwsem-spinlock.c b/lib/rwsem-spinlock.c index 7542afbb22b3..9be8a9144978 100644 --- a/lib/rwsem-spinlock.c +++ b/lib/rwsem-spinlock.c | |||
| @@ -9,12 +9,15 @@ | |||
| 9 | #include <linux/sched.h> | 9 | #include <linux/sched.h> |
| 10 | #include <linux/export.h> | 10 | #include <linux/export.h> |
| 11 | 11 | ||
| 12 | enum rwsem_waiter_type { | ||
| 13 | RWSEM_WAITING_FOR_WRITE, | ||
| 14 | RWSEM_WAITING_FOR_READ | ||
| 15 | }; | ||
| 16 | |||
| 12 | struct rwsem_waiter { | 17 | struct rwsem_waiter { |
| 13 | struct list_head list; | 18 | struct list_head list; |
| 14 | struct task_struct *task; | 19 | struct task_struct *task; |
| 15 | unsigned int flags; | 20 | enum rwsem_waiter_type type; |
| 16 | #define RWSEM_WAITING_FOR_READ 0x00000001 | ||
| 17 | #define RWSEM_WAITING_FOR_WRITE 0x00000002 | ||
| 18 | }; | 21 | }; |
| 19 | 22 | ||
| 20 | int rwsem_is_locked(struct rw_semaphore *sem) | 23 | int rwsem_is_locked(struct rw_semaphore *sem) |
| @@ -67,26 +70,17 @@ __rwsem_do_wake(struct rw_semaphore *sem, int wakewrite) | |||
| 67 | 70 | ||
| 68 | waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list); | 71 | waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list); |
| 69 | 72 | ||
| 70 | if (!wakewrite) { | 73 | if (waiter->type == RWSEM_WAITING_FOR_WRITE) { |
| 71 | if (waiter->flags & RWSEM_WAITING_FOR_WRITE) | 74 | if (wakewrite) |
| 72 | goto out; | 75 | /* Wake up a writer. Note that we do not grant it the |
| 73 | goto dont_wake_writers; | 76 | * lock - it will have to acquire it when it runs. */ |
| 74 | } | 77 | wake_up_process(waiter->task); |
| 75 | |||
| 76 | /* | ||
| 77 | * as we support write lock stealing, we can't set sem->activity | ||
| 78 | * to -1 here to indicate we get the lock. Instead, we wake it up | ||
| 79 | * to let it go get it again. | ||
| 80 | */ | ||
| 81 | if (waiter->flags & RWSEM_WAITING_FOR_WRITE) { | ||
| 82 | wake_up_process(waiter->task); | ||
| 83 | goto out; | 78 | goto out; |
| 84 | } | 79 | } |
| 85 | 80 | ||
| 86 | /* grant an infinite number of read locks to the front of the queue */ | 81 | /* grant an infinite number of read locks to the front of the queue */ |
| 87 | dont_wake_writers: | ||
| 88 | woken = 0; | 82 | woken = 0; |
| 89 | while (waiter->flags & RWSEM_WAITING_FOR_READ) { | 83 | do { |
| 90 | struct list_head *next = waiter->list.next; | 84 | struct list_head *next = waiter->list.next; |
| 91 | 85 | ||
| 92 | list_del(&waiter->list); | 86 | list_del(&waiter->list); |
| @@ -96,10 +90,10 @@ __rwsem_do_wake(struct rw_semaphore *sem, int wakewrite) | |||
| 96 | wake_up_process(tsk); | 90 | wake_up_process(tsk); |
| 97 | put_task_struct(tsk); | 91 | put_task_struct(tsk); |
| 98 | woken++; | 92 | woken++; |
| 99 | if (list_empty(&sem->wait_list)) | 93 | if (next == &sem->wait_list) |
| 100 | break; | 94 | break; |
| 101 | waiter = list_entry(next, struct rwsem_waiter, list); | 95 | waiter = list_entry(next, struct rwsem_waiter, list); |
| 102 | } | 96 | } while (waiter->type != RWSEM_WAITING_FOR_WRITE); |
| 103 | 97 | ||
| 104 | sem->activity += woken; | 98 | sem->activity += woken; |
| 105 | 99 | ||
| @@ -144,7 +138,7 @@ void __sched __down_read(struct rw_semaphore *sem) | |||
| 144 | 138 | ||
| 145 | /* set up my own style of waitqueue */ | 139 | /* set up my own style of waitqueue */ |
| 146 | waiter.task = tsk; | 140 | waiter.task = tsk; |
| 147 | waiter.flags = RWSEM_WAITING_FOR_READ; | 141 | waiter.type = RWSEM_WAITING_FOR_READ; |
| 148 | get_task_struct(tsk); | 142 | get_task_struct(tsk); |
| 149 | 143 | ||
| 150 | list_add_tail(&waiter.list, &sem->wait_list); | 144 | list_add_tail(&waiter.list, &sem->wait_list); |
| @@ -201,7 +195,7 @@ void __sched __down_write_nested(struct rw_semaphore *sem, int subclass) | |||
| 201 | /* set up my own style of waitqueue */ | 195 | /* set up my own style of waitqueue */ |
| 202 | tsk = current; | 196 | tsk = current; |
| 203 | waiter.task = tsk; | 197 | waiter.task = tsk; |
| 204 | waiter.flags = RWSEM_WAITING_FOR_WRITE; | 198 | waiter.type = RWSEM_WAITING_FOR_WRITE; |
| 205 | list_add_tail(&waiter.list, &sem->wait_list); | 199 | list_add_tail(&waiter.list, &sem->wait_list); |
| 206 | 200 | ||
| 207 | /* wait for someone to release the lock */ | 201 | /* wait for someone to release the lock */ |
diff --git a/lib/rwsem.c b/lib/rwsem.c index ad5e0df16ab4..cf0ad2ad19f5 100644 --- a/lib/rwsem.c +++ b/lib/rwsem.c | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | * Derived from arch/i386/kernel/semaphore.c | 4 | * Derived from arch/i386/kernel/semaphore.c |
| 5 | * | 5 | * |
| 6 | * Writer lock-stealing by Alex Shi <alex.shi@intel.com> | 6 | * Writer lock-stealing by Alex Shi <alex.shi@intel.com> |
| 7 | * and Michel Lespinasse <walken@google.com> | ||
| 7 | */ | 8 | */ |
| 8 | #include <linux/rwsem.h> | 9 | #include <linux/rwsem.h> |
| 9 | #include <linux/sched.h> | 10 | #include <linux/sched.h> |
| @@ -30,21 +31,22 @@ void __init_rwsem(struct rw_semaphore *sem, const char *name, | |||
| 30 | 31 | ||
| 31 | EXPORT_SYMBOL(__init_rwsem); | 32 | EXPORT_SYMBOL(__init_rwsem); |
| 32 | 33 | ||
| 34 | enum rwsem_waiter_type { | ||
| 35 | RWSEM_WAITING_FOR_WRITE, | ||
| 36 | RWSEM_WAITING_FOR_READ | ||
| 37 | }; | ||
| 38 | |||
| 33 | struct rwsem_waiter { | 39 | struct rwsem_waiter { |
| 34 | struct list_head list; | 40 | struct list_head list; |
| 35 | struct task_struct *task; | 41 | struct task_struct *task; |
| 36 | unsigned int flags; | 42 | enum rwsem_waiter_type type; |
| 37 | #define RWSEM_WAITING_FOR_READ 0x00000001 | ||
| 38 | #define RWSEM_WAITING_FOR_WRITE 0x00000002 | ||
| 39 | }; | 43 | }; |
| 40 | 44 | ||
| 41 | /* Wake types for __rwsem_do_wake(). Note that RWSEM_WAKE_NO_ACTIVE and | 45 | enum rwsem_wake_type { |
| 42 | * RWSEM_WAKE_READ_OWNED imply that the spinlock must have been kept held | 46 | RWSEM_WAKE_ANY, /* Wake whatever's at head of wait list */ |
| 43 | * since the rwsem value was observed. | 47 | RWSEM_WAKE_READERS, /* Wake readers only */ |
| 44 | */ | 48 | RWSEM_WAKE_READ_OWNED /* Waker thread holds the read lock */ |
| 45 | #define RWSEM_WAKE_ANY 0 /* Wake whatever's at head of wait list */ | 49 | }; |
| 46 | #define RWSEM_WAKE_NO_ACTIVE 1 /* rwsem was observed with no active thread */ | ||
| 47 | #define RWSEM_WAKE_READ_OWNED 2 /* rwsem was observed to be read owned */ | ||
| 48 | 50 | ||
| 49 | /* | 51 | /* |
| 50 | * handle the lock release when processes blocked on it that can now run | 52 | * handle the lock release when processes blocked on it that can now run |
| @@ -57,46 +59,43 @@ struct rwsem_waiter { | |||
| 57 | * - writers are only woken if downgrading is false | 59 | * - writers are only woken if downgrading is false |
| 58 | */ | 60 | */ |
| 59 | static struct rw_semaphore * | 61 | static struct rw_semaphore * |
| 60 | __rwsem_do_wake(struct rw_semaphore *sem, int wake_type) | 62 | __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type) |
| 61 | { | 63 | { |
| 62 | struct rwsem_waiter *waiter; | 64 | struct rwsem_waiter *waiter; |
| 63 | struct task_struct *tsk; | 65 | struct task_struct *tsk; |
| 64 | struct list_head *next; | 66 | struct list_head *next; |
| 65 | signed long woken, loop, adjustment; | 67 | long oldcount, woken, loop, adjustment; |
| 66 | 68 | ||
| 67 | waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list); | 69 | waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list); |
| 68 | if (!(waiter->flags & RWSEM_WAITING_FOR_WRITE)) | 70 | if (waiter->type == RWSEM_WAITING_FOR_WRITE) { |
| 69 | goto readers_only; | 71 | if (wake_type == RWSEM_WAKE_ANY) |
| 70 | 72 | /* Wake writer at the front of the queue, but do not | |
| 71 | if (wake_type == RWSEM_WAKE_READ_OWNED) | 73 | * grant it the lock yet as we want other writers |
| 72 | /* Another active reader was observed, so wakeup is not | 74 | * to be able to steal it. Readers, on the other hand, |
| 73 | * likely to succeed. Save the atomic op. | 75 | * will block as they will notice the queued writer. |
| 74 | */ | 76 | */ |
| 77 | wake_up_process(waiter->task); | ||
| 75 | goto out; | 78 | goto out; |
| 79 | } | ||
| 76 | 80 | ||
| 77 | /* Wake up the writing waiter and let the task grab the sem: */ | 81 | /* Writers might steal the lock before we grant it to the next reader. |
| 78 | wake_up_process(waiter->task); | 82 | * We prefer to do the first reader grant before counting readers |
| 79 | goto out; | 83 | * so we can bail out early if a writer stole the lock. |
