sched, timer: Replace spinlocks with atomics in thread_group_cputimer(), to improve...
authorJason Low <jason.low2@hp.com>
Tue, 28 Apr 2015 20:00:22 +0000 (13:00 -0700)
committerIngo Molnar <mingo@kernel.org>
Fri, 8 May 2015 10:15:31 +0000 (12:15 +0200)
While running a database workload, we found a scalability issue with itimers.

Much of the problem was caused by the thread_group_cputimer spinlock.
Each time we account for group system/user time, we need to obtain a
thread_group_cputimer's spinlock to update the timers. On larger systems
(such as a 16 socket machine), this caused more than 30% of total time
spent trying to obtain this kernel lock to update these group timer stats.

This patch converts the timers to 64-bit atomic variables and use
atomic add to update them without a lock. With this patch, the percent
of total time spent updating thread group cputimer timers was reduced
from 30% down to less than 1%.

Note: On 32-bit systems using the generic 64-bit atomics, this causes
sample_group_cputimer() to take locks 3 times instead of just 1 time.
However, we tested this patch on a 32-bit system ARM system using the
generic atomics and did not find the overhead to be much of an issue.
An explanation for why this isn't an issue is that 32-bit systems usually
have small numbers of CPUs, and cacheline contention from extra spinlocks
called periodically is not really apparent on smaller systems.

Signed-off-by: Jason Low <jason.low2@hp.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Thomas Gleixner <tglx@linutronix.de>
Acked-by: Rik van Riel <riel@redhat.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Aswin Chandramouleeswaran <aswin@hp.com>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Mel Gorman <mgorman@suse.de>
Cc: Mike Galbraith <umgwanakikbuti@gmail.com>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Preeti U Murthy <preeti@linux.vnet.ibm.com>
Cc: Scott J Norton <scott.norton@hp.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Waiman Long <Waiman.Long@hp.com>
Link: http://lkml.kernel.org/r/1430251224-5764-4-git-send-email-jason.low2@hp.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
include/linux/init_task.h
include/linux/sched.h
kernel/fork.c
kernel/sched/stats.h
kernel/time/posix-cpu-timers.c

index 696d22312b3199ed2f515240111a669a35d48934..7b9d8b59e7bf12a90b2991116a7bdf53e366825b 100644 (file)
@@ -50,9 +50,10 @@ extern struct fs_struct init_fs;
        .cpu_timers     = INIT_CPU_TIMERS(sig.cpu_timers),              \
        .rlim           = INIT_RLIMITS,                                 \
        .cputimer       = {                                             \
-               .cputime = INIT_CPUTIME,                                \
-               .running = 0,                                           \
-               .lock = __RAW_SPIN_LOCK_UNLOCKED(sig.cputimer.lock),    \
+               .utime            = ATOMIC64_INIT(0),                   \
+               .stime            = ATOMIC64_INIT(0),                   \
+               .sum_exec_runtime = ATOMIC64_INIT(0),                   \
+               .running          = 0                                   \
        },                                                              \
        .cred_guard_mutex =                                             \
                 __MUTEX_INITIALIZER(sig.cred_guard_mutex),             \
index d70910355b20a93c88822098b810c51b8f4a33f4..a45874c3fab6bfc955bff4d03ea53c7525c94494 100644 (file)
@@ -598,9 +598,10 @@ struct task_cputime {
  * used for thread group CPU timer calculations.
  */
 struct thread_group_cputimer {
-       struct task_cputime cputime;
+       atomic64_t utime;
+       atomic64_t stime;
+       atomic64_t sum_exec_runtime;
        int running;
-       raw_spinlock_t lock;
 };
 
 #include <linux/rwsem.h>
@@ -2967,11 +2968,6 @@ static __always_inline bool need_resched(void)
 void thread_group_cputime(struct task_struct *tsk, struct task_cputime *times);
 void thread_group_cputimer(struct task_struct *tsk, struct task_cputime *times);
 
-static inline void thread_group_cputime_init(struct signal_struct *sig)
-{
-       raw_spin_lock_init(&sig->cputimer.lock);
-}
-
 /*
  * Reevaluate whether the task has signals pending delivery.
  * Wake the task if so.
index 47c37a411a620163b215a94b32f852572d7ef4b9..2e670864174f084a871fab9ca1597e77a6e17d9b 100644 (file)
@@ -1091,9 +1091,6 @@ static void posix_cpu_timers_init_group(struct signal_struct *sig)
 {
        unsigned long cpu_limit;
 
-       /* Thread group counters. */
-       thread_group_cputime_init(sig);
-
        cpu_limit = READ_ONCE(sig->rlim[RLIMIT_CPU].rlim_cur);
        if (cpu_limit != RLIM_INFINITY) {
                sig->cputime_expires.prof_exp = secs_to_cputime(cpu_limit);
index 4ab7043396569f201cd6dde0a997733a71a4bb7a..c6d1c7da3ea53df7559ae21b2025ed34514b4a0e 100644 (file)
@@ -174,7 +174,8 @@ static inline bool cputimer_running(struct task_struct *tsk)
 {
        struct thread_group_cputimer *cputimer = &tsk->signal->cputimer;
 
-       if (!cputimer->running)
+       /* Check if cputimer isn't running. This is accessed without locking. */
+       if (!READ_ONCE(cputimer->running))
                return false;
 
        /*
@@ -215,9 +216,7 @@ static inline void account_group_user_time(struct task_struct *tsk,
        if (!cputimer_running(tsk))
                return;
 
-       raw_spin_lock(&cputimer->lock);
-       cputimer->cputime.utime += cputime;
-       raw_spin_unlock(&cputimer->lock);
+       atomic64_add(cputime, &cputimer->utime);
 }
 
 /**
@@ -238,9 +237,7 @@ static inline void account_group_system_time(struct task_struct *tsk,
        if (!cputimer_running(tsk))
                return;
 
-       raw_spin_lock(&cputimer->lock);
-       cputimer->cputime.stime += cputime;
-       raw_spin_unlock(&cputimer->lock);
+       atomic64_add(cputime, &cputimer->stime);
 }
 
 /**
@@ -261,7 +258,5 @@ static inline void account_group_exec_runtime(struct task_struct *tsk,
        if (!cputimer_running(tsk))
                return;
 
-       raw_spin_lock(&cputimer->lock);
-       cputimer->cputime.sum_exec_runtime += ns;
-       raw_spin_unlock(&cputimer->lock);
+       atomic64_add(ns, &cputimer->sum_exec_runtime);
 }
index e072d982f64cfb45db4ca8ed2922df16ff0c66d6..d85730669410ead09174e5aff1fdaa0bd59239fc 100644 (file)
@@ -196,39 +196,62 @@ static int cpu_clock_sample(const clockid_t which_clock, struct task_struct *p,
        return 0;
 }
 
-static void update_gt_cputime(struct task_cputime *a, struct task_cputime *b)
+/*
+ * Set cputime to sum_cputime if sum_cputime > cputime. Use cmpxchg
+ * to avoid race conditions with concurrent updates to cputime.
+ */
+static inline void __update_gt_cputime(atomic64_t *cputime, u64 sum_cputime)
 {
-       if (b->utime > a->utime)
-               a->utime = b->utime;
+       u64 curr_cputime;
+retry:
+       curr_cputime = atomic64_read(cputime);
+       if (sum_cputime > curr_cputime) {
+               if (atomic64_cmpxchg(cputime, curr_cputime, sum_cputime) != curr_cputime)
+                       goto retry;
+       }
+}
 
-       if (b->stime > a->stime)
-               a->stime = b->stime;
+static void update_gt_cputime(struct thread_group_cputimer *cputimer, struct task_cputime *sum)
+{
+       __update_gt_cputime(&cputimer->utime, sum->utime);
+       __update_gt_cputime(&cputimer->stime, sum->stime);
+       __update_gt_cputime(&cputimer->sum_exec_runtime, sum->sum_exec_runtime);
+}
 
-       if (b->sum_exec_runtime > a->sum_exec_runtime)
-               a->sum_exec_runtime = b->sum_exec_runtime;
+/* Sample thread_group_cputimer values in "cputimer", store results in "times". */
+static inline void sample_group_cputimer(struct task_cputime *times,
+                                         struct thread_group_cputimer *cputimer)
+{
+       times->utime = atomic64_read(&cputimer->utime);
+       times->stime = atomic64_read(&cputimer->stime);
+       times->sum_exec_runtime = atomic64_read(&cputimer->sum_exec_runtime);
 }
 
 void thread_group_cputimer(struct task_struct *tsk, struct task_cputime *times)
 {
        struct thread_group_cputimer *cputimer = &tsk->signal->cputimer;
        struct task_cputime sum;
-       unsigned long flags;
 
-       if (!cputimer->running) {
+       /* Check if cputimer isn't running. This is accessed without locking. */
+       if (!READ_ONCE(cputimer->running)) {
                /*
                 * The POSIX timer interface allows for absolute time expiry
                 * values through the TIMER_ABSTIME flag, therefore we have
-                * to synchronize the timer to the clock every time we start
-                * it.
+                * to synchronize the timer to the clock every time we start it.
                 */
                thread_group_cputime(tsk, &sum);
-               raw_spin_lock_irqsave(&cputimer->lock, flags);
-               cputimer->running = 1;
-               update_gt_cputime(&cputimer->cputime, &sum);
-       } else
-               raw_spin_lock_irqsave(&cputimer->lock, flags);
-       *times = cputimer->cputime;
-       raw_spin_unlock_irqrestore(&cputimer->lock, flags);
+               update_gt_cputime(cputimer, &sum);
+
+               /*
+                * We're setting cputimer->running without a lock. Ensure
+                * this only gets written to in one operation. We set
+                * running after update_gt_cputime() as a small optimization,
+                * but barriers are not required because update_gt_cputime()
+                * can handle concurrent updates.
+                */
+               WRITE_ONCE(cputimer->running, 1);
+       }
+       sample_group_cputimer(times, cputimer);
 }
 
 /*
@@ -582,7 +605,8 @@ bool posix_cpu_timers_can_stop_tick(struct task_struct *tsk)
        if (!task_cputime_zero(&tsk->cputime_expires))
                return false;
 
-       if (tsk->signal->cputimer.running)
+       /* Check if cputimer is running. This is accessed without locking. */
+       if (READ_ONCE(tsk->signal->cputimer.running))
                return false;
 
        return true;
@@ -882,14 +906,12 @@ static void check_thread_timers(struct task_struct *tsk,
        }
 }
 
-static void stop_process_timers(struct signal_struct *sig)
+static inline void stop_process_timers(struct signal_struct *sig)
 {
        struct thread_group_cputimer *cputimer = &sig->cputimer;
-       unsigned long flags;
 
-       raw_spin_lock_irqsave(&cputimer->lock, flags);
-       cputimer->running = 0;
-       raw_spin_unlock_irqrestore(&cputimer->lock, flags);
+       /* Turn off cputimer->running. This is done without locking. */
+       WRITE_ONCE(cputimer->running, 0);
 }
 
 static u32 onecputick;
@@ -1111,12 +1133,11 @@ static inline int fastpath_timer_check(struct task_struct *tsk)
        }
 
        sig = tsk->signal;
-       if (sig->cputimer.running) {
+       /* Check if cputimer is running. This is accessed without locking. */
+       if (READ_ONCE(sig->cputimer.running)) {
                struct task_cputime group_sample;
 
-               raw_spin_lock(&sig->cputimer.lock);
-               group_sample = sig->cputimer.cputime;
-               raw_spin_unlock(&sig->cputimer.lock);
+               sample_group_cputimer(&group_sample, &sig->cputimer);
 
                if (task_cputime_expired(&group_sample, &sig->cputime_expires))
                        return 1;
@@ -1157,7 +1178,7 @@ void run_posix_cpu_timers(struct task_struct *tsk)
         * If there are any active process wide timers (POSIX 1.b, itimers,
         * RLIMIT_CPU) cputimer must be running.
         */
-       if (tsk->signal->cputimer.running)
+       if (READ_ONCE(tsk->signal->cputimer.running))
                check_process_timers(tsk, &firing);
 
        /*