Commit 20d1c86a authored by Peter Zijlstra's avatar Peter Zijlstra Committed by Ingo Molnar

sched/clock, x86: Rewrite cyc2ns() to avoid the need to disable IRQs

Use a ring-buffer like multi-version object structure which allows
always having a coherent object; we use this to avoid having to
disable IRQs while reading sched_clock() and avoids a problem when
getting an NMI while changing the cyc2ns data.

                        MAINLINE   PRE        POST

    sched_clock_stable: 1          1          1
    (cold) sched_clock: 329841     331312     257223
    (cold) local_clock: 301773     310296     309889
    (warm) sched_clock: 38375      38247      25280
    (warm) local_clock: 100371     102713     85268
    (warm) rdtsc:       27340      27289      24247
    sched_clock_stable: 0          0          0
    (cold) sched_clock: 382634     372706     301224
    (cold) local_clock: 396890     399275     399870
    (warm) sched_clock: 38194      38124      25630
    (warm) local_clock: 143452     148698     129629
    (warm) rdtsc:       27345      27365      24307
Signed-off-by: default avatarPeter Zijlstra <peterz@infradead.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Link: http://lkml.kernel.org/n/tip-s567in1e5ekq2nlyhn8f987r@git.kernel.orgSigned-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent 57c67da2
...@@ -13,7 +13,26 @@ extern int recalibrate_cpu_khz(void); ...@@ -13,7 +13,26 @@ extern int recalibrate_cpu_khz(void);
extern int no_timer_check; extern int no_timer_check;
DECLARE_PER_CPU(unsigned long, cyc2ns); /*
DECLARE_PER_CPU(unsigned long long, cyc2ns_offset); * We use the full linear equation: f(x) = a + b*x, in order to allow
* a continuous function in the face of dynamic freq changes.
*
* Continuity means that when our frequency changes our slope (b); we want to
* ensure that: f(t) == f'(t), which gives: a + b*t == a' + b'*t.
*
* Without an offset (a) the above would not be possible.
*
* See the comment near cycles_2_ns() for details on how we compute (b).
*/
struct cyc2ns_data {
u32 cyc2ns_mul;
u32 cyc2ns_shift;
u64 cyc2ns_offset;
u32 __count;
/* u32 hole */
}; /* 24 bytes -- do not grow */
extern struct cyc2ns_data *cyc2ns_read_begin(void);
extern void cyc2ns_read_end(struct cyc2ns_data *);
#endif /* _ASM_X86_TIMER_H */ #endif /* _ASM_X86_TIMER_H */
...@@ -1883,6 +1883,8 @@ static struct pmu pmu = { ...@@ -1883,6 +1883,8 @@ static struct pmu pmu = {
void arch_perf_update_userpage(struct perf_event_mmap_page *userpg, u64 now) void arch_perf_update_userpage(struct perf_event_mmap_page *userpg, u64 now)
{ {
struct cyc2ns_data *data;
userpg->cap_user_time = 0; userpg->cap_user_time = 0;
userpg->cap_user_time_zero = 0; userpg->cap_user_time_zero = 0;
userpg->cap_user_rdpmc = x86_pmu.attr_rdpmc; userpg->cap_user_rdpmc = x86_pmu.attr_rdpmc;
...@@ -1891,13 +1893,17 @@ void arch_perf_update_userpage(struct perf_event_mmap_page *userpg, u64 now) ...@@ -1891,13 +1893,17 @@ void arch_perf_update_userpage(struct perf_event_mmap_page *userpg, u64 now)
if (!sched_clock_stable) if (!sched_clock_stable)
return; return;
data = cyc2ns_read_begin();
userpg->cap_user_time = 1; userpg->cap_user_time = 1;
userpg->time_mult = this_cpu_read(cyc2ns); userpg->time_mult = data->cyc2ns_mul;
userpg->time_shift = CYC2NS_SCALE_FACTOR; userpg->time_shift = data->cyc2ns_shift;
userpg->time_offset = this_cpu_read(cyc2ns_offset) - now; userpg->time_offset = data->cyc2ns_offset - now;
userpg->cap_user_time_zero = 1; userpg->cap_user_time_zero = 1;
userpg->time_zero = this_cpu_read(cyc2ns_offset); userpg->time_zero = data->cyc2ns_offset;
cyc2ns_read_end(data);
} }
/* /*
......
...@@ -39,7 +39,119 @@ static int __read_mostly tsc_disabled = -1; ...@@ -39,7 +39,119 @@ static int __read_mostly tsc_disabled = -1;
int tsc_clocksource_reliable; int tsc_clocksource_reliable;
/* Accelerators for sched_clock() /*
* Use a ring-buffer like data structure, where a writer advances the head by
* writing a new data entry and a reader advances the tail when it observes a
* new entry.
*
* Writers are made to wait on readers until there's space to write a new
* entry.
*
* This means that we can always use an {offset, mul} pair to compute a ns
* value that is 'roughly' in the right direction, even if we're writing a new
* {offset, mul} pair during the clock read.
*
* The down-side is that we can no longer guarantee strict monotonicity anymore
* (assuming the TSC was that to begin with), because while we compute the
* intersection point of the two clock slopes and make sure the time is
* continuous at the point of switching; we can no longer guarantee a reader is
* strictly before or after the switch point.
*
* It does mean a reader no longer needs to disable IRQs in order to avoid
* CPU-Freq updates messing with his times, and similarly an NMI reader will
* no longer run the risk of hitting half-written state.
*/
struct cyc2ns {
struct cyc2ns_data data[2]; /* 0 + 2*24 = 48 */
struct cyc2ns_data *head; /* 48 + 8 = 56 */
struct cyc2ns_data *tail; /* 56 + 8 = 64 */
}; /* exactly fits one cacheline */
static DEFINE_PER_CPU_ALIGNED(struct cyc2ns, cyc2ns);
struct cyc2ns_data *cyc2ns_read_begin(void)
{
struct cyc2ns_data *head;
preempt_disable();
head = this_cpu_read(cyc2ns.head);
/*
* Ensure we observe the entry when we observe the pointer to it.
* matches the wmb from cyc2ns_write_end().
*/
smp_read_barrier_depends();
head->__count++;
barrier();
return head;
}
void cyc2ns_read_end(struct cyc2ns_data *head)
{
barrier();
/*
* If we're the outer most nested read; update the tail pointer
* when we're done. This notifies possible pending writers
* that we've observed the head pointer and that the other
* entry is now free.
*/
if (!--head->__count) {
/*
* x86-TSO does not reorder writes with older reads;
* therefore once this write becomes visible to another
* cpu, we must be finished reading the cyc2ns_data.
*
* matches with cyc2ns_write_begin().
*/
this_cpu_write(cyc2ns.tail, head);
}
preempt_enable();
}
/*
* Begin writing a new @data entry for @cpu.
*
* Assumes some sort of write side lock; currently 'provided' by the assumption
* that cpufreq will call its notifiers sequentially.
*/
static struct cyc2ns_data *cyc2ns_write_begin(int cpu)
{
struct cyc2ns *c2n = &per_cpu(cyc2ns, cpu);
struct cyc2ns_data *data = c2n->data;
if (data == c2n->head)
data++;
/* XXX send an IPI to @cpu in order to guarantee a read? */
/*
* When we observe the tail write from cyc2ns_read_end(),
* the cpu must be done with that entry and its safe
* to start writing to it.
*/
while (c2n->tail == data)
cpu_relax();
return data;
}
static void cyc2ns_write_end(int cpu, struct cyc2ns_data *data)
{
struct cyc2ns *c2n = &per_cpu(cyc2ns, cpu);
/*
* Ensure the @data writes are visible before we publish the
* entry. Matches the data-depencency in cyc2ns_read_begin().
*/
smp_wmb();
ACCESS_ONCE(c2n->head) = data;
}
/*
* Accelerators for sched_clock()
* convert from cycles(64bits) => nanoseconds (64bits) * convert from cycles(64bits) => nanoseconds (64bits)
* basic equation: * basic equation:
* ns = cycles / (freq / ns_per_sec) * ns = cycles / (freq / ns_per_sec)
...@@ -61,49 +173,106 @@ int tsc_clocksource_reliable; ...@@ -61,49 +173,106 @@ int tsc_clocksource_reliable;
* -johnstul@us.ibm.com "math is hard, lets go shopping!" * -johnstul@us.ibm.com "math is hard, lets go shopping!"
*/ */
DEFINE_PER_CPU(unsigned long, cyc2ns);
DEFINE_PER_CPU(unsigned long long, cyc2ns_offset);
#define CYC2NS_SCALE_FACTOR 10 /* 2^10, carefully chosen */ #define CYC2NS_SCALE_FACTOR 10 /* 2^10, carefully chosen */
static void cyc2ns_data_init(struct cyc2ns_data *data)
{
data->cyc2ns_mul = 1U << CYC2NS_SCALE_FACTOR;
data->cyc2ns_shift = CYC2NS_SCALE_FACTOR;
data->cyc2ns_offset = 0;
data->__count = 0;
}
static void cyc2ns_init(int cpu)
{
struct cyc2ns *c2n = &per_cpu(cyc2ns, cpu);
cyc2ns_data_init(&c2n->data[0]);
cyc2ns_data_init(&c2n->data[1]);
c2n->head = c2n->data;
c2n->tail = c2n->data;
}
static inline unsigned long long cycles_2_ns(unsigned long long cyc) static inline unsigned long long cycles_2_ns(unsigned long long cyc)
{ {
unsigned long long ns = this_cpu_read(cyc2ns_offset); struct cyc2ns_data *data, *tail;
ns += mul_u64_u32_shr(cyc, this_cpu_read(cyc2ns), CYC2NS_SCALE_FACTOR); unsigned long long ns;
/*
* See cyc2ns_read_*() for details; replicated in order to avoid
* an extra few instructions that came with the abstraction.
* Notable, it allows us to only do the __count and tail update
* dance when its actually needed.
*/
preempt_disable();
data = this_cpu_read(cyc2ns.head);
tail = this_cpu_read(cyc2ns.tail);
if (likely(data == tail)) {
ns = data->cyc2ns_offset;
ns += mul_u64_u32_shr(cyc, data->cyc2ns_mul, CYC2NS_SCALE_FACTOR);
} else {
data->__count++;
barrier();
ns = data->cyc2ns_offset;
ns += mul_u64_u32_shr(cyc, data->cyc2ns_mul, CYC2NS_SCALE_FACTOR);
barrier();
if (!--data->__count)
this_cpu_write(cyc2ns.tail, data);
}
preempt_enable();
return ns; return ns;
} }
/* XXX surely we already have this someplace in the kernel?! */
#define DIV_ROUND(n, d) (((n) + ((d) / 2)) / (d))
static void set_cyc2ns_scale(unsigned long cpu_khz, int cpu) static void set_cyc2ns_scale(unsigned long cpu_khz, int cpu)
{ {
unsigned long long tsc_now, ns_now, *offset; unsigned long long tsc_now, ns_now;
unsigned long flags, *scale; struct cyc2ns_data *data;
unsigned long flags;
local_irq_save(flags); local_irq_save(flags);
sched_clock_idle_sleep_event(); sched_clock_idle_sleep_event();
scale = &per_cpu(cyc2ns, cpu); if (!cpu_khz)
offset = &per_cpu(cyc2ns_offset, cpu); goto done;
data = cyc2ns_write_begin(cpu);
rdtscll(tsc_now); rdtscll(tsc_now);
ns_now = cycles_2_ns(tsc_now); ns_now = cycles_2_ns(tsc_now);
if (cpu_khz) { /*
*scale = ((NSEC_PER_MSEC << CYC2NS_SCALE_FACTOR) + * Compute a new multiplier as per the above comment and ensure our
cpu_khz / 2) / cpu_khz; * time function is continuous; see the comment near struct
*offset = ns_now - mult_frac(tsc_now, *scale, * cyc2ns_data.
(1UL << CYC2NS_SCALE_FACTOR)); */
} data->cyc2ns_mul = DIV_ROUND(NSEC_PER_MSEC << CYC2NS_SCALE_FACTOR, cpu_khz);
data->cyc2ns_shift = CYC2NS_SCALE_FACTOR;
data->cyc2ns_offset = ns_now -
mul_u64_u32_shr(tsc_now, data->cyc2ns_mul, CYC2NS_SCALE_FACTOR);
cyc2ns_write_end(cpu, data);
done:
sched_clock_idle_wakeup_event(0); sched_clock_idle_wakeup_event(0);
local_irq_restore(flags); local_irq_restore(flags);
} }
/* /*
* Scheduler clock - returns current time in nanosec units. * Scheduler clock - returns current time in nanosec units.
*/ */
u64 native_sched_clock(void) u64 native_sched_clock(void)
{ {
u64 this_offset; u64 tsc_now;
/* /*
* Fall back to jiffies if there's no TSC available: * Fall back to jiffies if there's no TSC available:
...@@ -119,10 +288,10 @@ u64 native_sched_clock(void) ...@@ -119,10 +288,10 @@ u64 native_sched_clock(void)
} }
/* read the Time Stamp Counter: */ /* read the Time Stamp Counter: */
rdtscll(this_offset); rdtscll(tsc_now);
/* return the value in ns */ /* return the value in ns */
return cycles_2_ns(this_offset); return cycles_2_ns(tsc_now);
} }
/* We need to define a real function for sched_clock, to override the /* We need to define a real function for sched_clock, to override the
...@@ -678,11 +847,21 @@ void tsc_restore_sched_clock_state(void) ...@@ -678,11 +847,21 @@ void tsc_restore_sched_clock_state(void)
local_irq_save(flags); local_irq_save(flags);
__this_cpu_write(cyc2ns_offset, 0); /*
* We're comming out of suspend, there's no concurrency yet; don't
* bother being nice about the RCU stuff, just write to both
* data fields.
*/
this_cpu_write(cyc2ns.data[0].cyc2ns_offset, 0);
this_cpu_write(cyc2ns.data[1].cyc2ns_offset, 0);
offset = cyc2ns_suspend - sched_clock(); offset = cyc2ns_suspend - sched_clock();
for_each_possible_cpu(cpu) for_each_possible_cpu(cpu) {
per_cpu(cyc2ns_offset, cpu) = offset; per_cpu(cyc2ns.data[0].cyc2ns_offset, cpu) = offset;
per_cpu(cyc2ns.data[1].cyc2ns_offset, cpu) = offset;
}
local_irq_restore(flags); local_irq_restore(flags);
} }
...@@ -1005,8 +1184,10 @@ void __init tsc_init(void) ...@@ -1005,8 +1184,10 @@ void __init tsc_init(void)
* speed as the bootup CPU. (cpufreq notifiers will fix this * speed as the bootup CPU. (cpufreq notifiers will fix this
* up if their speed diverges) * up if their speed diverges)
*/ */
for_each_possible_cpu(cpu) for_each_possible_cpu(cpu) {
cyc2ns_init(cpu);
set_cyc2ns_scale(cpu_khz, cpu); set_cyc2ns_scale(cpu_khz, cpu);
}
if (tsc_disabled > 0) if (tsc_disabled > 0)
return; return;
......
...@@ -433,15 +433,49 @@ static void reset_with_ipi(struct pnmask *distribution, struct bau_control *bcp) ...@@ -433,15 +433,49 @@ static void reset_with_ipi(struct pnmask *distribution, struct bau_control *bcp)
return; return;
} }
static inline unsigned long cycles_2_us(unsigned long long cyc) /*
* Not to be confused with cycles_2_ns() from tsc.c; this gives a relative
* number, not an absolute. It converts a duration in cycles to a duration in
* ns.
*/
static inline unsigned long long cycles_2_ns(unsigned long long cyc)
{ {
struct cyc2ns_data *data = cyc2ns_read_begin();
unsigned long long ns; unsigned long long ns;
unsigned long us;
int cpu = smp_processor_id();
ns = (cyc * per_cpu(cyc2ns, cpu)) >> CYC2NS_SCALE_FACTOR; ns = mul_u64_u32_shr(cyc, data->cyc2ns_mul, data->cyc2ns_shift);
us = ns / 1000;
return us; cyc2ns_read_end(data);
return ns;
}
/*
* The reverse of the above; converts a duration in ns to a duration in cycles.
*/
static inline unsigned long long ns_2_cycles(unsigned long long ns)
{
struct cyc2ns_data *data = cyc2ns_read_begin();
unsigned long long cyc;
cyc = (ns << data->cyc2ns_shift) / data->cyc2ns_mul;
cyc2ns_read_end(data);
return cyc;
}
static inline unsigned long cycles_2_us(unsigned long long cyc)
{
return cycles_2_ns(cyc) / NSEC_PER_USEC;
}
static inline cycles_t sec_2_cycles(unsigned long sec)
{
return ns_2_cycles(sec * NSEC_PER_SEC);
}
static inline unsigned long long usec_2_cycles(unsigned long usec)
{
return ns_2_cycles(usec * NSEC_PER_USEC);
} }
/* /*
...@@ -668,16 +702,6 @@ static int wait_completion(struct bau_desc *bau_desc, ...@@ -668,16 +702,6 @@ static int wait_completion(struct bau_desc *bau_desc,
bcp, try); bcp, try);
} }
static inline cycles_t sec_2_cycles(unsigned long sec)
{
unsigned long ns;
cycles_t cyc;
ns = sec * 1000000000;
cyc = (ns << CYC2NS_SCALE_FACTOR)/(per_cpu(cyc2ns, smp_processor_id()));
return cyc;
}
/* /*
* Our retries are blocked by all destination sw ack resources being * Our retries are blocked by all destination sw ack resources being
* in use, and a timeout is pending. In that case hardware immediately * in use, and a timeout is pending. In that case hardware immediately
...@@ -1327,16 +1351,6 @@ static void ptc_seq_stop(struct seq_file *file, void *data) ...@@ -1327,16 +1351,6 @@ static void ptc_seq_stop(struct seq_file *file, void *data)
{ {
} }
static inline unsigned long long usec_2_cycles(unsigned long microsec)
{
unsigned long ns;
unsigned long long cyc;
ns = microsec * 1000;
cyc = (ns << CYC2NS_SCALE_FACTOR)/(per_cpu(cyc2ns, smp_processor_id()));
return cyc;
}
/* /*
* Display the statistics thru /proc/sgi_uv/ptc_statistics * Display the statistics thru /proc/sgi_uv/ptc_statistics
* 'data' points to the cpu number * 'data' points to the cpu number
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment