futex: Introducing throughput-optimized (TP) futexes
Subsystems (4)
v4 cover futex: Introducing throughput-optimized (TP) futexes
v3->v4:
- Properly handle EFAULT error due to page fault.
- Extend the use cases of TP futexes to userspace rwlock.
- Change the lock handoff trigger from number of spinnings to elapsed time (5ms).
- Various updates to the "perf bench futex mutex" microbenchmark.
- Add a new "perf bench futex rwlock" microbenchmark for measuring rwlock performance.
- Add mutex_owner to futex_state object to hold the serialization mutex owner.
- Streamline a number of helper functions and other miscellenous coding improvements.
- Rebase the patchset to the 4.10 kernel.
v2->v3:
- Use the abbreviation TP for the new futexes instead of TO.
- Make a number of changes accordingly to review comments from ThomasG, PeterZ and MikeG.
- Breaks the main futex patch into smaller pieces to make them easier to review.
v1->v2:
- Adds an explicit lock hand-off mechanism.
- Adds timeout support.
- Simplifies the required userspace code.
- Fixes a number of problems in the v1 code.
This patchset introduces a new futex implementation called throughput-optimized (TP) futexes. It is similar to PI futexes in its calling convention, but provides better throughput than the wait-wake (WW) futexes by encouraging lock stealing and optimistic spinning.
The new TP futexes can be used in implementing both userspace mutexes and rwlocks. They provides better performance while simplifying the userspace locking implementation at the same time. The WW futexes are still needed to implement other synchronization primitives like conditional variables and semaphores that cannot be handled by the TP futexes.
Another advantage of TP futexes is that it has a built-in lock handoff mechanism to prevent lock starvation from happenning as long as the underlying kernel mutex doesn't have lock starvation problem.
Patches 1-6 are preparatory patches that pave the way to implement the TP futexes.
Patch 7 implements the basic TP futex that can support userspace mutexes.
Patch 8 adds robust handling to TP futex to handle the death of TP futex exclusive lock owners.
Patch 9 adds a lock hand-off mechanism that can prevent lock starvation to happen while introducing minimal runtime overhead.
Patch 10 enables the FUTEX_LOCK futex(2) syscall to return status information, such as how the lock is acquired and how many time the task needs to sleep in the spinning loop, that can be used by userspace utilities to monitor how the TP futexes are performing.
Patch 11 enables userspace applications to supply a timeout value to abort the lock acquisition attempt after the specified time period has passed.
Patch 12 adds a new document tp-futex.txt in the Documentation directory to describe the new TP futexes.
Patch 13 adds a microbenchmark to the "perf bench futex" suite to measure the performance of the WW, PI and TP futexes when implementing a userspace mutex.
Patch 14 extends the TP futexes to support userspace rwlocks.
Patch 15 enables more reader lock stealing of TP futexes in the kernel.
Patch 16 groups readers together as a spin group to enhance reader throughput.
Patch 17 updates the tp-futex.txt file to add information about rwlock support.
Patch 18 adds another microbenchmark to the "perf bench futex" suite to measure the performance of the WW, TP futexes and Glibc when implementing a userspace rwlock.
Patch 19 updates the wake_up_q() function to returns the number woken tasks.
Patch 20 enables the dumping of internal futex state information via debugfs.
All the benchmark results shown in the change logs were produced by the microbenchmarks included in this patchset. So everyone can run the microbenchmark to see how the TP futexes perform and behave in their own test systems.
Once this patchset is finalized, an updated manpage patch to document the new TP futexes will be sent out. The next step will then be to make Glibc NTPL use the new TP futexes.
Performance highlight in term of average locking rates (ops/sec) on a 2-socket system are as follows:
WW futex TP futex Glibc
-------- -------- -----
mutex 121,129 152,242 -
rwlock 99,149 162,887 30,362Patches 7 and 16 contain more detailed information about the performance characteristics of the TP futexes when implementing userspace mutex and rwlock respectively when compared with other possible way of doing so via the wait-wake futexes.
Waiman Long (20):
futex: Consolidate duplicated timer setup code
futex: Rename futex_pi_state to futex_state
futex: Add helpers to get & cmpxchg futex value without lock
futex: Consolidate pure pi_state_list add & delete codes to helpers
futex: Add a new futex type field into futex_state
futex: Allow direct attachment of futex_state objects to hash bucket
futex: Introduce throughput-optimized (TP) futexes
TP-futex: Enable robust handling
TP-futex: Implement lock handoff to prevent lock starvation
TP-futex: Return status code on FUTEX_LOCK calls
TP-futex: Add timeout support
TP-futex, doc: Add TP futexes documentation
perf bench: New microbenchmark for userspace mutex performance
TP-futex: Support userspace reader/writer locks
TP-futex: Enable kernel reader lock stealing
TP-futex: Group readers together in wait queue
TP-futex, doc: Update TP futexes document on shared locking perf bench: New microbenchmark for userspace rwlock performance sched, TP-futex: Make wake_up_q() return wakeup count
futex: Dump internal futex state via debugfs
Documentation/00-INDEX | 2 + Documentation/tp-futex.txt | 284 +++++++ include/linux/sched.h | 6 +- include/uapi/linux/futex.h | 32 +- kernel/futex.c | 1411 +++++++++++++++++++++++++++++++--- kernel/sched/core.c | 6 +- tools/perf/bench/Build | 1 + tools/perf/bench/bench.h | 2 + tools/perf/bench/futex-locks.c | 1654 ++++++++++++++++++++++++++++++++++++++++ tools/perf/bench/futex.h | 43 ++ tools/perf/builtin-bench.c | 11 + tools/perf/check-headers.sh | 4 + 12 files changed, 3335 insertions(+), 121 deletions(-) create mode 100644 Documentation/tp-futex.txt create mode 100644 tools/perf/bench/futex-locks.c
Signature · 1 line
1.8.3.1
PATCH v4 1/20 futex: Consolidate duplicated timer setup code
A new futex_setup_timer() helper function is added to consolidate all the hrtimer_sleeper setup code.
--- kernel/futex.c | 67 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 30 deletions(-)
kernel/futex.c +37 -30
@@ -477,6 +477,35 @@ static void drop_futex_key_refs(union futex_key *key) }/** +* futex_setup_timer - set up the sleeping hrtimer. +* @time: ptr to the given timeout value +* @timeout: the hrtimer_sleeper structure to be set up +* @flags: futex flags +* @range_ns: optional range in ns +* +* Return: Initialized hrtimer_sleeper structure or NULL if no timeout +* value given +*/ +static inline struct hrtimer_sleeper * +futex_setup_timer (ktime_t *time,struct hrtimer_sleeper *timeout, +int flags,u64 range_ns) +{ +if (!time) +return NULL ; + +hrtimer_init_on_stack (&timeout->timer, (flags &FLAGS_CLOCKRT ) ? +CLOCK_REALTIME :CLOCK_MONOTONIC , +HRTIMER_MODE_ABS ); +hrtimer_init_sleeper (timeout, current); +if (range_ns) +hrtimer_set_expires_range_ns (&timeout->timer, *time, range_ns); +else +hrtimer_set_expires (&timeout->timer, *time); + +return timeout; +} + +/** * get_futex_key() - Get parameters which are the keys for a futex * @uaddr: virtual address of the futex * @fshared: 0 for a PROCESS_PRIVATE futex, 1 for PROCESS_SHARED
@@ -2402,7 +2431,7 @@ static int futex_wait_setup(u32 __user *uaddr, u32 val, unsigned int flags,static int futex_wait (u32 __user *uaddr,unsigned int flags,u32 val,ktime_t *abs_time,u32 bitset) { -struct hrtimer_sleeper timeout, *to =NULL ; +struct hrtimer_sleeper timeout, *to;struct restart_block *restart;struct futex_hash_bucket *hb;struct futex_q q = futex_q_init;
@@ -2412,17 +2441,8 @@ static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,return -EINVAL ; q.bitset = bitset; -if (abs_time) { - to = &timeout; - -hrtimer_init_on_stack (&to->timer, (flags &FLAGS_CLOCKRT ) ? -CLOCK_REALTIME :CLOCK_MONOTONIC , -HRTIMER_MODE_ABS ); -hrtimer_init_sleeper (to, current); -hrtimer_set_expires_range_ns (&to->timer, *abs_time, - current->timer_slack_ns); - } - + to =futex_setup_timer (abs_time, &timeout, flags, + current->timer_slack_ns); retry:/* * Prepare to wait on uaddr. On success, holds hb lock and increments
@@ -2502,7 +2522,7 @@ static long futex_wait_restart(struct restart_block *restart)static int futex_lock_pi (u32 __user *uaddr,unsigned int flags,ktime_t *time,int trylock) { -struct hrtimer_sleeper timeout, *to =NULL ; +struct hrtimer_sleeper timeout, *to;struct futex_hash_bucket *hb;struct futex_q q = futex_q_init;int res, ret;
@@ -2510,13 +2530,7 @@ static int futex_lock_pi(u32 __user *uaddr, unsigned int flags,if (refill_pi_state_cache ())return -ENOMEM ; -if (time) { - to = &timeout; -hrtimer_init_on_stack (&to->timer,CLOCK_REALTIME , -HRTIMER_MODE_ABS ); -hrtimer_init_sleeper (to, current); -hrtimer_set_expires (&to->timer, *time); - } + to =futex_setup_timer (time, &timeout,FLAGS_CLOCKRT ,0 ); retry: ret =get_futex_key (uaddr, flags &FLAGS_SHARED , &q.key,VERIFY_WRITE );
@@ -2811,7 +2825,7 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, u32 val, ktime_t *abs_time, u32 bitset, u32 __user *uaddr2) { - struct hrtimer_sleeper timeout, *to = NULL; + struct hrtimer_sleeper timeout, *to;struct rt_mutex_waiter rt_waiter;struct rt_mutex *pi_mutex =NULL ;struct futex_hash_bucket *hb;
@@ -2825,15 +2839,8 @@ static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,if (!bitset)return -EINVAL ; -if (abs_time) { - to = &timeout; -hrtimer_init_on_stack (&to->timer, (flags &FLAGS_CLOCKRT ) ? -CLOCK_REALTIME :CLOCK_MONOTONIC , -HRTIMER_MODE_ABS ); -hrtimer_init_sleeper (to, current); -hrtimer_set_expires_range_ns (&to->timer, *abs_time, - current->timer_slack_ns); - } + to =futex_setup_timer (abs_time, &timeout, flags, + current->timer_slack_ns);/* * The waiter is allocated on our stack, manipulated by the requeue
Signature · 1 line
1.8.3.1
PATCH v4 2/20 futex: Rename futex_pi_state to futex_state
In reply to Waiman LongThe futex_pi_state structure will be overloaded in later patches to store state information about non-PI futexes. So the structure name itself is no longer a good description of its purpose. So its name is changed to futex_state, a more generic name.
Some of the functions that process the futex states are also renamed.
--- include/linux/sched.h | 4 +- kernel/futex.c | 107 +++++++++++++++++++++++++------------------------- 2 files changed, 56 insertions(+), 55 deletions(-)
include/linux/sched.h +2 -2
@@ -126,7 +126,7 @@ struct sched_attr {u64 sched_period; }; -struct futex_pi_state ; +struct futex_state ;struct robust_list_head ;struct bio_list ;struct fs_struct ;
@@ -1830,7 +1830,7 @@ struct task_struct {struct compat_robust_list_head __user *compat_robust_list;#endif struct list_head pi_state_list; -struct futex_pi_state *pi_state_cache; +struct futex_state *pi_state_cache;#endif #ifdef CONFIG_PERF_EVENTS struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];
kernel/futex.c +54 -53
271 lines not on this page · raw message
Signature · 1 line
1.8.3.1
PATCH v4 3/20 futex: Add helpers to get & cmpxchg futex value without lock
In reply to Waiman LongTwo new helper functions cmpxchg_futex_value() and get_futex_value() are added to access and change the futex value without the hash bucket lock. As a result, page fault is enabled and the page will be faulted in if not present yet.
--- kernel/futex.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
kernel/futex.c +15 -0
@@ -803,6 +803,21 @@ static int get_futex_value_locked(u32 *dest, u32 __user *from)return ret ? -EFAULT :0 ; } +/* +* The equivalents of the above cmpxchg_futex_value_locked() and +* get_futex_value_locked which are called without the hash bucket lock +* and so can have page fault enabled. +*/ +static inline int cmpxchg_futex_value (u32 *curval,u32 __user *uaddr, +u32 uval,u32 newval) +{ +return futex_atomic_cmpxchg_inatomic (curval, uaddr, uval, newval); +} + +static inline int get_futex_value (u32 *dest,u32 __user *from) +{ +return __get_user (*dest, from); +}/* * PI code:
Signature · 1 line
1.8.3.1
PATCH v4 4/20 futex: Consolidate pure pi_state_list add & delete codes to helpers
In reply to Waiman LongTwo new helper functions (task_pi_list_add & task_pi_list_del) are created to consolidate all the pure pi_state_list addition and insertion codes. The set_owner argument in task_pi_list_add() will be needed in a later patch.
--- kernel/futex.c | 64 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 25 deletions(-)
kernel/futex.c +39 -25
@@ -822,6 +822,39 @@ static inline int get_futex_value(u32 *dest, u32 __user *from)/* * PI code: */ + +/** +* task_pi_list_add - add futex state object to a task's pi_state_list +* @task : task structure +* @state: futex state object +*/ +static inline void task_pi_list_add (struct task_struct *task, +struct futex_state *state) +{ +raw_spin_lock_irq (&task->pi_lock); +WARN_ON (!list_empty (&state->list)); +list_add (&state->list, &task->pi_state_list); + state->owner = task; +raw_spin_unlock_irq (&task->pi_lock); +} + +/** +* task_pi_list_del - delete futex state object from a task's pi_state_list +* @state: futex state object +* @warn : warn if list is empty when set +*/ +static inline void task_pi_list_del (struct futex_state *state,const bool warn) +{ +struct task_struct *task = state->owner; + +raw_spin_lock_irq (&task->pi_lock); +if (warn) +WARN_ON (list_empty (&state->list)); +list_del_init (&state->list); + state->owner =NULL ; +raw_spin_unlock_irq (&task->pi_lock); +} +static int refill_futex_state_cache (void ) {struct futex_state *state;
@@ -874,9 +907,7 @@ static void put_futex_state(struct futex_state *state)* and has cleaned up the futex state already */ if (state->owner) { -raw_spin_lock_irq (&state->owner->pi_lock); -list_del_init (&state->list); -raw_spin_unlock_irq (&state->owner->pi_lock); +task_pi_list_del (state, false);rt_mutex_proxy_unlock (&state->pi_mutex, state->owner); }
@@ -1397,16 +1428,8 @@ static int wake_futex_pi(u32 __user *uaddr, u32 uval, struct futex_q *this,return ret; } -raw_spin_lock (&pi_state->owner->pi_lock); -WARN_ON (list_empty (&pi_state->list)); -list_del_init (&pi_state->list); -raw_spin_unlock (&pi_state->owner->pi_lock); - -raw_spin_lock (&new_owner->pi_lock); -WARN_ON (!list_empty (&pi_state->list)); -list_add (&pi_state->list, &new_owner->pi_state_list); - pi_state->owner = new_owner; -raw_spin_unlock (&new_owner->pi_lock); +task_pi_list_del (pi_state, true); +task_pi_list_add (new_owner, pi_state);raw_spin_unlock_irq (&pi_state->pi_mutex.wait_lock);
@@ -2211,19 +2234,10 @@ static int fixup_pi_state_owner(u32 __user *uaddr, struct futex_q *q,* We fixed up user space. Now we need to fix the pi_state * itself. */ -if (pi_state->owner !=NULL ) { -raw_spin_lock_irq (&pi_state->owner->pi_lock); -WARN_ON (list_empty (&pi_state->list)); -list_del_init (&pi_state->list); -raw_spin_unlock_irq (&pi_state->owner->pi_lock); - } +if (pi_state->owner !=NULL ) +task_pi_list_del (pi_state, true); - pi_state->owner = newowner; - -raw_spin_lock_irq (&newowner->pi_lock); -WARN_ON (!list_empty (&pi_state->list)); -list_add (&pi_state->list, &newowner->pi_state_list); -raw_spin_unlock_irq (&newowner->pi_lock); +task_pi_list_add (newowner, pi_state);return 0 ;/*
Signature · 1 line
1.8.3.1
PATCH v4 5/20 futex: Add a new futex type field into futex_state
In reply to Waiman LongAs the futex_state structure will be overloaded in later patches to be used by non-PI futexes, it is necessary to add a type field to distinguish among different types of futexes.
--- kernel/futex.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)
kernel/futex.c +11 -4
@@ -191,6 +191,10 @@#define FLAGS_CLOCKRT 0x02#define FLAGS_HAS_TIMEOUT 0x04 +enum futex_type { +TYPE_PI =0 , +}; +/* * Futex state object: * - Priority Inheritance state
@@ -210,6 +214,7 @@ struct futex_state {struct task_struct *owner;atomic_t refcount; +enum futex_type type;union futex_key key; };
@@ -903,13 +908,14 @@ static void put_futex_state(struct futex_state *state)return ;/* -* If state->owner is NULL, the owner is most probably dying -* and has cleaned up the futex state already +* If state->owner is NULL and the type is TYPE_PI, the owner is +* most probably dying and has cleaned up the futex state already. */ if (state->owner) {task_pi_list_del (state, false); -rt_mutex_proxy_unlock (&state->pi_mutex, state->owner); +if (state->type ==TYPE_PI ) +rt_mutex_proxy_unlock (&state->pi_mutex, state->owner); }if (current->pi_state_cache)
@@ -1062,7 +1068,7 @@ static int attach_to_pi_state(u32 uval, struct futex_state *pi_state,/* * Userspace might have messed up non-PI and PI futexes [3] */ -if (unlikely (!pi_state)) +if (unlikely (!pi_state || (pi_state->type !=TYPE_PI )))return -EINVAL ;WARN_ON (!atomic_read (&pi_state->refcount));
@@ -1180,6 +1186,7 @@ static int attach_to_pi_owner(u32 uval, union futex_key *key,/* Store the key for possible exit cleanups: */ pi_state->key = *key; + pi_state->type =TYPE_PI ;WARN_ON (!list_empty (&pi_state->list));list_add (&pi_state->list, &p->pi_state_list);
Signature · 1 line
1.8.3.1
PATCH v4 6/20 futex: Allow direct attachment of futex_state objects to hash bucket
In reply to Waiman LongCurrently, the futex state objects can only be located indirectly as
hash bucket => futex_q => futex state
Actually it can be beneficial in some cases to locate the futex state object directly from the hash bucket without the futex_q middleman. Therefore, a new list head to link the futex state objects as well as a new spinlock to manage them are added to the hash bucket.
To limit size increase for UP systems, these new fields are only for SMP machines where the cacheline alignment of the hash bucket leaves it with enough empty space for the new fields.
--- kernel/futex.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
kernel/futex.c +24 -0
@@ -207,6 +207,11 @@ struct futex_state {struct list_head list;/* +* Can link to fs_head in the owning hash bucket. +*/ +struct list_head fs_list; + +/* * The PI object: */ struct rt_mutex pi_mutex;
@@ -262,11 +267,24 @@ struct futex_q {* Hash buckets are shared by all the futex_keys that hash to the same * location. Each key may have multiple futex_q structures, one for each task * waiting on a futex. +* +* Alternatively (in SMP), a key can be associated with a unique futex_state +* object where multiple waiters waiting for that futex can queue up in that +* futex_state object without using the futex_q structure. A separate +* futex_state lock (fs_lock) is used for processing those futex_state objects. */ struct futex_hash_bucket {atomic_t waiters;spinlock_t lock;struct plist_head chain; + +#ifdef CONFIG_SMP +/* +* Fields for managing futex_state object list +*/ +spinlock_t fs_lock; +struct list_head fs_head; +#endif }____cacheline_aligned_in_smp ;/*
@@ -873,6 +891,8 @@ static int refill_futex_state_cache(void)return -ENOMEM ;INIT_LIST_HEAD (&state->list); +INIT_LIST_HEAD (&state->fs_list); +/* pi_mutex gets initialized later */ state->owner =NULL ;atomic_set (&state->refcount,1 );
@@ -3363,6 +3383,10 @@ static int __init futex_init(void)atomic_set (&futex_queues[i].waiters,0 );plist_head_init (&futex_queues[i].chain);spin_lock_init (&futex_queues[i].lock); +#ifdef CONFIG_SMP +INIT_LIST_HEAD (&futex_queues[i].fs_head); +spin_lock_init (&futex_queues[i].fs_lock); +#endif }return 0 ;
Signature · 1 line
1.8.3.1
PATCH v4 7/20 futex: Introduce throughput-optimized (TP) futexes
In reply to Waiman LongA new futex implementation called throughput-optimized (TP) futexes is introduced. The goal of this new futex type is to maximize locking throughput at the expense of fairness and deterministic latency. Its throughput is higher than that of the wait-wake futexes especially on systems with a large number of CPUs and for workloads where the lock owners are unlikely to sleep. The downside of this new futex is the increase in the response time variance.
The new TP futex type is designed to be used for mutex-like exclusive user space locks. It is not as versatile as the wait-wake futexes which can be used for other locking primitives like conditional variables, or read-write locks. So it is not a direct replacement of wait-wake futexes.
A futex locking microbenchmark was written where separate userspace mutexes are implemented using wait-wake (WW), PI and TP futexes respectively. This microbenchmark was intructed to run 10s of locking operations with a load latency of 5 as the critical section on a 2-socket 36-core E5-2699 v3 system (HT off). The system was running on a 4.10 based kernel. the results of the benchmark runs were as follows:
WW futex PI futex TP futex
-------- -------- --------
Total locking ops 43,607,917 303,047 54,812,918
Per-thread avg/sec 121,129 841 152,242
Per-thread min/sec 114,842 841 37,440
Per-thread max/sec 127,201 842 455,677
% Stddev 0.50% 0.00% 9.89%
lock futex calls 10,262,001 303,041 134,989
unlock futex calls 12,956,025 303,042 21The TP futexes had better throughput, but they also have larger variance.
For the TP futexes, the heavy hitters in the perf profile were:
98.18% 7.25% [kernel.vmlinux] [k] futex_lock
87.31% 87.31% [kernel.vmlinux] [k] osq_lock
3.62% 3.62% [kernel.vmlinux] [k] mutex_spin_on_owner
For the WW futexes, the heavy hitters were:
86.38% 86.38% [kernel.vmlinux] [k] queued_spin_lock_slowpath
48.39% 2.65% [kernel.vmlinux] [k] futex_wake
45.13% 3.57% [kernel.vmlinux] [k] futex_wait_setup
By increasing the load latency, the performance discrepancies between WW and TP futexes actually increases as shown in the table below.
Load Latency WW locking ops TP locking ops % change
------------ -------------- -------------- --------
5 43,607,917 54,812,918 +26%
10 32,822,045 45,685,420 +39%
20 23,841,429 39,439,048 +65%
30 18,360,775 31,494,788 +72%
40 15,383,536 27,687,160 +80%
50 13,290,368 23,096,937 +74%
100 8,577,763 14,410,909 +68%1us sleep 176,450 179,660 +2%
--- include/uapi/linux/futex.h | 4 + kernel/futex.c | 559 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 560 insertions(+), 3 deletions(-)
include/uapi/linux/futex.h +4 -0
@@ -20,6 +20,8 @@#define FUTEX_WAKE_BITSET 10#define FUTEX_WAIT_REQUEUE_PI 11#define FUTEX_CMP_REQUEUE_PI 12 +#define FUTEX_LOCK 13 +#define FUTEX_UNLOCK 14#define FUTEX_PRIVATE_FLAG 128#define FUTEX_CLOCK_REALTIME 256
@@ -39,6 +41,8 @@ FUTEX_PRIVATE_FLAG)#define FUTEX_CMP_REQUEUE_PI_PRIVATE (FUTEX_CMP_REQUEUE_PI | \ FUTEX_PRIVATE_FLAG) +#define FUTEX_LOCK_PRIVATE (FUTEX_LOCK | FUTEX_PRIVATE_FLAG) +#define FUTEX_UNLOCK_PRIVATE (FUTEX_UNLOCK | FUTEX_PRIVATE_FLAG)/* * Support for robust futexes: the kernel cleans up held futexes at
kernel/futex.c +556 -3
626 lines not on this page · raw message
Signature · 1 line
1.8.3.1
kbuild test robot
build botRe: [PATCH v4 07/20] futex: Introduce throughput-optimized (TP) futexes
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021 reproduce: make htmldocs
Hi Waiman,
[auto build test WARNING on linus/master]
[also build test WARNING on v4.10-rc1 next-20161224]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021
reproduce: make htmldocs
All warnings (new ones prefixed by >>):
>> kernel/futex.c:3419: warning: No description found for parameter 'uaddr' >> kernel/futex.c:3419: warning: No description found for parameter 'vpid' >> kernel/futex.c:3419: warning: No description found for parameter 'puval' >> kernel/futex.c:3419: warning: No description found for parameter 'waiter'
kernel/futex.c:3456: warning: No description found for parameter 'uaddr' kernel/futex.c:3456: warning: No description found for parameter 'vpid' kernel/futex.c:3456: warning: No description found for parameter 'puval'
vim +/uaddr +3419 kernel/futex.c
3403 * The HB fs_lock should NOT be held while calling this function.
3404 * The flag bits are ignored in the trylock.
3405 *
3406 * If waiter is true
3407 * then
3408 * don't preserve the flag bits;
3409 * else
3410 * preserve the flag bits
3411 * endif
3412 *
3413 * Return: 1 if lock acquired;
3414 * 0 if lock acquisition failed;
3415 * -EFAULT if an error happened.
3416 */
3417 static inline int futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,
3418 const bool waiter)
> 3419 { 3420 u32 uval, flags = 0;
3421
3422 if (unlikely(get_futex_value(puval, uaddr)))
3423 return -EFAULT;
3424
3425 uval = *puval;
3426
3427 if (uval & FUTEX_TID_MASK)
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
PATCH v4 8/20 TP-futex: Enable robust handling
In reply to Waiman LongThe TP futexes don't have code to handle the death of futex owners. There are 2 different cases that need to be considered.
As top waiter gets a reference to the task structure of the futex owner, the task structure will never go away even if the owner dies. When the futex owner died while the top waiter is spinning, the task structure will be marked dead or the pid won't have a matching task structure if the task died before a reference is taken. Alternatively, if robust futex attribute is enabled, the FUTEX_OWNER_DIED bit of the futex word may also be set. In all those cases, what the top waiter need to do is to grab the futex directly. An informational message will be printed to highlight this event.
If the futex owner died while the top waiter is sleeping, we need to make the exit processing code to wake up the top waiter. This is done by chaining the futex state object into the pi_state_list of the futex owner before the top waiter sleeps so that if exit_pi_state_list() is called, the wakeup will happen. The top waiter needs to remove its futex state object from the pi_state_list of the old owner if the ownership changes hand or when the lock is acquired.
--- kernel/futex.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 6 deletions(-)
kernel/futex.c +79 -6
@@ -1004,7 +1004,7 @@ static struct task_struct * futex_find_get_task(pid_t pid) }/* -* This task is holding PI mutexes at exit time => bad. +* This task is holding PI or TP mutexes at exit time => bad. * Kernel cleans up PI-state, but userspace is likely hosed. * (Robust-futex cleanup is separate and might save the day for userspace.) */
@@ -1021,12 +1021,31 @@ void exit_pi_state_list(struct task_struct *curr)* We are a ZOMBIE and nobody can enqueue itself on * pi_state_list anymore, but we have to be careful * versus waiters unqueueing themselves: +* +* For TP futexes, the only purpose of showing up in the +* pi_state_list is for this function to wake up the serialization +* mutex owner (state->mutex_owner). We don't actually need to take +* the HB lock. The futex state and task struct won't go away as long +* as we hold the pi_lock. */ raw_spin_lock_irq (&curr->pi_lock);while (!list_empty (head)) { next = head->next; pi_state = list_entry(next, struct futex_state, list); + +if (pi_state->type ==TYPE_TP ) { +struct task_struct *owner; + + owner =READ_ONCE (pi_state->mutex_owner); +WARN_ON (list_empty (&pi_state->list)); +list_del_init (&pi_state->list); + pi_state->owner =NULL ; +if (owner) +wake_up_process (owner); +continue ; + } + key = pi_state->key; hb =hash_futex (&key);raw_spin_unlock_irq (&curr->pi_lock);
@@ -3183,8 +3202,8 @@ int handle_futex_death(u32 __user *uaddr, struct task_struct *curr, int pi) goto retry;/* -* Wake robust non-PI futexes here. The wakeup of -* PI futexes happens in exit_pi_state(): +* Wake robust wait-wake futexes here. The wakeup of +* PI and TP futexes happens in exit_pi_state(): */ if (!pi && (uval &FUTEX_WAITERS ))futex_wake (uaddr,1 ,1 ,FUTEX_BITSET_MATCH_ANY );
@@ -3325,6 +3344,12 @@ void exit_robust_list(struct task_struct *curr)* Unlike the other futexes, the futex_q structures aren't used. Instead, * they will queue up in the serialization mutex of the futex state container * queued in the hash bucket. +* +* To handle the exceptional case that the futex owner died, the robust +* futexes list mechanism is used to for waking up sleeping top waiter. +* Checks are also made in the futex_spin_on_owner() loop for dead task +* structure or invalid pid. In both cases, the top waiter will take over +* the ownership of the futex. */ /**
@@ -3513,6 +3538,10 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)static int futex_spin_on_owner (u32 __user *uaddr,const u32 vpid,struct futex_state *state) { +#define OWNER_DEAD_MESSAGE \ + "futex: owner pid %d of TP futex 0x%lx was %s.\n" \ + "\tLock is now acquired by pid %d!\n" +int ret;u32 uval;u32 owner_pid =0 ;
@@ -3527,13 +3556,47 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,break ;if ((uval &FUTEX_TID_MASK ) != owner_pid) { -if (owner_task) +if (owner_task) { +/* +* task_pi_list_del() should always be +* done before put_task_struct(). The futex +* state may have been dequeued if the task +* is dead. +*/ +if (state->owner) { +WARN_ON (state->owner != owner_task); +task_pi_list_del (state, true); + }put_task_struct (owner_task); + } owner_pid = uval &FUTEX_TID_MASK ; owner_task =futex_find_get_task (owner_pid); } +if (unlikely (!owner_task || + (owner_task->flags &PF_EXITING ) || + (uval &FUTEX_OWNER_DIED ))) { +/* +* PID invalid or exiting/dead task, we can directly +* grab the lock now. +*/ +u32 curval; +char *owner_state; + + ret =cmpxchg_futex_value_locked (&curval, uaddr, uval, + vpid); +if (unlikely (ret)) +break ; +if (curval != uval) +continue ; + owner_state = (owner_task || (uval &FUTEX_OWNER_DIED )) + ?"dead" :"invalid" ; +pr_info (OWNER_DEAD_MESSAGE , owner_pid, + (long )uaddr, owner_state, vpid); +break ; + } +if (need_resched ()) {__set_current_state (TASK_RUNNING );schedule_preempt_disabled ();
@@ -3552,12 +3615,17 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,/* * If the owner isn't active, we need to go to sleep after -* making sure that the FUTEX_WAITERS bit is set. +* making sure that the FUTEX_WAITERS bit is set. We also +* need to put the futex state into the futex owner's +* pi_state_list to prevent deadlock when the owner dies. */ ret =futex_set_waiters_bit (uaddr, &uval);if (ret)break ; +if (owner_task && !state->owner) +task_pi_list_add (owner_task, state); +/* * Do a trylock after setting the task state to make * sure we won't miss a wakeup.
@@ -3593,8 +3661,13 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid, goto retry; } -if (owner_task) +if (owner_task) { +if (state->owner) +task_pi_list_del (state, false);put_task_struct (owner_task); + }else { +WARN_ON (state->owner); + }/* * Cleanup futex state.
Signature · 1 line
1.8.3.1
PATCH v4 9/20 TP-futex: Implement lock handoff to prevent lock starvation
In reply to Waiman LongThe current TP futexes has no guarantee that the top waiter (serialization mutex owner) can get the lock within a finite time. As a result, lock starvation can happen.
A lock handoff mechanism is added to the TP futexes to prevent lock starvation from happening. The idea is that the top waiter can set a special handoff_pid variable with its own pid. The futex owner will then check this variable at unlock time to see if it should free the futex or change its value to the designated pid to transfer the lock directly to the top waiter.
This change does have the effect of limiting the amount of unfairness and hence may adversely affect performance depending on the workloads.
Currently the handoff mechanism is triggered when the top waiter fails to acquire the lock after about 5ms of spinning and sleeping. So the maximum lock handoff rate is about 200 handoffs per second.
--- kernel/futex.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 6 deletions(-)
kernel/futex.c +53 -6
@@ -63,6 +63,7 @@#include <linux/pid.h> #include <linux/nsproxy.h> #include <linux/ptrace.h> +#include <linux/sched.h> #include <linux/sched/rt.h> #include <linux/hugetlb.h> #include <linux/freezer.h>
@@ -235,6 +236,8 @@ struct futex_state {struct task_struct *mutex_owner;/* For TP futexes only */ atomic_t refcount; +u32 handoff_pid;/* For TP futexes only */ +enum futex_type type;union futex_key key; };
@@ -912,6 +915,7 @@ static int refill_futex_state_cache(void)/* pi_mutex gets initialized later */ state->owner =NULL ; state->mutex_owner =NULL ; + state->handoff_pid =0 ;atomic_set (&state->refcount,1 ); state->key =FUTEX_KEY_INIT ;
@@ -3335,8 +3339,9 @@ void exit_robust_list(struct task_struct *curr)* and unlocking. * * The purpose of this FUTEX_WAITERS bit is to make the unlocker wake up the -* serialization mutex owner. Not having the FUTEX_WAITERS bit set doesn't -* mean there is no waiter in the kernel. +* serialization mutex owner or to hand off the lock directly to the top +* waiter. Not having the FUTEX_WAITERS bit set doesn't mean there is no +* waiter in the kernel. * * Like PI futexes, TP futexes are orthogonal to robust futexes can be * used together.
@@ -3352,6 +3357,16 @@ void exit_robust_list(struct task_struct *curr)* the ownership of the futex. */ +/* +* Timeout value for enabling lock handoff and prevent lock starvation. +* +* Currently, lock handoff will be enabled if the top waiter can't get the +* futex after about 5ms. To reduce time checking overhead, it is only done +* after every 128 spins or right after wakeup. As a result, the actual +* elapsed time will be a bit longer than the specified value. +*/ +#define TP_HANDOFF_TIMEOUT 5000000/* 5ms */ +/** * lookup_futex_state - Looking up the futex state structure. * @hb: hash bucket
@@ -3431,6 +3446,7 @@ static inline int put_futex_state_unlocked(struct futex_state *state)* If waiter is true * then * don't preserve the flag bits; +* check for handoff (futex word == own pid) * else * preserve the flag bits * endif
@@ -3449,6 +3465,9 @@ static inline int futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval, uval = *puval; +if (waiter && (uval &FUTEX_TID_MASK ) == vpid) +return 1 ; +if (uval &FUTEX_TID_MASK )return 0 ;/* Trylock fails */
@@ -3542,10 +3561,12 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,"futex: owner pid %d of TP futex 0x%lx was %s.\n" \"\tLock is now acquired by pid %d!\n" - int ret; + int ret, loopcnt =1 ; +bool handoff_set = false;u32 uval;u32 owner_pid =0 ;struct task_struct *owner_task =NULL ; +u64 handoff_time =sched_clock () +TP_HANDOFF_TIMEOUT ;preempt_disable ();WRITE_ONCE (state->mutex_owner, current);
@@ -3600,6 +3621,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,if (need_resched ()) {__set_current_state (TASK_RUNNING );schedule_preempt_disabled (); + loopcnt =0 ;continue ; }
@@ -3608,6 +3630,23 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,break ; } +/* +* Enable lock handoff if the elapsed time exceed the timeout +* value. We also need to set the FUTEX_WAITERS bit to make +* sure that futex lock holder will initiate the handoff at +* unlock time. +*/ +if (!handoff_set && !(loopcnt++ &0x7f )) { +if (sched_clock () > handoff_time) { +WARN_ON (READ_ONCE (state->handoff_pid)); + ret =futex_set_waiters_bit (uaddr, &uval); +if (ret) +break ; +WRITE_ONCE (state->handoff_pid, vpid); + handoff_set = true; + } + } +if (owner_task->on_cpu) {cpu_relax ();continue ;
@@ -3650,8 +3689,10 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,* lock stealing happen in between setting the FUTEX_WAITERS * and setting state to TASK_INTERRUPTIBLE. */ -if (!(uval &FUTEX_OWNER_DIED ) && (uval &FUTEX_WAITERS )) +if (!(uval &FUTEX_OWNER_DIED ) && (uval &FUTEX_WAITERS )) {schedule_preempt_disabled (); + loopcnt =0 ; + }__set_current_state (TASK_RUNNING ); }
@@ -3673,6 +3714,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,* Cleanup futex state. */ WRITE_ONCE (state->mutex_owner,NULL ); +WRITE_ONCE (state->handoff_pid,0 );preempt_enable ();return ret;
@@ -3787,6 +3829,7 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)static int futex_unlock (u32 __user *uaddr,unsigned int flags) {u32 uval, vpid =task_pid_vnr (current); +u32 newpid =0 ;union futex_key key =FUTEX_KEY_INIT ;struct futex_hash_bucket *hb;struct futex_state *state =NULL ;
@@ -3820,6 +3863,10 @@ static int futex_unlock(u32 __user *uaddr, unsigned int flags) goto out_put_key; } + newpid =READ_ONCE (state->handoff_pid); +if (newpid) +WRITE_ONCE (state->handoff_pid,0 ); + owner =READ_ONCE (state->mutex_owner);if (owner)wake_q_add (&wake_q, owner);
@@ -3827,13 +3874,13 @@ static int futex_unlock(u32 __user *uaddr, unsigned int flags)spin_unlock (&hb->fs_lock);/* -* Unlock the futex. +* Unlock the futex or handoff to the next owner. * The flag bits are not preserved to encourage more lock stealing. */ for (;;) {u32 old = uval; -if (cmpxchg_futex_value (&uval, uaddr, old,0 )) { +if (cmpxchg_futex_value (&uval, uaddr, old, newpid)) { ret = -EFAULT ;break ; }
Signature · 1 line
1.8.3.1
PATCH v4 10/20 TP-futex: Return status code on FUTEX_LOCK calls
In reply to Waiman LongTo better understand how the TP futexes are performing, it is useful to return the internal status on the TP futexes. The FUTEX_LOCK futex(2) syscall will now return a positive status code if no error happens. The status code consists of the following 3 fields:
1) Bits 00-07: code on how the lock is acquired.
2) Bits 08-15: reserved
3) Bits 16-30: how many time the task sleeps in the optimistic spinning loop.
By returning the TP status code, an external monitoring or tracking program can have a macro view of how the TP futexes are performing.
--- kernel/futex.c | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-)
kernel/futex.c +32 -11
@@ -3367,7 +3367,23 @@ void exit_robust_list(struct task_struct *curr)*/ #define TP_HANDOFF_TIMEOUT 5000000/* 5ms */ -/** +/* +* The futex_lock() function returns the internal status of the TP futex. +* The status code consists of the following 3 fields: +* 1) bits 00-07: code on how the lock is acquired +* 0 - steals the lock +* 1 - top waiter (mutex owner) acquires the lock +* 2 - handed off the lock +* 2) bits 08-15: reserved +* 3) bits 15-30: how many times the task has slept or yield to scheduler +* in futex_spin_on_owner(). +*/ +#define TP_LOCK_STOLEN 0 +#define TP_LOCK_ACQUIRED 1 +#define TP_LOCK_HANDOFF 2 +#define TP_STATUS_SLEEP (val, sleep) ((val)|((sleep) << 16)) + +/** * lookup_futex_state - Looking up the futex state structure. * @hb: hash bucket * @key: futex key
@@ -3451,9 +3467,11 @@ static inline int put_futex_state_unlocked(struct futex_state *state)* preserve the flag bits * endif * -* Return: 1 if lock acquired; +* Return: TP_LOCK_ACQUIRED if lock acquired; +* TP_LOCK_HANDOFF if lock was handed off; * 0 if lock acquisition failed; * -EFAULT if an error happened. +* *puval will contain the latest futex value when trylock fails. */ static inline int futex_trylock (u32 __user *uaddr,const u32 vpid,u32 *puval,const bool waiter)
@@ -3466,7 +3484,7 @@ static inline int futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval, uval = *puval;if (waiter && (uval &FUTEX_TID_MASK ) == vpid) -return 1 ; +return TP_LOCK_HANDOFF ;if (uval &FUTEX_TID_MASK )return 0 ;/* Trylock fails */
@@ -3477,7 +3495,7 @@ static inline int futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,if (unlikely (cmpxchg_futex_value (puval, uaddr, uval, vpid|flags)))return -EFAULT ; -return *puval == uval; +return (*puval == uval) ?TP_LOCK_ACQUIRED :0 ; }/**
@@ -3491,7 +3509,8 @@ static inline int futex_trylock(u32 __user *uaddr, const u32 vpid, u32 *puval,* of faulting in the futex word. This function should only be called from * within futex_spin_on_owner(). * -* Return: 1 if lock acquired; +* Return: TP_LOCK_ACQUIRED if lock acquired; +* TP_LOCK_HANDOFF if lock was handed off; * 0 if lock acquisition failed; * -EFAULT if an error happened. */
@@ -3552,7 +3571,7 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)* unless the pid wraps around and the perceived owner is not the real owner. * To guard against this case, we will have to use the robust futex feature. * -* Return: 0 if futex acquired, < 0 if an error happens. +* Return: TP status code if lock acquired, < 0 if an error happens. */ static int futex_spin_on_owner (u32 __user *uaddr,const u32 vpid,struct futex_state *state)
@@ -3562,6 +3581,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,"\tLock is now acquired by pid %d!\n" int ret, loopcnt =1 ; +int nsleep =0 ;bool handoff_set = false;u32 uval;u32 owner_pid =0 ;
@@ -3621,6 +3641,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,if (need_resched ()) {__set_current_state (TASK_RUNNING );schedule_preempt_disabled (); + nsleep++; loopcnt =0 ;continue ; }
@@ -3691,6 +3712,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,*/ if (!(uval &FUTEX_OWNER_DIED ) && (uval &FUTEX_WAITERS )) {schedule_preempt_disabled (); + nsleep++; loopcnt =0 ; }__set_current_state (TASK_RUNNING );
@@ -3717,7 +3739,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,WRITE_ONCE (state->handoff_pid,0 );preempt_enable (); -return ret; +return (ret <0 ) ? ret :TP_STATUS_SLEEP (ret, nsleep); }/*
@@ -3731,8 +3753,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,* This function is not inlined so that it can show up separately in perf * profile for performance analysis purpose. * -* Return: 0 - lock acquired -* < 0 - an error happens +* Return: TP status code if lock acquired, < 0 if an error happens. */ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags) {
@@ -3747,7 +3768,7 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)*/ ret =futex_trylock (uaddr, vpid, &uval, false);if (ret) - goto out; +return (ret <0 ) ? ret :TP_LOCK_STOLEN ;/* * Detect deadlocks.
@@ -3815,7 +3836,7 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)put_futex_key (&key); out: -return (ret <0 ) ? ret :0 ; +return ret; }/*
Signature · 1 line
1.8.3.1
PATCH v4 11/20 TP-futex: Add timeout support
In reply to Waiman LongUnlike other futexes, TP futexes do not accept the specification of a timeout value. To align with the other futexes, timeout support is now added. However, the timeout isn't as precise as the other futex types due to the fact that timer expiration can't be detected when the thread is waiting in the serialization mutex.
--- kernel/futex.c | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-)
kernel/futex.c +42 -8
@@ -3556,9 +3556,10 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)/** * futex_spin_on_owner - Optimistically spin on futex owner -* @uaddr: futex address -* @vpid: PID of current task -* @state: futex state object +* @uaddr: futex address +* @vpid: PID of current task +* @state: futex state object +* @timeout: hrtimer_sleeper structure * * Spin on the futex word while the futex owner is active. Otherwise, set * the FUTEX_WAITERS bit and go to sleep.
@@ -3574,7 +3575,8 @@ static inline int futex_set_waiters_bit(u32 __user *uaddr, u32 *puval)* Return: TP status code if lock acquired, < 0 if an error happens. */ static int futex_spin_on_owner (u32 __user *uaddr,const u32 vpid, -struct futex_state *state) +struct futex_state *state, +struct hrtimer_sleeper *timeout) {#define OWNER_DEAD_MESSAGE \ "futex: owner pid %d of TP futex 0x%lx was %s.\n" \
@@ -3646,6 +3648,12 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,continue ; } + +if (timeout && !timeout->task) { + ret = -ETIMEDOUT ; +break ; + } +if (signal_pending (current)) { ret = -EINTR ;break ;
@@ -3753,10 +3761,18 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,* This function is not inlined so that it can show up separately in perf * profile for performance analysis purpose. * +* The timeout functionality isn't as precise as other other futex types +* as timer expiration will not be detected while waiting in the +* serialization mutex. One possible solution is to modify the expiration +* function to send out a signal as well so as to break the thread out of +* the mutex code if we really want more precise timeout. +* * Return: TP status code if lock acquired, < 0 if an error happens. */ -static noinline int futex_lock(u32 __user *uaddr, unsigned int flags) +static noinline int +futex_lock (u32 __user *uaddr,unsigned int flags,ktime_t *time) { +struct hrtimer_sleeper timeout, *to;struct futex_hash_bucket *hb;union futex_key key =FUTEX_KEY_INIT ;struct futex_state *state;
@@ -3780,6 +3796,8 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)if (refill_futex_state_cache ())return -ENOMEM ; + to =futex_setup_timer (time, &timeout, flags, current->timer_slack_ns); + ret =get_futex_key (uaddr, flags &FLAGS_SHARED , &key,VERIFY_WRITE );if (unlikely (ret)) goto out;
@@ -3805,6 +3823,9 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags) goto out_put_state_key; } +if (to) +hrtimer_start_expires (&to->timer,HRTIMER_MODE_ABS ); +/* * Acquiring the serialization mutex. *
@@ -3816,10 +3837,19 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags) goto out_put_state_key;/* +* Check for timeout. +*/ +if (to && !timeout.task) { + ret = -ETIMEDOUT ; +mutex_unlock (&state->mutex); + goto out_put_state_key; + } + +/* * As the mutex owner, we can now spin on the futex word as well as * the active-ness of the futex owner. */ - ret =futex_spin_on_owner (uaddr, vpid, state); + ret =futex_spin_on_owner (uaddr, vpid, state, to);mutex_unlock (&state->mutex);
@@ -3836,6 +3866,10 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags)put_futex_key (&key); out: +if (to) { +hrtimer_cancel (&to->timer); +destroy_hrtimer_on_stack (&to->timer); + }return ret; }
@@ -3989,7 +4023,7 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,return futex_requeue (uaddr, flags, uaddr2, val, val2, &val3,1 );#ifdef CONFIG_SMP case FUTEX_LOCK : -return futex_lock (uaddr, flags); +return futex_lock (uaddr, flags, timeout);case FUTEX_UNLOCK :return futex_unlock (uaddr, flags);#endif
@@ -4008,7 +4042,7 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout,int cmd = op &FUTEX_CMD_MASK ;if (utime && (cmd ==FUTEX_WAIT || cmd ==FUTEX_LOCK_PI || - cmd ==FUTEX_WAIT_BITSET || + cmd ==FUTEX_WAIT_BITSET || cmd ==FUTEX_LOCK || cmd ==FUTEX_WAIT_REQUEUE_PI )) {if (unlikely (should_fail_futex (!(op &FUTEX_PRIVATE_FLAG ))))return -EFAULT ;
Signature · 1 line
1.8.3.1
PATCH v4 12/20 TP-futex, doc: Add TP futexes documentation
In reply to Waiman LongThis patch adds a new document file on how to use the TP futexes.
--- Documentation/00-INDEX | 2 + Documentation/tp-futex.txt | 161 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 Documentation/tp-futex.txt
Documentation/00-INDEX +2 -0
@@ -416,6 +416,8 @@ this_cpu_ops.txt - List rationale behind and the way to use this_cpu operations. thermal/ - directory with information on managing thermal issues (CPU/temp) +tp-futex.txt + - Documentation on lightweight throughput-optimized futexes. trace/ - directory with info on tracing technologies within linux translations/
Documentation/tp-futex.txt new file +161 -0
161 lines not on this page · raw message
Signature · 1 line
1.8.3.1
PATCH v4 13/20 perf bench: New microbenchmark for userspace mutex performance
In reply to Waiman LongThis microbenchmark simulates how the use of different futex types can affect the actual performanace of userspace mutex locks. The usage is:
perf bench futex mutex <options>
Three sets of simple mutex lock and unlock functions are implemented using the wait-wake, PI and TP futexes respectively. This microbenchmark then runs the locking rate measurement tests using either one of those mutexes or all of them consecutively.
--- tools/perf/bench/Build | 1 + tools/perf/bench/bench.h | 1 + tools/perf/bench/futex-locks.c | 844 +++++++++++++++++++++++++++++++++++++++++ tools/perf/bench/futex.h | 23 ++ tools/perf/builtin-bench.c | 10 + tools/perf/check-headers.sh | 4 + 6 files changed, 883 insertions(+) create mode 100644 tools/perf/bench/futex-locks.c
tools/perf/bench/Build +1 -0
@@ -6,6 +6,7 @@ perf-y += futex-wake.o perf-y += futex-wake-parallel.o perf-y += futex-requeue.o perf-y += futex-lock-pi.o +perf-y += futex-locks.o perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-asm.o perf-$(CONFIG_X86_64) += mem-memset-x86-64-asm.o
tools/perf/bench/bench.h +1 -0
@@ -36,6 +36,7 @@int bench_futex_requeue (int argc,const char **argv,const char *prefix);/* pi futexes */ int bench_futex_lock_pi (int argc,const char **argv,const char *prefix); +int bench_futex_mutex (int argc,const char **argv,const char *prefix);#define BENCH_FORMAT_DEFAULT_STR "default"#define BENCH_FORMAT_DEFAULT 0
tools/perf/bench/futex-locks.c new file +844 -0
844 lines not on this page · raw message
tools/perf/bench/futex.h +23 -0
@@ -87,6 +87,29 @@ val, opflags); } +#ifndef FUTEX_LOCK +#define FUTEX_LOCK 0xff/* Disable the use of TP futexes */ +#define FUTEX_UNLOCK 0xff +#endif /* FUTEX_LOCK */ + +/** +* futex_lock() - lock the TP futex +*/ +static inline int +futex_lock (u_int32_t *uaddr,struct timespec *timeout,int opflags) +{ +return futex (uaddr,FUTEX_LOCK ,0 , timeout,NULL ,0 , opflags); +} + +/** +* futex_unlock() - unlock the TP futex +*/ +static inline int +futex_unlock (u_int32_t *uaddr,int opflags) +{ +return futex (uaddr,FUTEX_UNLOCK ,0 ,NULL ,NULL ,0 , opflags); +} +#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP #include <pthread.h> static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr,
tools/perf/builtin-bench.c +10 -0
@@ -20,6 +20,7 @@#include "builtin.h" #include "bench/bench.h" +#include <locale.h> #include <stdio.h> #include <stdlib.h> #include <string.h>
@@ -62,6 +63,7 @@ struct bench { { "requeue", "Benchmark for futex requeue calls", bench_futex_requeue },/* pi-futexes */ { "lock-pi", "Benchmark for futex lock_pi calls", bench_futex_lock_pi }, + { "mutex", "Benchmark for mutex locks using futexes", bench_futex_mutex }, { "all", "Run all futex benchmarks", NULL }, { NULL, NULL, NULL } };
@@ -215,6 +217,7 @@ int cmd_bench(int argc, const char **argv, const char *prefix __maybe_unused) {struct collection *coll;int ret =0 ; +char *locale;if (argc <2 ) {/* No collection specified. */
@@ -222,6 +225,13 @@ int cmd_bench(int argc, const char **argv, const char *prefix __maybe_unused) goto end; } +/* +* Enable better number formatting. +*/ + locale =setlocale (LC_NUMERIC ,"" ); +if (!strcmp (locale,"C" )) +setlocale (LC_NUMERIC ,"en_US" ); + argc =parse_options (argc, argv, bench_options, bench_usage,PARSE_OPT_STOP_AT_NON_OPTION );
tools/perf/check-headers.sh +4 -0
@@ -57,3 +57,7 @@ check arch/x86/lib/memcpy_64.S -B -I "^EXPORT_SYMBOL" -I "^#include <asm/ check arch/x86/lib/memset_64.S -B -I "^EXPORT_SYMBOL" -I "^#include <asm/export.h>" check include/uapi/asm-generic/mman.h -B -I "^#include <\(uapi/\)*asm-generic/mman-common.h>" check include/uapi/linux/mman.h -B -I "^#include <\(uapi/\)*asm/mman.h>" + +# need to link to the latest futex.h header +[[ -f ../../include/uapi/linux/futex.h ]] && + ln -sf ../../../../../include/uapi/linux/futex.h util/include/linux
Signature · 1 line
1.8.3.1
Arnaldo Carvalho de Melo
maintainerRe: [PATCH v4 13/20] perf bench: New microbenchmark for userspace mutex performance
Em Thu, Dec 29, 2016 at 11:13:39AM -0500, Waiman Long escreveu:
Quoted 5 lines
This microbenchmark simulates how the use of different futex types can affect the actual performanace of userspace mutex locks. The usage is:
perf bench futex mutex <options>
Showing the tool output is preferred, trim it if needed or state why that is not possible.
Quoted 5 lines
Three sets of simple mutex lock and unlock functions are implemented using the wait-wake, PI and TP futexes respectively. This microbenchmark then runs the locking rate measurement tests using either one of those mutexes or all of them consecutively.
Quoted 8 lines
Signed-off-by Waiman Long <[email protected]>--- tools/perf/bench/Build | 1 + tools/perf/bench/bench.h | 1 + tools/perf/bench/futex-locks.c | 844 +++++++++++++++++++++++++++++++++++++++++ tools/perf/bench/futex.h | 23 ++ tools/perf/builtin-bench.c | 10 + tools/perf/check-headers.sh | 4 +
You forgot to document it in tools/perf/Documentation/perf-bench.txt
Also I would suggest you submit this first without supportint TP futexes, i.e. supporting just what is in the kernel already, this way I could cherry-pick this, which would be useful already for benchmarking the existing types of futexes.
Then, after the new futex type is accepted into the kernel, in a separate patch you would add support for it in 'perf bench futex mutex'.
Thanks,
- Arnaldo
Quoted 967 lines
6 files changed, 883 insertions(+) create mode 100644 tools/perf/bench/futex-locks.ctools/perf/bench/Build +1
-0@@ -6,6 +6,7 @@ perf-y += futex-wake.o perf-y += futex-wake-parallel.o perf-y += futex-requeue.o perf-y += futex-lock-pi.o +perf-y += futex-locks.o perf-$(CONFIG_X86_64) += mem-memcpy-x86-64-asm.o perf-$(CONFIG_X86_64) += mem-memset-x86-64-asm.otools/perf/bench/bench.h +1
-0@@ -36,6 +36,7 @@int bench_futex_requeue (int argc,const char **argv,const char *prefix);/* pi futexes */ int bench_futex_lock_pi (int argc,const char **argv,const char *prefix); +int bench_futex_mutex (int argc,const char **argv,const char *prefix);#define BENCH_FORMAT_DEFAULT_STR "default"#define BENCH_FORMAT_DEFAULT 0tools/perf/bench/futex-locks.c new file +844
-0@@ -0,0 +1,844 @@ +/* +* Copyright (C) 2016 Waiman Long <[email protected]> +* +* This microbenchmark simulates how the use of different futex types can +* affect the actual performanace of userspace locking primitives like mutex. +* +* The raw throughput of the futex lock and unlock calls is not a good +* indication of actual throughput of the mutex code as it may not really +* need to call into the kernel. Therefore, 3 sets of simple mutex lock and +* unlock functions are written to implenment a mutex lock using the +* wait-wake, PI and TP futexes respectively. These functions serve as the +* basis for measuring the locking throughput. +*/ + +#include <pthread.h> + +#include <signal.h> +#include <string.h> +#include "../util/stat.h" +#include "../perf-sys.h" +#include <subcmd/parse-options.h> +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <errno.h> +#include "bench.h" +#include "futex.h" + +#include <err.h> +#include <stdlib.h> +#include <sys/time.h> + +#define CACHELINE_SIZE 64 +#define gettid () syscall(SYS_gettid) +#define __cacheline_aligned __attribute__((__aligned__(CACHELINE_SIZE))) + +typedef u32 futex_t ; +typedef void (*lock_fn_t )(futex_t *futex,int tid); +typedef void (*unlock_fn_t )(futex_t *futex,int tid); + +/* +* Statistical count list +*/ +enum { +STAT_OPS ,/* # of exclusive locking operations */ +STAT_LOCKS ,/* # of exclusive lock futex calls */ +STAT_UNLOCKS ,/* # of exclusive unlock futex calls */ +STAT_SLEEPS ,/* # of exclusive lock sleeps */ +STAT_EAGAINS ,/* # of EAGAIN errors */ +STAT_WAKEUPS ,/* # of wakeups (unlock return) */ +STAT_HANDOFFS ,/* # of lock handoff (TP only) */ +STAT_STEALS ,/* # of lock steals (TP only) */ +STAT_LOCKERRS ,/* # of exclusive lock errors */ +STAT_UNLKERRS ,/* # of exclusive unlock errors */ +STAT_NUM /* Total # of statistical count */ +}; + +/* +* Syscall time list +*/ +enum { +TIME_LOCK ,/* Total exclusive lock syscall time */ +TIME_UNLK ,/* Total exclusive unlock syscall time */ +TIME_NUM , +}; + +struct worker { +futex_t *futex; +pthread_t thread; + +/* +* Per-thread operation statistics +*/ +u32 stats[STAT_NUM ]; + +/* +* Lock/unlock times +*/ +u64 times[TIME_NUM ]; +} __cacheline_aligned; + +/* +* Global cache-aligned futex +*/ +static futex_t __cacheline_aligned global_futex; +static futex_t *pfutex = &global_futex; + +static __threadfutex_t thread_id;/* Thread ID */ +static __threadint counter;/* Sleep counter */ + +static struct worker *worker, *worker_alloc; +static unsigned int nsecs =10 ; +static bool verbose, done, fshared, exit_now, timestat; +static unsigned int ncpus, nthreads; +static int flags; +static const char *ftype; +static int loadlat =1 ; +static int locklat =1 ; +static int wratio; +struct timeval start, end, runtime; +static unsigned int worker_start; +static unsigned int threads_starting; +static unsigned int threads_stopping; +static struct stats throughput_stats; +static lock_fn_t mutex_lock_fn; +static unlock_fn_t mutex_unlock_fn; + +/* +* Lock/unlock syscall time macro +*/ +static inline void systime_add (int tid,int item,struct timespec *begin, +struct timespec *_end) +{ + worker[tid].times[item] += (_end->tv_sec - begin->tv_sec)*1000000000 + + _end->tv_nsec - begin->tv_nsec; +} + +static inline double stat_percent (struct worker *w,int top,int bottom) +{ +return (double )w->stats[top] *100 / w->stats[bottom]; +} + +/* +* Inline functions to update the statistical counts +* +* Enable statistics collection may sometimes impact the locking rates +* to be measured. So we can specify the DISABLE_STAT macro to disable +* statistic counts collection for all except the core locking rate counts. +* +* #define DISABLE_STAT +*/ +#ifndef DISABLE_STAT +static inline void stat_add (int tid,int item,int num) +{ + worker[tid].stats[item] += num; +} + +static inline void stat_inc (int tid,int item) +{ +stat_add (tid, item,1 ); +} +#else +static inline void stat_add (int tid__maybe_unused ,int item__maybe_unused , +int num__maybe_unused ) +{ +} + +static inline void stat_inc (int tid__maybe_unused ,int item__maybe_unused ) +{ +} +#endif + +/* +* The latency value within a lock critical section (load) and between locking +* operations is in term of the number of cpu_relax() calls that are being +* issued. +*/ +static const struct option mutex_options[] = { +OPT_INTEGER ('d' ,"locklat" , &locklat,"Specify inter-locking latency (default = 1)" ), +OPT_STRING ('f' ,"ftype" , &ftype,"type" ,"Specify futex type: WW, PI, TP, all (default)" ), +OPT_INTEGER ('L' ,"loadlat" , &loadlat,"Specify load latency (default = 1)" ), +OPT_UINTEGER ('r' ,"runtime" , &nsecs,"Specify runtime (in seconds, default = 10s)" ), +OPT_BOOLEAN ('S' ,"shared" , &fshared,"Use shared futexes instead of private ones" ), +OPT_BOOLEAN ('T' ,"timestat" , ×tat,"Track lock/unlock syscall times" ), +OPT_UINTEGER ('t' ,"threads" , &nthreads,"Specify number of threads, default = # of CPUs" ), +OPT_BOOLEAN ('v' ,"verbose" , &verbose,"Verbose mode: display thread-level details" ), +OPT_INTEGER ('w' ,"wait-ratio" , &wratio,"Specify <n>/1024 of load is 1us sleep, default = 0" ), +OPT_END () +}; + +static const char *const bench_futex_mutex_usage[] = { +"perf bench futex mutex <options>" , +NULL +}; + +/** +* futex_cmpxchg() - atomic compare and exchange +* @uaddr: The address of the futex to be modified +* @oldval: The expected value of the futex +* @newval: The new value to try and assign to the futex +* +* Implement cmpxchg using gcc atomic builtins. +* +* Return: the old futex value. +*/ +static inline futex_t futex_cmpxchg (futex_t *uaddr,futex_t old,futex_t new) +{ +return __sync_val_compare_and_swap (uaddr, old, new); +} + +/** +* futex_xchg() - atomic exchange +* @uaddr: The address of the futex to be modified +* @newval: The new value to assign to the futex +* +* Implement cmpxchg using gcc atomic builtins. +* +* Return: the old futex value. +*/ +static inline futex_t futex_xchg (futex_t *uaddr,futex_t new) +{ +return __sync_lock_test_and_set (uaddr, new); +} + +/** +* atomic_dec_return - atomically decrement & return the new value +* @uaddr: The address of the futex to be decremented +* Return: The new value +*/ +static inline int atomic_dec_return (futex_t *uaddr) +{ +return __sync_sub_and_fetch (uaddr,1 ); +} + +/** +* atomic_inc_return - atomically increment & return the new value +* @uaddr: The address of the futex to be incremented +* Return: The new value +*/ +static inline int atomic_inc_return (futex_t *uaddr) +{ +return __sync_add_and_fetch (uaddr,1 ); +} + +/**********************[ MUTEX lock/unlock functions ]*********************/ + +/* +* Wait-wake futex lock/unlock functions (Glibc implementation) +* futex value: 0 - unlocked +* 1 - locked +* 2 - locked with waiters (contended) +*/ +static void ww_mutex_lock (futex_t *futex,int tid) +{ +struct timespec stime, etime; +futex_t val = *futex; +int ret; + +if (!val) { + val =futex_cmpxchg (futex,0 ,1 ); +if (val ==0 ) +return ; + } + +for (;;) { +if (val !=2 ) { +/* +* Force value to 2 to indicate waiter +*/ + val =futex_xchg (futex,2 ); +if (val ==0 ) +return ; + } +if (timestat) { +clock_gettime (CLOCK_REALTIME , &stime); + ret =futex_wait (futex,2 ,NULL , flags); +clock_gettime (CLOCK_REALTIME , &etime); +systime_add (tid,TIME_LOCK , &stime, &etime); + }else { + ret =futex_wait (futex,2 ,NULL , flags); + } + +stat_inc (tid,STAT_LOCKS ); +if (ret <0 ) { +if (errno ==EAGAIN ) +stat_inc (tid,STAT_EAGAINS ); +else +stat_inc (tid,STAT_LOCKERRS ); + } + + val = *futex; + } +} + +static void ww_mutex_unlock (futex_t *futex,int tid) +{ +struct timespec stime, etime; +futex_t val; +int ret; + + val =futex_xchg (futex,0 ); + +if (val ==2 ) { +stat_inc (tid,STAT_UNLOCKS ); +if (timestat) { +clock_gettime (CLOCK_REALTIME , &stime); + ret =futex_wake (futex,1 , flags); +clock_gettime (CLOCK_REALTIME , &etime); +systime_add (tid,TIME_UNLK , &stime, &etime); + }else { + ret =futex_wake (futex,1 , flags); + } +if (ret <0 ) +stat_inc (tid,STAT_UNLKERRS ); +else +stat_add (tid,STAT_WAKEUPS , ret); + } +} + +/* +* Alternate wait-wake futex lock/unlock functions with thread_id lock word +*/ +static void ww2_mutex_lock (futex_t *futex,int tid) +{ +struct timespec stime, etime; +futex_t val = *futex; +int ret; + +if (!val) { + val =futex_cmpxchg (futex,0 , thread_id); +if (val ==0 ) +return ; + } + +for (;;) { +/* +* Set the FUTEX_WAITERS bit, if not set yet. +*/ +while (!(val &FUTEX_WAITERS )) { +futex_t old; + +if (!val) { + val =futex_cmpxchg (futex,0 , thread_id); +if (val ==0 ) +return ; +continue ; + } + old =futex_cmpxchg (futex, val, val |FUTEX_WAITERS ); +if (old == val) { + val |=FUTEX_WAITERS ; +break ; + } + val = old; + } +if (timestat) { +clock_gettime (CLOCK_REALTIME , &stime); + ret =futex_wait (futex, val,NULL , flags); +clock_gettime (CLOCK_REALTIME , &etime); +systime_add (tid,TIME_LOCK , &stime, &etime); + }else { + ret =futex_wait (futex, val,NULL , flags); + } +stat_inc (tid,STAT_LOCKS ); +if (ret <0 ) { +if (errno ==EAGAIN ) +stat_inc (tid,STAT_EAGAINS ); +else +stat_inc (tid,STAT_LOCKERRS ); + } + + val = *futex; + } +} + +static void ww2_mutex_unlock (futex_t *futex,int tid) +{ +struct timespec stime, etime; +futex_t val; +int ret; + + val =futex_xchg (futex,0 ); + +if ((val &FUTEX_TID_MASK ) != thread_id) +stat_inc (tid,STAT_UNLKERRS ); + +if (val &FUTEX_WAITERS ) { +stat_inc (tid,STAT_UNLOCKS ); +if (timestat) { +clock_gettime (CLOCK_REALTIME , &stime); + ret =futex_wake (futex,1 , flags); +clock_gettime (CLOCK_REALTIME , &etime); +systime_add (tid,TIME_UNLK , &stime, &etime); + }else { + ret =futex_wake (futex,1 , flags); + } +if (ret <0 ) +stat_inc (tid,STAT_UNLKERRS ); +else +stat_add (tid,STAT_WAKEUPS , ret); + } +} + +/* +* PI futex lock/unlock functions +*/ +static void pi_mutex_lock (futex_t *futex,int tid) +{ +struct timespec stime, etime; +futex_t val; +int ret; + + val =futex_cmpxchg (futex,0 , thread_id); +if (val ==0 ) +return ; + +/* +* Retry if an error happens +*/ +for (;;) { +if (timestat) { +clock_gettime (CLOCK_REALTIME , &stime); + ret =futex_lock_pi (futex,NULL , flags); +clock_gettime (CLOCK_REALTIME , &etime); +systime_add (tid,TIME_LOCK , &stime, &etime); + }else { + ret =futex_lock_pi (futex,NULL , flags); + } +stat_inc (tid,STAT_LOCKS ); +if (ret >=0 ) +break ; +stat_inc (tid,STAT_LOCKERRS ); + } +} + +static void pi_mutex_unlock (futex_t *futex,int tid) +{ +struct timespec stime, etime; +futex_t val; +int ret; + + val =futex_cmpxchg (futex, thread_id,0 ); +if (val == thread_id) +return ; + +if (timestat) { +clock_gettime (CLOCK_REALTIME , &stime); + ret =futex_unlock_pi (futex, flags); +clock_gettime (CLOCK_REALTIME , &etime); +systime_add (tid,TIME_UNLK , &stime, &etime); + }else { + ret =futex_unlock_pi (futex, flags); + } +if (ret <0 ) +stat_inc (tid,STAT_UNLKERRS ); +else +stat_add (tid,STAT_WAKEUPS , ret); +stat_inc (tid,STAT_UNLOCKS ); +} + +/* +* TP futex lock/unlock functions +*/ +static void tp_mutex_lock (futex_t *futex,int tid) +{ +struct timespec stime, etime; +futex_t val; +int ret; + + val =futex_cmpxchg (futex,0 , thread_id); +if (val ==0 ) +return ; + +/* +* Retry if an error happens +*/ +for (;;) { +if (timestat) { +clock_gettime (CLOCK_REALTIME , &stime); + ret =futex_lock (futex,NULL , flags); +clock_gettime (CLOCK_REALTIME , &etime); +systime_add (tid,TIME_LOCK , &stime, &etime); + }else { + ret =futex_lock (futex,NULL , flags); + } +stat_inc (tid,STAT_LOCKS ); +if (ret >=0 ) +break ; +stat_inc (tid,STAT_LOCKERRS ); + } +/* +* Get # of sleeps & locking method +*/ +stat_add (tid,STAT_SLEEPS , ret >>16 ); + ret &=0xff ; +if (!ret) +stat_inc (tid,STAT_STEALS ); +else if (ret ==2 ) +stat_inc (tid,STAT_HANDOFFS ); +} + +static void tp_mutex_unlock (futex_t *futex,int tid) +{ +struct timespec stime, etime; +futex_t val; +int ret; + + val =futex_cmpxchg (futex, thread_id,0 ); +if (val == thread_id) +return ; + +if (timestat) { +clock_gettime (CLOCK_REALTIME , &stime); + ret =futex_unlock (futex, flags); +clock_gettime (CLOCK_REALTIME , &etime); +systime_add (tid,TIME_UNLK , &stime, &etime); + }else { + ret =futex_unlock (futex, flags); + } +stat_inc (tid,STAT_UNLOCKS ); +if (ret <0 ) +stat_inc (tid,STAT_UNLKERRS ); +else +stat_add (tid,STAT_WAKEUPS , ret); +} + +/**************************************************************************/ + +/* +* Load function +*/ +static inline void load (int tid) +{ +int n = loadlat; + +/* +* Optionally does a 1us sleep instead if wratio is defined and +* is within bound. +*/ +if (wratio && (((counter++ + tid) &0x3ff ) < wratio)) { +usleep (1 ); +return ; + } + +while (n-- >0 ) +cpu_relax (); +} + +static inline void csdelay (void ) +{ +int n = locklat; + +while (n-- >0 ) +cpu_relax (); +} + +static void toggle_done (int sig__maybe_unused , +siginfo_t *info__maybe_unused , +void *uc__maybe_unused ) +{ +/* inform all threads that we're done for the day */ + done = true; +gettimeofday (&end,NULL ); +timersub (&end, &start, &runtime); +if (sig) + exit_now = true; +} + +static void *mutex_workerfn (void *arg) +{ +long tid = (long )arg; +struct worker *w = &worker[tid]; +lock_fn_t lock_fn = mutex_lock_fn; +unlock_fn_t unlock_fn = mutex_unlock_fn; + + thread_id =gettid (); + counter =0 ; + +atomic_dec_return (&threads_starting); + +/* +* Busy wait until asked to start +*/ +while (!worker_start) +cpu_relax (); + +do { +lock_fn (w->futex, tid); +load (tid); +unlock_fn (w->futex, tid); + w->stats[STAT_OPS ]++;/* One more locking operation */ +csdelay (); + }while (!done); + +if (verbose) +printf ("[thread %3ld (%d)] exited.\n" , tid, thread_id); +atomic_inc_return (&threads_stopping); +return NULL ; +} + +static void create_threads (struct worker *w,pthread_attr_t *thread_attr, +void *(*workerfn)(void *arg),long tid) +{ +cpu_set_t cpu; + +/* +* Bind each thread to a CPU +*/ +CPU_ZERO (&cpu); +CPU_SET (tid % ncpus, &cpu); + w->futex = pfutex; + +if (pthread_attr_setaffinity_np (thread_attr,sizeof (cpu_set_t), &cpu)) +err (EXIT_FAILURE ,"pthread_attr_setaffinity_np" ); + +if (pthread_create (&w->thread, thread_attr, workerfn, (void *)tid)) +err (EXIT_FAILURE ,"pthread_create" ); +} + +static int futex_mutex_type (const char **ptype) +{ +const char *type = *ptype; + +if (!strcasecmp (type,"WW" )) { + *ptype ="WW" ; + mutex_lock_fn = ww_mutex_lock; + mutex_unlock_fn = ww_mutex_unlock; + }else if (!strcasecmp (type,"WW2" )) { + *ptype ="WW2" ; + mutex_lock_fn = ww2_mutex_lock; + mutex_unlock_fn = ww2_mutex_unlock; + }else if (!strcasecmp (type,"PI" )) { + *ptype ="PI" ; + mutex_lock_fn = pi_mutex_lock; + mutex_unlock_fn = pi_mutex_unlock; + }else if (!strcasecmp (type,"TP" )) { + *ptype ="TP" ; + mutex_lock_fn = tp_mutex_lock; + mutex_unlock_fn = tp_mutex_unlock; + +/* +* Check if TP futex is supported. +*/ +futex_unlock (&global_futex,0 ); +if (errno ==ENOSYS ) { +fprintf (stderr, +"\nTP futexes are not supported by the kernel!\n" ); +return -1 ; + } + }else { +return -1 ; + } +return 0 ; +} + +static void futex_test_driver (const char *futex_type, +int (*proc_type)(const char **ptype), +void *(*workerfn)(void *arg)) +{ +u64 us; +int i, j; +struct worker total; +double avg, stddev; +pthread_attr_t thread_attr; + +/* +* There is an extra blank line before the error counts to highlight +* them. +*/ +const char *desc[STAT_NUM ] = { + [STAT_OPS ] ="Total exclusive locking ops" , + [STAT_LOCKS ] ="Exclusive lock futex calls" , + [STAT_UNLOCKS ] ="Exclusive unlock futex calls" , + [STAT_SLEEPS ] ="Exclusive lock sleeps" , + [STAT_WAKEUPS ] ="Process wakeups" , + [STAT_EAGAINS ] ="EAGAIN lock errors" , + [STAT_HANDOFFS ] ="Lock handoffs" , + [STAT_STEALS ] ="Lock stealings" , + [STAT_LOCKERRS ] ="\nExclusive lock errors" , + [STAT_UNLKERRS ] ="\nExclusive unlock errors" , + }; + +if (exit_now) +return ; + +if (proc_type (&futex_type) <0 ) { +fprintf (stderr,"Unknown futex type '%s'!\n" , futex_type); +exit (1 ); + } + +printf ("\n=====================================\n" ); +printf ("[PID %d]: %d threads doing %s futex lockings (load=%d) for %d secs.\n\n" , +getpid (), nthreads, futex_type, loadlat, nsecs); + +init_stats (&throughput_stats); + + *pfutex =0 ; + done = false; + threads_starting = nthreads; +pthread_attr_init (&thread_attr); + +for (i =0 ; i < (int )nthreads; i++) +create_threads (&worker[i], &thread_attr, workerfn, i); + +while (threads_starting) +usleep (1 ); + +gettimeofday (&start,NULL ); + +/* +* Start the test +* +* Unlike the other futex benchmarks, this one uses busy waiting +* instead of pthread APIs to make sure that all the threads (except +* the one that shares CPU with the parent) will start more or less +* simultaineously. +*/ +atomic_inc_return (&worker_start); +sleep (nsecs); +toggle_done (0 ,NULL ,NULL ); + +/* +* In verbose mode, we check if all the threads have been stopped +* after 1ms and report the status if some are still running. +*/ +if (verbose) { +usleep (1000 ); +if (threads_stopping != nthreads) { +printf ("%d threads still running 1ms after timeout" +" - futex = 0x%x\n" , + nthreads - threads_stopping, *pfutex); +/* +* If the threads are still running after 10s, +* go directly to statistics printing and exit. +*/ +for (i =10 ; i >0 ; i--) { +sleep (1 ); +if (threads_stopping == nthreads) +break ; + } +if (!i) { +printf ("*** Threads waiting ABORTED!! ***\n\n" ); + goto print_stat; + } + } + } + +for (i =0 ; i < (int )nthreads; i++) { +int ret =pthread_join (worker[i].thread,NULL ); + +if (ret) +err (EXIT_FAILURE ,"pthread_join" ); + } + +print_stat: +pthread_attr_destroy (&thread_attr); + + us = runtime.tv_sec *1000000 + runtime.tv_usec; +memset (&total,0 ,sizeof (total)); +for (i =0 ; i < (int )nthreads; i++) { +/* +* Get a rounded estimate of the # of locking ops/sec. +*/ +u64 tp = (u64 )worker[i].stats[STAT_OPS ] *1000000 / us; + +for (j =0 ; j <STAT_NUM ; j++) + total.stats[j] += worker[i].stats[j]; + +update_stats (&throughput_stats, tp); +if (verbose) +printf ("[thread %3d] futex: %p [ %'ld ops/sec ]\n" , + i, worker[i].futex, (long )tp); + } + + avg =avg_stats (&throughput_stats); + stddev =stddev_stats (&throughput_stats); + +printf ("Locking statistics:\n" ); +printf ("%-28s = %'.2fs\n" ,"Test run time" , (double )us/1000000 ); +for (i =0 ; i <STAT_NUM ; i++) +if (total.stats[i]) +printf ("%-28s = %'d\n" , desc[i], total.stats[i]); + +if (timestat && total.times[TIME_LOCK ]) { +printf ("\nSyscall times:\n" ); +if (total.stats[STAT_LOCKS ]) +printf ("Avg exclusive lock syscall = %'ldns\n" , + total.times[TIME_LOCK ]/total.stats[STAT_LOCKS ]); +if (total.stats[STAT_UNLOCKS ]) +printf ("Avg exclusive unlock syscall = %'ldns\n" , + total.times[TIME_UNLK ]/total.stats[STAT_UNLOCKS ]); + } + +printf ("\nPercentages:\n" ); +if (total.stats[STAT_LOCKS ]) +printf ("Exclusive lock futex calls = %.1f%%\n" , +stat_percent (&total,STAT_LOCKS ,STAT_OPS )); +if (total.stats[STAT_UNLOCKS ]) +printf ("Exclusive unlock futex calls = %.1f%%\n" , +stat_percent (&total,STAT_UNLOCKS ,STAT_OPS )); +if (total.stats[STAT_EAGAINS ]) +printf ("EAGAIN lock errors = %.1f%%\n" , +stat_percent (&total,STAT_EAGAINS ,STAT_LOCKS )); +if (total.stats[STAT_WAKEUPS ]) +printf ("Process wakeups = %.1f%%\n" , +stat_percent (&total,STAT_WAKEUPS ,STAT_UNLOCKS )); + +printf ("\nPer-thread Locking Rates:\n" ); +printf ("Avg = %'d ops/sec (+- %.2f%%)\n" , (int )(avg +0.5 ), +rel_stddev_stats (stddev, avg)); +printf ("Min = %'d ops/sec\n" , (int )throughput_stats.min); +printf ("Max = %'d ops/sec\n" , (int )throughput_stats.max); + +if (*pfutex !=0 ) +printf ("\nResidual futex value = 0x%x\n" , *pfutex); + +/* Clear the workers area */ +memset (worker,0 ,sizeof (*worker) * nthreads); +} + +int bench_futex_mutex (int argc,const char **argv, +const char *prefix__maybe_unused ) +{ +struct sigaction act; + + argc =parse_options (argc, argv, mutex_options, + bench_futex_mutex_usage,0 ); +if (argc) + goto err; + + ncpus =sysconf (_SC_NPROCESSORS_ONLN); + +sigfillset (&act.sa_mask); + act.sa_sigaction = toggle_done; +sigaction (SIGINT , &act,NULL ); + +if (!nthreads) + nthreads = ncpus; + +/* +* Since the allocated memory buffer may not be properly cacheline +* aligned, we need to allocate one more than needed and manually +* adjust array boundary to be cacheline aligned. +*/ + worker_alloc =calloc (nthreads +1 ,sizeof (*worker)); +if (!worker_alloc) +err (EXIT_FAILURE ,"calloc" ); + worker = (void *)((unsigned long )&worker_alloc[1 ] & + ~(CACHELINE_SIZE -1 )); + +if (!fshared) + flags =FUTEX_PRIVATE_FLAG ; + +if (!ftype || !strcmp (ftype,"all" )) { +futex_test_driver ("WW" , futex_mutex_type, mutex_workerfn); +futex_test_driver ("PI" , futex_mutex_type, mutex_workerfn); +futex_test_driver ("TP" , futex_mutex_type, mutex_workerfn); + }else { +futex_test_driver (ftype, futex_mutex_type, mutex_workerfn); + } +free (worker_alloc); +return 0 ; +err: +usage_with_options (bench_futex_mutex_usage, mutex_options); +exit (EXIT_FAILURE ); +}tools/perf/bench/futex.h +23
-0@@ -87,6 +87,29 @@ val, opflags); } +#ifndef FUTEX_LOCK +#define FUTEX_LOCK 0xff/* Disable the use of TP futexes */ +#define FUTEX_UNLOCK 0xff +#endif /* FUTEX_LOCK */ + +/** +* futex_lock() - lock the TP futex +*/ +static inline int +futex_lock (u_int32_t *uaddr,struct timespec *timeout,int opflags) +{ +return futex (uaddr,FUTEX_LOCK ,0 , timeout,NULL ,0 , opflags); +} + +/** +* futex_unlock() - unlock the TP futex +*/ +static inline int +futex_unlock (u_int32_t *uaddr,int opflags) +{ +return futex (uaddr,FUTEX_UNLOCK ,0 ,NULL ,NULL ,0 , opflags); +} +#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP #include <pthread.h> static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr,tools/perf/builtin-bench.c +10
-0@@ -20,6 +20,7 @@#include "builtin.h" #include "bench/bench.h" +#include <locale.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -62,6 +63,7 @@ struct bench { { "requeue", "Benchmark for futex requeue calls", bench_futex_requeue },/* pi-futexes */ { "lock-pi", "Benchmark for futex lock_pi calls", bench_futex_lock_pi }, + { "mutex", "Benchmark for mutex locks using futexes", bench_futex_mutex }, { "all", "Run all futex benchmarks", NULL }, { NULL, NULL, NULL } };@@ -215,6 +217,7 @@ int cmd_bench(int argc, const char **argv, const char *prefix __maybe_unused) {struct collection *coll;int ret =0 ; +char *locale;if (argc <2 ) {/* No collection specified. */ @@ -222,6 +225,13 @@ int cmd_bench(int argc, const char **argv, const char *prefix __maybe_unused) goto end; } +/* +* Enable better number formatting. +*/ + locale =setlocale (LC_NUMERIC ,"" ); +if (!strcmp (locale,"C" )) +setlocale (LC_NUMERIC ,"en_US" ); + argc =parse_options (argc, argv, bench_options, bench_usage,PARSE_OPT_STOP_AT_NON_OPTION );tools/perf/check-headers.sh +4
-0@@ -57,3 +57,7 @@ check arch/x86/lib/memcpy_64.S -B -I "^EXPORT_SYMBOL" -I "^#include <asm/ check arch/x86/lib/memset_64.S -B -I "^EXPORT_SYMBOL" -I "^#include <asm/export.h>" check include/uapi/asm-generic/mman.h -B -I "^#include <\(uapi/\)*asm-generic/mman-common.h>" check include/uapi/linux/mman.h -B -I "^#include <\(uapi/\)*asm/mman.h>" + +# need to link to the latest futex.h header +[[ -f ../../include/uapi/linux/futex.h ]] && + ln -sf ../../../../../include/uapi/linux/futex.h util/include/linuxSignature · 1 line
1.8.3.1
Re: [PATCH v4 13/20] perf bench: New microbenchmark for userspace mutex performance
Quoted 8 lines · Arnaldo Carvalho de Melo
Em Thu, Dec 29, 2016 at 11:13:39AM -0500, Waiman Long escreveu:
Quoted 5 lines
This microbenchmark simulates how the use of different futex types can affect the actual performanace of userspace mutex locks. The usage is:
perf bench futex mutex <options>
Showing the tool output is preferred, trim it if needed or state why that is not possible.
I will do so when I update the patchset.
Quoted 14 lines
Quoted 6 lines
Three sets of simple mutex lock and unlock functions are implemented using the wait-wake, PI and TP futexes respectively. This microbenchmark then runs the locking rate measurement tests using either one of those mutexes or all of them consecutively.
Quoted 8 lines
Signed-off-by Waiman Long <[email protected]>--- tools/perf/bench/Build | 1 + tools/perf/bench/bench.h | 1 + tools/perf/bench/futex-locks.c | 844 +++++++++++++++++++++++++++++++++++++++++ tools/perf/bench/futex.h | 23 ++ tools/perf/builtin-bench.c | 10 + tools/perf/check-headers.sh | 4 +
Quoted 1 line
You forgot to document it in
tools/perf/Documentation/perf-bench.txt
Yes, I forgot about that. I will update the document in the next v5 patchset.
Quoted 7 lines
Also I would suggest you submit this first without supportint TP futexes, i.e. supporting just what is in the kernel already, this way I could cherry-pick this, which would be useful already for benchmarking the existing types of futexes.
Then, after the new futex type is accepted into the kernel, in a separate patch you would add support for it in 'perf bench futex mutex'.
I can separate out the TP portion of the changes and put them later in the patchset.
Thanks for the comments.
Cheers,
Longman
PATCH v4 14/20 TP-futex: Support userspace reader/writer locks
In reply to Waiman LongThe TP futexes was designed to support userspace mutually exclusive locks. They are now extended to support userspace reader/writer locks as well.
Two new futex command codes are added:
1) FUTEX_LOCK_SHARED - to acquire a shared lock (reader-lock)
2) FUTEX_UNLOCK_SHARED - to release a shred lock (reader-unlock)
The original FUTEX_LOCK and FUTEX_UNLOCK command codes acts like a writer lock and unlock method in a userspace rwlock.
A special bit FUTEX_SHARED (bit 29) is used to indicate if a lock is an exclusive (writer) or shared (reader) lock. Because of that, the maximum TID value is now reduce to 1^29 instead of 1^30. With a shared lock, the lower 24 bits are used to track the reader counts. Bit 28 is a special FUTEX_SHARED_UNLOCK bit that should used by userspace to prevent racing in the FUTEX_UNLOCK_SHARED futex(2) syscall. Bits 24-27 are reserved for future extension.
When a TP futex becomes reader-owned, there is no way to tell if the readers are running or not. So a fixed time interval (50us) is now used for spinning on a reader-owned futex. The top waiter will abort the spinning and goes to sleep when no activity is observed in the futex value itself after the spinning timer expired. Activities here means any change in the reader count of the futex indicating that some new readers are coming in or old readers are leaving.
Sample code to acquire/release a reader lock in userspace are:
void read_lock(int *faddr)
{
int val = cmpxchg(faddr, 0, FUTEX_SHARED + 1); if (!val)
return;
for (;;) {
int old = val, new;
if (!old)
new = FUTEX_SHARED + 1;
else if ((old & (~FUTEX_TID_SHARED_MASK|
FUTEX_SHARED_UNLOCK) ||
!(old & FUTEX_SHARED))
break;
else
new = old + 1;
val = cmpxchg(faddr, old, new);
if (val == old)
return;
}
while (futex(faddr, FUTEX_LOCK_SHARED, ...) < 0)
;}
void read_unlock(int *faddr)
{
int val = xadd(faddr, -1);
int old;
for (;;) {
if ((val & (FUTEX_SCNT_MASK|FUTEX_SHARED_UNLOCK) ||
!(val & FUTEX_SHARED))
return; /* Not the last reader or shared */
if (val & ~FUTEX_SHARED_TID_MASK) {
old = cmpxchg(faddr, val, val|FUTEX_SHARED_UNLOCK);
if (old == val)
break;
} else {
old = cmpxchg(faddr, val, 0);
if (old == val)
return;
}
val = old;
}
futex(faddr, FUTEX_UNLOCK_SHARED, ...);}
The read lock/unlock code is a bit more complicated than the corresponding write lock/unlock code. This is by design to minimize any additional overhead for mutex lock and unlock. As a result, the TP futex rwlock prefers writers a bit more than readers.
--- include/uapi/linux/futex.h | 28 +++++- kernel/futex.c | 227 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 219 insertions(+), 36 deletions(-)
include/uapi/linux/futex.h +26 -2
@@ -22,6 +22,8 @@#define FUTEX_CMP_REQUEUE_PI 12#define FUTEX_LOCK 13#define FUTEX_UNLOCK 14 +#define FUTEX_LOCK_SHARED 15 +#define FUTEX_UNLOCK_SHARED 16#define FUTEX_PRIVATE_FLAG 128#define FUTEX_CLOCK_REALTIME 256
@@ -43,6 +45,9 @@ FUTEX_PRIVATE_FLAG)#define FUTEX_LOCK_PRIVATE (FUTEX_LOCK | FUTEX_PRIVATE_FLAG)#define FUTEX_UNLOCK_PRIVATE (FUTEX_UNLOCK | FUTEX_PRIVATE_FLAG) +#define FUTEX_LOCK_SHARED_PRIVATE (FUTEX_LOCK_SHARED | FUTEX_PRIVATE_FLAG) +#define FUTEX_UNLOCK_SHARED_PRIVATE (FUTEX_UNLOCK_SHARED | \ + FUTEX_PRIVATE_FLAG)/* * Support for robust futexes: the kernel cleans up held futexes at
@@ -111,9 +116,28 @@ struct robust_list_head {#define FUTEX_OWNER_DIED 0x40000000/* -* The rest of the robust-futex field is for the TID: +* Shared locking bit (for readers). +* Robust futexes cannot be used in combination with shared locking. +* +* The FUTEX_SHARED_UNLOCK bit can be set by userspace to indicate that +* an shared unlock is in progress and so no shared locking is allowed +* within the kernel. +*/ +#define FUTEX_SHARED 0x20000000 +#define FUTEX_SHARED_UNLOCK 0x10000000 + +/* +* The rest of the bits is for the TID or, in the case of readers, the +* shared locking bit and the numbers of active readers present. IOW, the +* max supported TID is actually 0x1fffffff. Only 24 bits are used for +* tracking the number of shared futex owners. Bits 24-27 may be used by +* the userspace for internal management purpose. However, a successful +* FUTEX_UNLOCK_SHARED futex(2) syscall will clear or alter those bits. */ -#define FUTEX_TID_MASK 0x3fffffff +#define FUTEX_TID_MASK 0x1fffffff +#define FUTEX_SCNT_MASK 0x00ffffff/* Shared futex owner count */ +#define FUTEX_SHARED_TID_MASK 0x3fffffff/* FUTEX_SHARED bit + TID */ +#define FUTEX_SHARED_SCNT_MASK 0x20ffffff/* FUTEX_SHARED bit + count */ /* * This limit protects against a deliberately circular list.
kernel/futex.c +193 -34
413 lines not on this page · raw message
Signature · 1 line
1.8.3.1
kbuild test robot
build botRe: [PATCH v4 14/20] TP-futex: Support userspace reader/writer locks
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021 config: i386-randconfig-x006-201652 (attached as .config) compiler: gcc-6…
Hi Waiman,
[auto build test WARNING on linus/master]
[also build test WARNING on v4.10-rc1 next-20161224]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021
config: i386-randconfig-x006-201652 (attached as .config)
compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901 reproduce:
# save the attached .config to linux build tree
make ARCH=i386 Note: it may well be a FALSE warning. FWIW you are at least aware of it now.
http://gcc.gnu.org/wiki/Better_Uninitialized_Warnings
All warnings (new ones prefixed by >>):
kernel/futex.c: In function 'futex_lock':
>> kernel/futex.c:3776:13: warning: 'rspin_timeout' may be used uninitialized in this function [-Wmaybe-uninitialized]
else if (curtime > rspin_timeout)
^
kernel/futex.c:3656:6: note: 'rspin_timeout' was declared here
u64 rspin_timeout;
^~~~~~~~~~~~~vim +/rspin_timeout +3776 kernel/futex.c
3760
3761 if (!owner_task && (reader_value >= 0)) {
3762 int old_count = reader_value;
3763
3764 /*
3765 * Reset timeout value if the old reader
3766 * count is 0 or the reader value changes and
3767 * handoff time hasn't been reached. Otherwise,
3768 * disable reader spinning if the handoff time
3769 * is reached.
3770 */
3771 reader_value = (uval & FUTEX_TID_MASK);
3772 if (!old_count || (!handoff_set
3773 && (reader_value != old_count)))
3774 rspin_timeout = curtime +
3775 TP_RSPIN_TIMEOUT;
> 3776 else if (curtime > rspin_timeout)
3777 reader_value = -1;
3778 }
3779 if (!handoff_set && (curtime > handoff_time)) {
3780 WARN_ON(READ_ONCE(state->handoff_pid));
3781 WRITE_ONCE(state->handoff_pid, vpid);
3782
3783 /*
3784 * Disable handoff check.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
PATCH v4 15/20 TP-futex: Enable kernel reader lock stealing
In reply to Waiman LongThe TP-futexes prefer writers as the code path is much simpler for them. This may not be good for overall system performance especially if there is a fair amount of reader activities.
This patch enables kernel reader to steal the lock when the futex is currently reader-owned and the lock handoff mechanism hasn't been enabled yet. A new field locksteal_disabled is added to the futex state object for controlling reader lock stealing. So the waiting reader must retrieve the futex state object first before doing it.
--- kernel/futex.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-)
kernel/futex.c +21 -2
@@ -237,6 +237,7 @@ struct futex_state {atomic_t refcount;u32 handoff_pid;/* For TP futexes only */ +int locksteal_disabled;/* For TP futexes only */ enum futex_type type;union futex_key key;
@@ -3436,6 +3437,7 @@ void exit_robust_list(struct task_struct *curr) state =alloc_futex_state (); state->type =TYPE_TP ; state->key = *key; + state->locksteal_disabled = false;list_add (&state->fs_list, &hb->fs_head);WARN_ON (atomic_read (&state->refcount) !=1 );
@@ -3781,9 +3783,10 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,WRITE_ONCE (state->handoff_pid, vpid);/* -* Disable handoff check. +* Disable handoff check and reader lock +* stealing. */ - handoff_set = true; + state->locksteal_disabled = handoff_set = true; } }
@@ -3886,6 +3889,7 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,*/ WRITE_ONCE (state->mutex_owner,NULL );WRITE_ONCE (state->handoff_pid,0 ); + state->locksteal_disabled = false;preempt_enable ();return (ret <0 ) ? ret :TP_STATUS_SLEEP (ret, nsleep);
@@ -3966,6 +3970,21 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags, goto out_put_state_key; } +/* +* For reader, we will try to steal the lock here as if it is the +* top waiter without taking the serialization mutex if reader +* lock stealing isn't disabled even though the FUTEX_WAITERS bit +* is set. +*/ +if (shared && !state->locksteal_disabled) { + ret =futex_trylock (uaddr, vpid, &uval, true); +if (ret) { +if (ret >0 ) + ret =TP_LOCK_STOLEN ; + goto out_put_state_key; + } + } +if (to)hrtimer_start_expires (&to->timer,HRTIMER_MODE_ABS );
Signature · 1 line
1.8.3.1
PATCH v4 16/20 TP-futex: Group readers together in wait queue
In reply to Waiman LongAll the TP futex lock waiters are serialized in the kernel using a kernel mutex which acts like a wait queue. The order at which the waiters popped out from the wait queue will affect performance when exclusive (writer) and shared (reader) lock waiters are mixed in the queue. The worst case scenarios will be something like RWRWRW... in term of ordering where no parallelism in term of lock ownership can happen.
To improve throughput, the readers are now grouped together as a single entity in the wait queue. The first reader that enters the mutex wait queue will become the leader of the group. The other readers will spin on the group leader via an OSQ lock. When the futex is put in the shared mode, either by the group leader or by an external reader the spinning readers in the reader group will then acquire the read lock successively.
The spinning readers in the group will get disbanded when the group leader goes to sleep. In this case, all those readers will go into the mutex wait queue alone and wait for their turn to acquire the TP futex.
On a 2-socket 36-core E5-2699 v3 system (HT off) running on a 4.10 based kernel, the performance of TP rwlock with 1:1 reader/writer ratio versus one based on the wait-wake futexes as well as the Glibc rwlock with a microbenchmark (1 worker thread per cpu core) running for 10s were as follows:
WW futex TP futex Glibc
-------- -------- -----
Total locking ops 35,707,234 58,645,434 10,930,422
Per-thread avg/sec 99,149 162,887 30,362
Per-thread min/sec 93,190 38,641 29,872
Per-thread max/sec 104,213 225,983 30,708
Write lock futex calls 11,161,534 14,094 -
Write unlock futex calls 8,696,121 167 -
Read lock futex calls 1,717,863 6,659 -
Read unlock futex calls 4,316,418 323 -It can be seen that the throughput of the TP futex is close to 2X the WW futex and almost 6X the Glibc version in this particular case.
The following table shows the CPU cores scaling for the average per-thread locking rates (ops/sec):
WW futex TP futex Glibc
-------- -------- -----
9 threads (1 socket) 422,014 647,006 190,236
18 threads (2 sockets) 197,145 330,353 66,934
27 threads (2 sockets) 127,947 213,417 43,641
36 threads (2 sockets) 99,149 162,887 30,362The following tables shows the average per-thread locking rates (36 threads) with different reader percentages:
WW futex TP futex Glibc
-------- -------- -----
90% readers 124,426 159,657 60,208
95% readers 148,905 152,315 68,666
100% reader 210,029 191,759 84,316The rwlocks based on the WW futexes and Glibc prefer readers by default. So it can be seen that the performance of the WW futex and Glibc rwlocks increased with higher reader percentages. The TP futex rwlock, however, prefers writers a bit more than readers. So the performance didn't increase as the reader percentage rises.
The WW futexes and Glibc rwlocks also have a writer-preferring version. Their performance with the same tests are as follows:
WW futex TP futex Glibc
-------- -------- -----
90% readers 87,866 159,657 26,057
95% readers 93,193 152,315 32,611
100% reader 193,267 191,759 88,440With separate 18 reader and 18 writer threads, the the average per-thread reader and writer locking rates with different load latencies (L, default = 1) and reader-preferring rwlocks are:
WW futex TP futex Glibc
-------- -------- ----- Reader rate, L=1 381,411 74,059 164,184
Writer rate, L=1 0 240,841 0
Reader rate, L=5 330,732 57,361 150,691
Writer rate, L=5 0 175,400 0
Reader rate, L=50 304,505 17,355 97,066
Writer rate, L=50 0 114,504 0
The corresponding locking rates with writer-preferring rwlocks are:
WW futex TP futex Glibc
-------- -------- ----- Reader rate, L=1 138,805 74,059 54
Writer rate, L=1 31,113 240,841 56,424
Reader rate, L=5 114,414 57,361 24
Writer rate, L=5 28,062 175,400 52,039
Reader rate, L=50 88,619 51,483 5 Writer rate, L=50 21,005 98,005 49,885
With both the WW futex and Glibc rwlocks, lock starvation happened for the writers with reader-preferring rwlocks. For writer preferring rwlocks, the WW futex one fared better. The Glibc one, however, was close to starving the readers.
The TP futex prefers writer in general, but the actual preference depends on the timing. Lock starvation should not happen on the TP futexes as long as the underlying kernel mutex is lock starvation free which is the case for 4.10 and later kernel.
--- kernel/futex.c | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 3 deletions(-)
kernel/futex.c +132 -3
@@ -239,6 +239,16 @@ struct futex_state {u32 handoff_pid;/* For TP futexes only */ int locksteal_disabled;/* For TP futexes only */ +/* +* To improve reader throughput in TP futexes, all the readers +* in the mutex queue are grouped together. The first reader in the +* queue will set first_reader, then the rest of the readers will +* spin on the first reader via the OSQ without actually entering +* the mutex queue. +*/ +struct optimistic_spin_queue reader_osq; +struct task_struct *first_reader; +enum futex_type type;union futex_key key; };
@@ -3388,14 +3398,21 @@ void exit_robust_list(struct task_struct *curr)* 0 - steals the lock * 1 - top waiter (mutex owner) acquires the lock * 2 - handed off the lock -* 2) bits 08-15: reserved -* 3) bits 15-30: how many times the task has slept or yield to scheduler +* 2) bit 08: 1 if reader spins alone (shared lock only) +* bit 09: 1 if reader is a spin group leader (shared lock only) +* bits 10-16: reserved +* 3) bits 16-30: how many times the task has slept or yield to scheduler * in futex_spin_on_owner(). */ #define TP_LOCK_STOLEN 0#define TP_LOCK_ACQUIRED 1#define TP_LOCK_HANDOFF 2 + +#define TP_READER_ALONE 1 +#define TP_READER_GROUP 2 +#define TP_STATUS_SLEEP (val, sleep) ((val)|((sleep) << 16)) +#define TP_STATUS_ALONE (val, alone) ((val)|((alone) << 8))/** * lookup_futex_state - Looking up the futex state structure.
@@ -3438,6 +3455,7 @@ void exit_robust_list(struct task_struct *curr) state->type =TYPE_TP ; state->key = *key; state->locksteal_disabled = false; +osq_lock_init (&state->reader_osq);list_add (&state->fs_list, &hb->fs_head);WARN_ON (atomic_read (&state->refcount) !=1 );
@@ -3895,6 +3913,98 @@ static int futex_spin_on_owner(u32 __user *uaddr, const u32 vpid,return (ret <0 ) ? ret :TP_STATUS_SLEEP (ret, nsleep); } +/** +* futex_spin_on_reader - Optimistically spin on first reader +* @uaddr: futex address +* @pfirst: pointer to first reader +* @state: futex state object +* @timeout: hrtimer_sleeper structure +* +* Reader performance will depend on the placement of readers within the +* mutex queue. For a queue of 4 readers and 4 writers, for example, the +* optimal placement will be either RRRRWWWW or WWWWRRRR. A worse case will +* be RWRWRWRW. +* +* One way to avoid the worst case scenario is to gather all the readers +* together as a single unit and place it into the mutex queue. This is done +* by having the first reader puts its task structure into state->first_reader +* and the rest of the readers optimistically spin on it instead of entering +* the mutex queue. +* +* On exit from this function, the reader would have +* 1) acquired a read lock on the futex; +* 2) become the first reader and goes into the mutex queue; or +* 3) seen the first reader slept and needs to go into the mutex queue alone. +* +* Any fault on accessing the futex will cause it to return 0 and goes into +* the mutex queue. +* +* Return: > 0 - acquired the read lock +* <= 0 - need to goes into the mutex queue +*/ +static int futex_spin_on_reader (u32 __user *uaddr,struct task_struct **pfirst, +struct futex_state *state, +struct hrtimer_sleeper *timeout) +{ +struct task_struct *first_reader; +int ret =0 ; + +preempt_disable (); + +retry: + first_reader =READ_ONCE (state->first_reader); +if (!first_reader) + first_reader =cmpxchg (&state->first_reader,NULL , current); +if (!first_reader) + goto out;/* Became the first reader */ + +if (!osq_lock (&state->reader_osq)) + goto reschedule; + +for (;;) { +u32 uval; + +if (!state->locksteal_disabled) { + ret =futex_trylock_preempt_disabled (uaddr, +FUTEX_SHARED , &uval); +/* +* Return if lock acquired or an error happened +*/ +if (ret) +break ; + } + +/* +* Reread the first reader value again. +*/ + first_reader =READ_ONCE (state->first_reader); +if (!first_reader) + first_reader =cmpxchg (&state->first_reader,NULL , + current); +if (!first_reader || !first_reader->on_cpu) +break ; + +if (need_resched ()) { +osq_unlock (&state->reader_osq); + goto reschedule; + } + +cpu_relax (); + } +osq_unlock (&state->reader_osq); +out: + *pfirst = first_reader; +preempt_enable (); +return ret; + +reschedule: +/* +* Yield the CPU and retry later. +*/ +schedule_preempt_disabled (); + goto retry; +} +/* * Userspace tried a 0 -> TID atomic transition of the futex value * and failed. The kernel side here does the whole locking operation.
@@ -3921,9 +4031,11 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags, {struct hrtimer_sleeper timeout, *to;struct futex_hash_bucket *hb; +struct task_struct *first_reader =NULL ;union futex_key key =FUTEX_KEY_INIT ;struct futex_state *state;u32 uval, vpid = shared ?FUTEX_SHARED :task_pid_vnr (current); +int alone =0 ;int ret;/*
@@ -3989,6 +4101,17 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags,hrtimer_start_expires (&to->timer,HRTIMER_MODE_ABS );/* +* Spin on the first reader and return if we acquired the read lock. +*/ +if (shared) { +int rspin_ret =futex_spin_on_reader (uaddr, &first_reader, + state, to); +if (rspin_ret >0 ) + goto out_put_state_key; + alone = first_reader ?TP_READER_ALONE :TP_READER_GROUP ; + } + +/* * Acquiring the serialization mutex. * * If we got a signal or has some other error, we need to abort
@@ -4016,6 +4139,12 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags,mutex_unlock (&state->mutex); out_put_state_key: +/* +* We will be the first reader if (first_reader == NULL). +*/ +if (shared && !first_reader) +WRITE_ONCE (state->first_reader,NULL ); +if (!put_futex_state_unlocked (state)) {/* * May need to free the futex state object and so must be
@@ -4032,7 +4161,7 @@ static noinline int futex_lock(u32 __user *uaddr, unsigned int flags,hrtimer_cancel (&to->timer);destroy_hrtimer_on_stack (&to->timer); } -return ret; +return (ret <0 ) ? ret :TP_STATUS_ALONE (ret, alone); }/*
Signature · 1 line
1.8.3.1
kbuild test robot
build botRe: [PATCH v4 16/20] TP-futex: Group readers together in wait queue
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021 config: m32r-usrv_defconfig (attached as .config) compiler: m32r-linux-gcc…
Hi Waiman,
[auto build test ERROR on linus/master]
[also build test ERROR on v4.10-rc1 next-20161224]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021
config: m32r-usrv_defconfig (attached as .config)
compiler: m32r-linux-gcc (GCC) 6.2.0
reproduce:
wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=m32r
All errors (new ones prefixed by >>):
kernel/built-in.o: In function `futex_spin_on_reader':
>> kernel/futex.c:3961: undefined reference to `osq_lock' kernel/futex.c:3961:(.text+0x60cd8): relocation truncated to fit: R_M32R_26_PCREL_RELA against undefined symbol `osq_lock' >> kernel/futex.c:3988: undefined reference to `osq_unlock'
kernel/futex.c:3988:(.text+0x61028): relocation truncated to fit: R_M32R_26_PCREL_RELA against undefined symbol osq_unlock' kernel/futex.c:3994: undefined reference to osq_unlock'
kernel/futex.c:3994:(.text+0x61080): relocation truncated to fit: R_M32R_26_PCREL_RELA against undefined symbol `osq_unlock'
vim +3961 kernel/futex.c
3955 first_reader = READ_ONCE(state->first_reader);
3956 if (!first_reader)
3957 first_reader = cmpxchg(&state->first_reader, NULL, current);
3958 if (!first_reader)
3959 goto out; /* Became the first reader */
3960
> 3961 if (!osq_lock(&state->reader_osq))
3962 goto reschedule;
3963
3964 for (;;) {
3965 u32 uval;
3966
3967 if (!state->locksteal_disabled) {
3968 ret = futex_trylock_preempt_disabled(uaddr,
3969 FUTEX_SHARED, &uval);
3970 /*
3971 * Return if lock acquired or an error happened
3972 */
3973 if (ret)
3974 break;
3975 }
3976
3977 /*
3978 * Reread the first reader value again.
3979 */
3980 first_reader = READ_ONCE(state->first_reader);
3981 if (!first_reader)
3982 first_reader = cmpxchg(&state->first_reader, NULL,
3983 current);
3984 if (!first_reader || !first_reader->on_cpu)
3985 break;
3986
3987 if (need_resched()) {
> 3988 osq_unlock(&state->reader_osq); 3989 goto reschedule;
3990 }
3991
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
kbuild test robot
build botRe: [PATCH v4 16/20] TP-futex: Group readers together in wait queue
In reply to Waiman Longurl: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021 config: ia64-defconfig (attached as .config) compiler: ia64-linux-gcc…
Hi Waiman,
[auto build test ERROR on linus/master]
[also build test ERROR on v4.10-rc1 next-20161224]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021
config: ia64-defconfig (attached as .config)
compiler: ia64-linux-gcc (GCC) 6.2.0
reproduce:
wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=ia64
All errors (new ones prefixed by >>):
kernel/built-in.o: In function `futex_lock':
>> futex.c:(.text+0xfadd2): undefined reference to `osq_lock' >> futex.c:(.text+0xfb1f2): undefined reference to `osq_unlock'
futex.c:(.text+0xfb2d2): undefined reference to osq_unlock' futex.c:(.text+0xfc0c2): undefined reference to osq_lock' futex.c:(.text+0xfc6d2): undefined reference to `osq_unlock'
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
PATCH v4 17/20 TP-futex, doc: Update TP futexes document on shared locking
In reply to Waiman LongThe tp-futex.txt was updated to add description about shared locking support.
--- Documentation/tp-futex.txt | 163 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 143 insertions(+), 20 deletions(-)
Documentation/tp-futex.txt +143 -20
@@ -68,22 +68,58 @@ the kernel using the FUTEX_LOCK futex(2) syscall. Only the optional timeout parameter is being used by this futex(2) syscall. -Inside the kernel, a kernel mutex is used for serialization among -the futex waiters. Only the top lock waiter which is the owner of -the serialization mutex is allowed to continuously spin and attempt -to acquire the lock. Other lock waiters will have one attempt to -steal the lock before entering the mutex queues. +A shared lock acquirer, on the other hand, has to do either one of +the following 2 actions depends on the current value of the futex: + + 1) If current futex value is 0, atomically put (FUTEX_SHARED + 1) + into the lower 30 bits of the futex. + + 2) If the FUTEX_SHARED bit is set and the FUTEX_SHARED_UNLOCK bit + isn't, atomically increment the reader count in the lower 24 bits. + The other unused bits (24-27) can be used for other management purpose + and will be ignored by the kernel. + +Any failure to both of the above actions will lead to the following +new FUTEX_LOCK_SHARED futex(2) syscall. + + futex(uaddr, FUTEX_LOCK_SHARED, 0, timeout, NULL, 0); + +The special FUTEX_SHARED bit (bit 30) is used to distinguish between a +shared lock and an exclusive lock. If the FUTEX_SHARED bit isn't set, +the lower 29 bits contain the TID of the exclusive lock owner. If +the FUTEX_SHARED bit is set, the lower 29 bits contain the number of +tasks owning the shared lock. Therefore, only 29 bits are allowed +to be used by the TID. + +The FUTEX_SHARED_UNLOCK bit can be used by userspace to prevent racing +and to indicate that a shared unlock is in progress as it will disable +any shared trylock attempt in the kernel. + +Inside the kernel, a kernel mutex is used for serialization among the +futex waiters. Only the top lock waiter which is the owner of the +serialization mutex is allowed to continuously spin and attempt to +acquire the lock. Other lock waiters will have one or two attempts +to steal the lock before entering the mutex queues. When the exclusive futex lock owner is no longer running, the top waiter will set the FUTEX_WAITERS bit before going to sleep. This is to make sure the futex owner will go into the kernel at unlock time to wake up the top waiter. +When the futex is shared by multiple owners, there is no way to +determine if all the shared owners are running or not. In this case, +the top waiter will spin for a fixed amount of time if no reader +activity is observed before giving up and going to sleep. Any change +in the reader count will be considered reader activities and so will +reset the timer. However, when the hand-off threshold has been reached, +reader optimistic spinning timer reset will be disabled automatically. + The return values of the above futex locking syscall, if non-negative, -are status code that consists of 2 fields - the lock acquisition code -(bits 0-7) and the number of sleeps (bits 8-30) in the optimistic -spinning loop before acquiring the futex. A negative returned value -means an error has happened. +are status code that consists of 3 fields - the lock acquisition code +(bits 0-7) and the number of sleeps (bits 16-30) in the optimistic +spinning loop before acquiring the futex. Bits 8-15 are reserved +for future extension. A negative returned value means an error has +happened. The lock acquisition code can have the following values: a) 0 - lock stolen as non-top waiter
@@ -96,14 +132,21 @@ issue a FUTEX_UNLOCK futex(2) syscall to wake up the top waiter. futex(uaddr, FUTEX_UNLOCK, 0, NULL, NULL, 0); -A return value of 1 from the FUTEX_UNLOCK futex(2) syscall indicates -a task has been woken up. The syscall returns 0 if no sleeping task -is woken. A negative value will be returned if an error happens. +For a shared lock owner, unlock is done by atomically decrementing +the reader count by one. If the resultant futex value has no reader +remaining, the process has to atomically change the futex value from +FUTEX_SHARED to 0. If that fails, it has to issue a FUTEX_UNLOCK_SHARED +futex(2) syscall to wake up the top waiter. + +A return value of 1 from the FUTEX_UNLOCK or FUTEX_UNLOCK_SHARED +futex(2) syscall indicates a task has been woken up. The syscall +returns 0 if no sleeping task is woken. A negative value will be +returned if an error happens. -The error number returned by a FUTEX_UNLOCK syscall on an empty futex -can be used to decide if the TP futex functionality is implemented -in the kernel. If it is present, an EPERFM error will be returned. -Otherwise it will return ENOSYS. +The error number returned by a FUTEX_UNLOCK or FUTEX_UNLOCK_SHARED +syscall on an empty futex can be used to decide if the TP futex +functionality is implemented in the kernel. If it is present, an +EPERFM error will be returned. Otherwise it will return ENOSYS. TP futexes require the kernel to have SMP support as well as support for the cmpxchg functionality. For architectures that don't support
@@ -118,6 +161,13 @@ is the PID wrap-around issue where another process with the same PID as the real futex owner because of PID wrap-around is mis-identified as the owner of a futex. +Shared locking TP futexes, on the other hand, cannot be used with +robust futexes. The unexpected death of a shared lock owner may +leave the futex permanently in the shared state leading to deadlock +of exclusive lock waiters. If necessary, some kind of timeout and +userspace lock management system have to be used to resolve this +deadlock problem. + Usage Scenario --------------
@@ -129,6 +179,24 @@ used when the critical section is long and prone to sleeping. However, it may not have the performance gain when compared with a wait-wake futex in this case. +A TP futex can also be used to implement a userspace reader-writer +lock (rwlock) where the writers use the FUTEX_LOCK and FUTEX_UNLOCK +futex(2) syscalls and the readers used the FUTEX_LOCK_SHARED and +FUTEX_UNLOCK_SHARED futex(2) syscalls. Beside providing a better +performance compared with wait-wake futexes, other advantages of +using the the TP futexes for rwlocks are: + + 1) The simplicity and ease of maintenance of the userspace locking + codes. There is only one version of rwlock using TP futex versus + a reader-preferring variant and/or a writer-preferring variant + when using the wait-wake futexes. + + 2) TP futex is lock-starvation free. That may not be the case + with rwlocks implemented with wait-wake futexes especially the + reader-preferring variants where the writers can be starved of + the lock under certain circumstances. Some writer-preferring + rwlock variants may also starve readers of getting the lock. + The wait-wake futexes are more versatile as they can also be used to implement other locking primitives like semaphores or conditional variables. So the TP futex is not a direct replacement of the
@@ -138,12 +206,12 @@ futex is likely a better option than the wait-wake futex. Sample Code ----------- -The following are sample code to implement simple mutex lock and -unlock functions. +The following are sample code to implement simple mutex or writer +lock and unlock functions. __thread int thread_id; -void mutex_lock(int *faddr) +void write_lock(int *faddr) { if (cmpxchg(faddr, 0, thread_id) == 0) return;
@@ -151,7 +219,7 @@ void mutex_lock(int *faddr) ; } -void mutex_unlock(int *faddr) +void write_unlock(int *faddr) { int old, fval;
@@ -159,3 +227,58 @@ void mutex_unlock(int *faddr) return; futex(faddr, FUTEX_UNLOCK, ...); } + +The following sample code can be used to implement shared or reader +lock and unlock functions. + +void read_lock(int *faddr) +{ + int val = cmpxchg(faddr, 0, FUTEX_SHARED + 1); + + if (!val) + return; + for (;;) { + int old = val, new; + + if (!old) + new = FUTEX_SHARED + 1; + else if (!(old & FUTEX_SHARED) || + (old & ((~FUTEX_SHARED_TID_MASK)| + FUTEX_SHARED_UNLOCK))) + break; + else + new = old + 1; + val = cmpxchg(faddr, old, new); + if (val == old) + return; + } + while (futex(faddr, FUTEX_LOCK_SHARED, ...) < 0) + ; +} + +void read_unlock(int *faddr) +{ + int val = xadd(faddr, -1) - 1; + int old; + + for (;;) { + /* + * Return if not the last reader, not in shared locking + * mode or the unlock bit has been set. + */ + if (!(val & FUTEX_SHARED) || + (val & (FUTEX_SHARED_UNLOCK|FUTEX_SCNT_MASK))) + return; + if (val & ~FUTEX_SHARED_TID_MASK) { + old = cmpxchg(faddr, val, val|FUTEX_SHARED_UNLOCK); + if (old == val) + break; /* Do FUTEX_UNLOCK_SHARED */ + } else { + old = cmpxchg(faddr, val, 0); + if (old == val) + return; + } + val = old; + } + futex(faddr, FUTEX_UNLOCK_SHARED, ...); +}
Signature · 1 line
1.8.3.1
PATCH v4 18/20 perf bench: New microbenchmark for userspace rwlock performance
In reply to Waiman LongThis microbenchmark simulates how the use of different futex types can affect the actual performanace of userspace rwlock locks. The usage is:
perf bench futex rwlock <options>
Three sets of simple rwlock lock and unlock functions are implemented using the wait-wake, TP futexes and glibc rwlock respectively. This microbenchmark then runs the locking rate measurement tests using either one of those rwlocks or all of them consecutively.
--- tools/perf/bench/bench.h | 1 + tools/perf/bench/futex-locks.c | 842 ++++++++++++++++++++++++++++++++++++++++- tools/perf/bench/futex.h | 24 +- tools/perf/builtin-bench.c | 1 + 4 files changed, 850 insertions(+), 18 deletions(-)
tools/perf/bench/bench.h +1 -0
@@ -37,6 +37,7 @@/* pi futexes */ int bench_futex_lock_pi (int argc,const char **argv,const char *prefix);int bench_futex_mutex (int argc,const char **argv,const char *prefix); +int bench_futex_rwlock (int argc,const char **argv,const char *prefix);#define BENCH_FORMAT_DEFAULT_STR "default"#define BENCH_FORMAT_DEFAULT 0
tools/perf/bench/futex-locks.c +826 -16
1,000 lines not on this page · raw message
tools/perf/bench/futex.h +22 -2
@@ -88,8 +88,10 @@ }#ifndef FUTEX_LOCK -#define FUTEX_LOCK 0xff/* Disable the use of TP futexes */ -#define FUTEX_UNLOCK 0xff +#define FUTEX_LOCK 0xff/* Disable the use of TP futexes */ +#define FUTEX_UNLOCK 0xff +#define FUTEX_LOCK_SHARED 0xff +#define FUTEX_UNLOCK_SHARED 0xff#endif /* FUTEX_LOCK */ /**
@@ -110,6 +112,24 @@return futex (uaddr,FUTEX_UNLOCK ,0 ,NULL ,NULL ,0 , opflags); } +/** +* futex_lock_shared() - shared locking of TP futex +*/ +static inline int +futex_lock_shared (u_int32_t *uaddr,struct timespec *timeout,int opflags) +{ +return futex (uaddr,FUTEX_LOCK_SHARED ,0 , timeout,NULL ,0 , opflags); +} + +/** +* futex_unlock_shared() - shared unlocking of TP futex +*/ +static inline int +futex_unlock_shared (u_int32_t *uaddr,int opflags) +{ +return futex (uaddr,FUTEX_UNLOCK_SHARED ,0 ,NULL ,NULL ,0 , opflags); +} +#ifndef HAVE_PTHREAD_ATTR_SETAFFINITY_NP #include <pthread.h> static inline int pthread_attr_setaffinity_np(pthread_attr_t *attr,
tools/perf/builtin-bench.c +1 -0
@@ -64,6 +64,7 @@ struct bench {/* pi-futexes */ { "lock-pi", "Benchmark for futex lock_pi calls", bench_futex_lock_pi }, { "mutex", "Benchmark for mutex locks using futexes", bench_futex_mutex }, + { "rwlock", "Benchmark for rwlocks using futexes", bench_futex_rwlock }, { "all", "Run all futex benchmarks", NULL }, { NULL, NULL, NULL } };
Signature · 1 line
1.8.3.1
PATCH v4 19/20 sched, TP-futex: Make wake_up_q() return wakeup count
In reply to Waiman LongUnlike wake_up_process(), wake_up_q() doesn't tell us how many tasks have been woken up. This information can sometimes be useful for tracking purpose. So wake_up_q() is now modified to return that information.
--- include/linux/sched.h | 2 +- kernel/futex.c | 8 +++----- kernel/sched/core.c | 6 ++++-- 3 files changed, 8 insertions(+), 8 deletions(-)
include/linux/sched.h +1 -1
@@ -1038,7 +1038,7 @@ struct wake_q_head {extern void wake_q_add (struct wake_q_head *head,struct task_struct *task); -extern void wake_up_q (struct wake_q_head *head); +extern int wake_up_q (struct wake_q_head *head);/* * sched-domains (multiprocessor balancing) declarations:
kernel/futex.c +3 -5
@@ -4254,11 +4254,9 @@ static int futex_unlock(u32 __user *uaddr, unsigned int flags, out_put_key:put_futex_key (&key);if (owner) { -/* -* No error would have happened if owner defined. -*/ -wake_up_q (&wake_q); -return ret ? ret :1 ; +int cnt =wake_up_q (&wake_q); + +return ret ? ret : cnt; }return ret;
kernel/sched/core.c +4 -2
@@ -449,9 +449,10 @@ void wake_q_add(struct wake_q_head *head, struct task_struct *task) head->lastp = &node->next; } -void wake_up_q (struct wake_q_head *head) +int wake_up_q (struct wake_q_head *head) {struct wake_q_node *node = head->first; +int wakecnt =0 ;while (node !=WAKE_Q_TAIL ) {struct task_struct *task;
@@ -466,9 +467,10 @@ void wake_up_q(struct wake_q_head *head)* wake_up_process() implies a wmb() to pair with the queueing * in wake_q_add() so as not to miss wakeups. */ -wake_up_process (task); + wakecnt +=wake_up_process (task);put_task_struct (task); } +return wakecnt; }/*
Signature · 1 line
1.8.3.1
PATCH v4 20/20 futex: Dump internal futex state via debugfs
In reply to Waiman LongFor debugging purpose, it is sometimes useful to dump the internal states in the futex hash bucket table. This patch adds a file "futex_hash_table" in debugfs root filesystem to dump the internal futex states.
--- kernel/futex.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+)
kernel/futex.c +82 -0
@@ -4425,3 +4425,85 @@ static int __init futex_init(void)return 0 ; }__initcall (futex_init); + +#if defined(CONFIG_DEBUG_FS ) && defined(CONFIG_SMP ) +/* +* Debug code to dump selected content of in-kernel futex hash bucket table. +*/ +#include <linux/debugfs.h> + +static int futex_dump_show (struct seq_file *m,void *arg) +{ +struct futex_hash_bucket *hb = arg; +struct futex_state *state; +int i; + +if (list_empty (&hb->fs_head)) +return 0 ; + +seq_printf (m,"\nHash bucket %ld:\n" , hb - futex_queues); +spin_lock (&hb->fs_lock); + i =0 ; +list_for_each_entry (state, &hb->fs_head, fs_list) { +seq_printf (m," Futex state %d\n" , i++); +if (state->owner) +seq_printf (m," owner PID = %d\n" , +task_pid_vnr (state->owner)); +if (state->mutex_owner) +seq_printf (m," mutex owner PID = %d\n" , +task_pid_vnr (state->mutex_owner)); +seq_printf (m," reference count = %d\n" , +atomic_read (&state->refcount)); +seq_printf (m," handoff PID = %d\n" , state->handoff_pid); + } +spin_unlock (&hb->fs_lock); +return 0 ; +} + +static void *futex_dump_start (struct seq_file *m,loff_t *pos) +{ +return (*pos < futex_hashsize) ? &futex_queues[*pos] :NULL ; +} + +static void *futex_dump_next (struct seq_file *m,void *arg,loff_t *pos) +{ + (*pos)++; +return (*pos < futex_hashsize) ? &futex_queues[*pos] :NULL ; +} + +static void futex_dump_stop (struct seq_file *m,void *arg) +{ +} + +static const struct seq_operations futex_dump_op = { + .start = futex_dump_start, + .next = futex_dump_next, + .stop = futex_dump_stop, + .show = futex_dump_show, +}; + +static int futex_dump_open (struct inode *inode,struct file *file) +{ +return seq_open (file, &futex_dump_op); +} + +static const struct file_operations fops_futex_dump = { + .open = futex_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/* +* Initialize debugfs for the futex hash bucket table dump. +*/ +static int __init init_futex_dump (void ) +{ +if (!debugfs_create_file ("futex_hash_table" ,0400 ,NULL ,NULL , + &fops_futex_dump)) +return -ENOMEM ; +return 0 ; +} +fs_initcall (init_futex_dump); + +#endif /* CONFIG_DEBUG_FS && CONFIG_SMP */
Signature · 1 line
1.8.3.1
kbuild test robot
build botRe: [PATCH v4 20/20] futex: Dump internal futex state via debugfs
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021 config: i386-randconfig-x006-201652 (attached as .config) compiler: gcc-6…
Hi Waiman,
[auto build test WARNING on linus/master]
[also build test WARNING on v4.10-rc1 next-20161224]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Waiman-Long/futex-Introducing-throughput-optimized-TP-futexes/20161230-020021
config: i386-randconfig-x006-201652 (attached as .config)
compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901 reproduce:
# save the attached .config to linux build tree
make ARCH=i386 All warnings (new ones prefixed by >>):
kernel/futex.c: In function 'futex_dump_show':
>> kernel/futex.c:4444:33: warning: format '%ld' expects argument of type 'long int', but argument 3 has type 'int' [-Wformat=]
seq_printf(m, "\nHash bucket %ld:\n", hb - futex_queues);
^
kernel/futex.c: In function 'futex_lock':
kernel/futex.c:3796:13: warning: 'rspin_timeout' may be used uninitialized in this function [-Wmaybe-uninitialized]
else if (curtime > rspin_timeout)
^
kernel/futex.c:3676:6: note: 'rspin_timeout' was declared here
u64 rspin_timeout;
^~~~~~~~~~~~~vim +4444 kernel/futex.c
4428
4429 #if defined(CONFIG_DEBUG_FS) && defined(CONFIG_SMP)
4430 /*
4431 * Debug code to dump selected content of in-kernel futex hash bucket table.
4432 */
4433 #include <linux/debugfs.h>
4434
4435 static int futex_dump_show(struct seq_file *m, void *arg)
4436 {
4437 struct futex_hash_bucket *hb = arg;
4438 struct futex_state *state;
4439 int i;
4440
4441 if (list_empty(&hb->fs_head))
4442 return 0;
4443
> 4444 seq_printf(m, "\nHash bucket %ld:\n", hb - futex_queues);
4445 spin_lock(&hb->fs_lock);
4446 i = 0;
4447 list_for_each_entry(state, &hb->fs_head, fs_list) {
4448 seq_printf(m, " Futex state %d\n", i++);
4449 if (state->owner)
4450 seq_printf(m, " owner PID = %d\n",
4451 task_pid_vnr(state->owner));
4452 if (state->mutex_owner)
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation