perf tools: Add documentation for topdown metrics
Add some documentation how to use the topdown metrics in ring 3. Signed-off-by: Andi Kleen <ak@linux.intel.com> Co-developed-by: Kan Liang <kan.liang@linux.intel.com> Acked-by: Jiri Olsa <jolsa@redhat.com> Acked-by: Namhyung Kim <namhyung@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Stephane Eranian <eranian@google.com> Link: http://lore.kernel.org/lkml/20200911144808.27603-5-kan.liang@linux.intel.com Signed-off-by: Kan Liang <kan.liang@linux.intel.com> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
55c36a9fc2
commit
328781df86
|
@ -0,0 +1,256 @@
|
|||
Using TopDown metrics in user space
|
||||
-----------------------------------
|
||||
|
||||
Intel CPUs (since Sandy Bridge and Silvermont) support a TopDown
|
||||
methology to break down CPU pipeline execution into 4 bottlenecks:
|
||||
frontend bound, backend bound, bad speculation, retiring.
|
||||
|
||||
For more details on Topdown see [1][5]
|
||||
|
||||
Traditionally this was implemented by events in generic counters
|
||||
and specific formulas to compute the bottlenecks.
|
||||
|
||||
perf stat --topdown implements this.
|
||||
|
||||
Full Top Down includes more levels that can break down the
|
||||
bottlenecks further. This is not directly implemented in perf,
|
||||
but available in other tools that can run on top of perf,
|
||||
such as toplev[2] or vtune[3]
|
||||
|
||||
New Topdown features in Ice Lake
|
||||
===============================
|
||||
|
||||
With Ice Lake CPUs the TopDown metrics are directly available as
|
||||
fixed counters and do not require generic counters. This allows
|
||||
to collect TopDown always in addition to other events.
|
||||
|
||||
% perf stat -a --topdown -I1000
|
||||
# time retiring bad speculation frontend bound backend bound
|
||||
1.001281330 23.0% 15.3% 29.6% 32.1%
|
||||
2.003009005 5.0% 6.8% 46.6% 41.6%
|
||||
3.004646182 6.7% 6.7% 46.0% 40.6%
|
||||
4.006326375 5.0% 6.4% 47.6% 41.0%
|
||||
5.007991804 5.1% 6.3% 46.3% 42.3%
|
||||
6.009626773 6.2% 7.1% 47.3% 39.3%
|
||||
7.011296356 4.7% 6.7% 46.2% 42.4%
|
||||
8.012951831 4.7% 6.7% 47.5% 41.1%
|
||||
...
|
||||
|
||||
This also enables measuring TopDown per thread/process instead
|
||||
of only per core.
|
||||
|
||||
Using TopDown through RDPMC in applications on Ice Lake
|
||||
======================================================
|
||||
|
||||
For more fine grained measurements it can be useful to
|
||||
access the new directly from user space. This is more complicated,
|
||||
but drastically lowers overhead.
|
||||
|
||||
On Ice Lake, there is a new fixed counter 3: SLOTS, which reports
|
||||
"pipeline SLOTS" (cycles multiplied by core issue width) and a
|
||||
metric register that reports slots ratios for the different bottleneck
|
||||
categories.
|
||||
|
||||
The metrics counter is CPU model specific and is not available on older
|
||||
CPUs.
|
||||
|
||||
Example code
|
||||
============
|
||||
|
||||
Library functions to do the functionality described below
|
||||
is also available in libjevents [4]
|
||||
|
||||
The application opens a group with fixed counter 3 (SLOTS) and any
|
||||
metric event, and allow user programs to read the performance counters.
|
||||
|
||||
Fixed counter 3 is mapped to a pseudo event event=0x00, umask=04,
|
||||
so the perf_event_attr structure should be initialized with
|
||||
{ .config = 0x0400, .type = PERF_TYPE_RAW }
|
||||
The metric events are mapped to the pseudo event event=0x00, umask=0x8X.
|
||||
For example, the perf_event_attr structure can be initialized with
|
||||
{ .config = 0x8000, .type = PERF_TYPE_RAW } for Retiring metric event
|
||||
The Fixed counter 3 must be the leader of the group.
|
||||
|
||||
#include <linux/perf_event.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Provide own perf_event_open stub because glibc doesn't */
|
||||
__attribute__((weak))
|
||||
int perf_event_open(struct perf_event_attr *attr, pid_t pid,
|
||||
int cpu, int group_fd, unsigned long flags)
|
||||
{
|
||||
return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
|
||||
}
|
||||
|
||||
/* Open slots counter file descriptor for current task. */
|
||||
struct perf_event_attr slots = {
|
||||
.type = PERF_TYPE_RAW,
|
||||
.size = sizeof(struct perf_event_attr),
|
||||
.config = 0x400,
|
||||
.exclude_kernel = 1,
|
||||
};
|
||||
|
||||
int slots_fd = perf_event_open(&slots, 0, -1, -1, 0);
|
||||
if (slots_fd < 0)
|
||||
... error ...
|
||||
|
||||
/*
|
||||
* Open metrics event file descriptor for current task.
|
||||
* Set slots event as the leader of the group.
|
||||
*/
|
||||
struct perf_event_attr metrics = {
|
||||
.type = PERF_TYPE_RAW,
|
||||
.size = sizeof(struct perf_event_attr),
|
||||
.config = 0x8000,
|
||||
.exclude_kernel = 1,
|
||||
};
|
||||
|
||||
int metrics_fd = perf_event_open(&metrics, 0, -1, slots_fd, 0);
|
||||
if (metrics_fd < 0)
|
||||
... error ...
|
||||
|
||||
|
||||
The RDPMC instruction (or _rdpmc compiler intrinsic) can now be used
|
||||
to read slots and the topdown metrics at different points of the program:
|
||||
|
||||
#include <stdint.h>
|
||||
#include <x86intrin.h>
|
||||
|
||||
#define RDPMC_FIXED (1 << 30) /* return fixed counters */
|
||||
#define RDPMC_METRIC (1 << 29) /* return metric counters */
|
||||
|
||||
#define FIXED_COUNTER_SLOTS 3
|
||||
#define METRIC_COUNTER_TOPDOWN_L1 0
|
||||
|
||||
static inline uint64_t read_slots(void)
|
||||
{
|
||||
return _rdpmc(RDPMC_FIXED | FIXED_COUNTER_SLOTS);
|
||||
}
|
||||
|
||||
static inline uint64_t read_metrics(void)
|
||||
{
|
||||
return _rdpmc(RDPMC_METRIC | METRIC_COUNTER_TOPDOWN_L1);
|
||||
}
|
||||
|
||||
Then the program can be instrumented to read these metrics at different
|
||||
points.
|
||||
|
||||
It's not a good idea to do this with too short code regions,
|
||||
as the parallelism and overlap in the CPU program execution will
|
||||
cause too much measurement inaccuracy. For example instrumenting
|
||||
individual basic blocks is definitely too fine grained.
|
||||
|
||||
Decoding metrics values
|
||||
=======================
|
||||
|
||||
The value reported by read_metrics() contains four 8 bit fields
|
||||
that represent a scaled ratio that represent the Level 1 bottleneck.
|
||||
All four fields add up to 0xff (= 100%)
|
||||
|
||||
The binary ratios in the metric value can be converted to float ratios:
|
||||
|
||||
#define GET_METRIC(m, i) (((m) >> (i*8)) & 0xff)
|
||||
|
||||
#define TOPDOWN_RETIRING(val) ((float)GET_METRIC(val, 0) / 0xff)
|
||||
#define TOPDOWN_BAD_SPEC(val) ((float)GET_METRIC(val, 1) / 0xff)
|
||||
#define TOPDOWN_FE_BOUND(val) ((float)GET_METRIC(val, 2) / 0xff)
|
||||
#define TOPDOWN_BE_BOUND(val) ((float)GET_METRIC(val, 3) / 0xff)
|
||||
|
||||
and then converted to percent for printing.
|
||||
|
||||
The ratios in the metric accumulate for the time when the counter
|
||||
is enabled. For measuring programs it is often useful to measure
|
||||
specific sections. For this it is needed to deltas on metrics.
|
||||
|
||||
This can be done by scaling the metrics with the slots counter
|
||||
read at the same time.
|
||||
|
||||
Then it's possible to take deltas of these slots counts
|
||||
measured at different points, and determine the metrics
|
||||
for that time period.
|
||||
|
||||
slots_a = read_slots();
|
||||
metric_a = read_metrics();
|
||||
|
||||
... larger code region ...
|
||||
|
||||
slots_b = read_slots()
|
||||
metric_b = read_metrics()
|
||||
|
||||
# compute scaled metrics for measurement a
|
||||
retiring_slots_a = GET_METRIC(metric_a, 0) * slots_a
|
||||
bad_spec_slots_a = GET_METRIC(metric_a, 1) * slots_a
|
||||
fe_bound_slots_a = GET_METRIC(metric_a, 2) * slots_a
|
||||
be_bound_slots_a = GET_METRIC(metric_a, 3) * slots_a
|
||||
|
||||
# compute delta scaled metrics between b and a
|
||||
retiring_slots = GET_METRIC(metric_b, 0) * slots_b - retiring_slots_a
|
||||
bad_spec_slots = GET_METRIC(metric_b, 1) * slots_b - bad_spec_slots_a
|
||||
fe_bound_slots = GET_METRIC(metric_b, 2) * slots_b - fe_bound_slots_a
|
||||
be_bound_slots = GET_METRIC(metric_b, 3) * slots_b - be_bound_slots_a
|
||||
|
||||
Later the individual ratios for the measurement period can be recreated
|
||||
from these counts.
|
||||
|
||||
slots_delta = slots_b - slots_a
|
||||
retiring_ratio = (float)retiring_slots / slots_delta
|
||||
bad_spec_ratio = (float)bad_spec_slots / slots_delta
|
||||
fe_bound_ratio = (float)fe_bound_slots / slots_delta
|
||||
be_bound_ratio = (float)be_bound_slots / slota_delta
|
||||
|
||||
printf("Retiring %.2f%% Bad Speculation %.2f%% FE Bound %.2f%% BE Bound %.2f%%\n",
|
||||
retiring_ratio * 100.,
|
||||
bad_spec_ratio * 100.,
|
||||
fe_bound_ratio * 100.,
|
||||
be_bound_ratio * 100.);
|
||||
|
||||
Resetting metrics counters
|
||||
==========================
|
||||
|
||||
Since the individual metrics are only 8bit they lose precision for
|
||||
short regions over time because the number of cycles covered by each
|
||||
fraction bit shrinks. So the counters need to be reset regularly.
|
||||
|
||||
When using the kernel perf API the kernel resets on every read.
|
||||
So as long as the reading is at reasonable intervals (every few
|
||||
seconds) the precision is good.
|
||||
|
||||
When using perf stat it is recommended to always use the -I option,
|
||||
with no longer interval than a few seconds
|
||||
|
||||
perf stat -I 1000 --topdown ...
|
||||
|
||||
For user programs using RDPMC directly the counter can
|
||||
be reset explicitly using ioctl:
|
||||
|
||||
ioctl(perf_fd, PERF_EVENT_IOC_RESET, 0);
|
||||
|
||||
This "opens" a new measurement period.
|
||||
|
||||
A program using RDPMC for TopDown should schedule such a reset
|
||||
regularly, as in every few seconds.
|
||||
|
||||
Limits on Ice Lake
|
||||
==================
|
||||
|
||||
Four pseudo TopDown metric events are exposed for the end-users,
|
||||
topdown-retiring, topdown-bad-spec, topdown-fe-bound and topdown-be-bound.
|
||||
They can be used to collect the TopDown value under the following
|
||||
rules:
|
||||
- All the TopDown metric events must be in a group with the SLOTS event.
|
||||
- The SLOTS event must be the leader of the group.
|
||||
- The PERF_FORMAT_GROUP flag must be applied for each TopDown metric
|
||||
events
|
||||
|
||||
The SLOTS event and the TopDown metric events can be counting members of
|
||||
a sampling read group. Since the SLOTS event must be the leader of a TopDown
|
||||
group, the second event of the group is the sampling event.
|
||||
For example, perf record -e '{slots, $sampling_event, topdown-retiring}:S'
|
||||
|
||||
|
||||
[1] https://software.intel.com/en-us/top-down-microarchitecture-analysis-method-win
|
||||
[2] https://github.com/andikleen/pmu-tools/wiki/toplev-manual
|
||||
[3] https://software.intel.com/en-us/intel-vtune-amplifier-xe
|
||||
[4] https://github.com/andikleen/pmu-tools/tree/master/jevents
|
||||
[5] https://sites.google.com/site/analysismethods/yasin-pubs
|
Loading…
Reference in New Issue