mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-23 11:13:28 +01:00
df37c4f17f
Summary: As noted in documentation, different repetition modes have different trade-offs: > .. option:: -repetition-mode=[duplicate|loop] > > Specify the repetition mode. `duplicate` will create a large, straight line > basic block with `num-repetitions` copies of the snippet. `loop` will wrap > the snippet in a loop which will be run `num-repetitions` times. The `loop` > mode tends to better hide the effects of the CPU frontend on architectures > that cache decoded instructions, but consumes a register for counting > iterations. Indeed. Example: >>! In D74156#1873657, @lebedev.ri wrote: > At least for `CMOV`, i'm seeing wildly different results > | | Latency | RThroughput | > | duplicate | 1 | 0.8 | > | loop | 2 | 0.6 | > where latency=1 seems correct, and i'd expect the througput to be close to 1/2 (since there are two execution units). This isn't great for analysis, at least for schedule model development. As discussed in excruciating detail in >>! In D74156#1924514, @gchatelet wrote: >>>! In D74156#1920632, @lebedev.ri wrote: >> ... did that explanation of the question i'm having made any sense? > > Thx for digging in the conversation ! > Ok it makes more sense now. > > I discussed it a bit with @courbet: > - We want the analysis tool to stay simple so we'd rather not make it knowledgeable of the repetition mode. > - We'd like to still be able to select either repetition mode to dig into special cases > > So we could add a third `min` repetition mode that would run both and take the minimum. It could be the default option. > Would you have some time to look what it would take to add this third mode? there appears to be an agreement that it is indeed sub-par, and that we should provide an optional, measurement (not analysis!) -time way to rectify the situation. However, the solutions isn't entirely straight-forward. We can just add an actual 'multiplexer' `MinSnippetRepetitor`, because if we just concatenate snippets produced by `DuplicateSnippetRepetitor` and `LoopSnippetRepetitor` and run+measure that, the measurement will naturally be different from what we'd get by running+measuring them separately and taking the min. ([[ https://www.wolframalpha.com/input/?i=%28x%2By%29%2F2+%21%3D+min%28x%2C+y%29 | `time(D+L)/2 != min(time(D), time(L))` ]]) Also, it seems best to me to have a single snippet instead of generating a snippet per repetition mode, since the only difference here is that the loop repetition mode reserves one register for loop counter. As far as i can tell, we can either teach `BenchmarkRunner::runConfiguration()` to produce a single report given multiple repetitors (as in the patch), or do that one layer higher - don't modify `BenchmarkRunner::runConfiguration()`, produce multiple reports, don't actually print each one, but aggregate them somehow and only print the final one. Initially i've gone ahead with the latter approach, but it didn't look like a natural fit; the former (as in the diff) does seem like a better fit to me. There's also a question of the test coverage. It sure currently does work here: ``` $ ./bin/llvm-exegesis --opcode-name=CMOV64rr --mode=inverse_throughput --repetition-mode=duplicate Check generated assembly with: /usr/bin/objdump -d /tmp/snippet-8fb949.o --- mode: inverse_throughput key: instructions: - 'CMOV64rr RAX RAX R11 i_0x0' - 'CMOV64rr RBP RBP R15 i_0x0' - 'CMOV64rr RBX RBX RBX i_0x0' - 'CMOV64rr RCX RCX RBX i_0x0' - 'CMOV64rr RDI RDI R10 i_0x0' - 'CMOV64rr RDX RDX RAX i_0x0' - 'CMOV64rr RSI RSI RAX i_0x0' - 'CMOV64rr R8 R8 R8 i_0x0' - 'CMOV64rr R9 R9 RDX i_0x0' - 'CMOV64rr R10 R10 RBX i_0x0' - 'CMOV64rr R11 R11 R14 i_0x0' - 'CMOV64rr R12 R12 R9 i_0x0' - 'CMOV64rr R13 R13 R12 i_0x0' - 'CMOV64rr R14 R14 R15 i_0x0' - 'CMOV64rr R15 R15 R13 i_0x0' config: '' register_initial_values: - 'RAX=0x0' - 'R11=0x0' - 'EFLAGS=0x0' - 'RBP=0x0' - 'R15=0x0' - 'RBX=0x0' - 'RCX=0x0' - 'RDI=0x0' - 'R10=0x0' - 'RDX=0x0' - 'RSI=0x0' - 'R8=0x0' - 'R9=0x0' - 'R14=0x0' - 'R12=0x0' - 'R13=0x0' cpu_name: bdver2 llvm_triple: x86_64-unknown-linux-gnu num_repetitions: 10000 measurements: - { key: inverse_throughput, value: 0.819, per_snippet_value: 12.285 } error: '' info: instruction has tied variables, using static renaming. assembled_snippet: 5541574156415541545348B8000000000000000049BB00000000000000004883EC08C7042400000000C7442404000000009D48BD000000000000000049BF000000000000000048BB000000000000000048B9000000000000000048BF000000000000000049BA000000000000000048BA000000000000000048BE000000000000000049B8000000000000000049B9000000000000000049BE000000000000000049BC000000000000000049BD0000000000000000490F40C3490F40EF480F40DB480F40CB490F40FA480F40D0480F40F04D0F40C04C0F40CA4C0F40D34D0F40DE4D0F40E14D0F40EC4D0F40F74D0F40FD490F40C35B415C415D415E415F5DC3 ... $ ./bin/llvm-exegesis --opcode-name=CMOV64rr --mode=inverse_throughput --repetition-mode=loop Check generated assembly with: /usr/bin/objdump -d /tmp/snippet-051eb3.o --- mode: inverse_throughput key: instructions: - 'CMOV64rr RAX RAX R11 i_0x0' - 'CMOV64rr RBP RBP RSI i_0x0' - 'CMOV64rr RBX RBX R9 i_0x0' - 'CMOV64rr RCX RCX RSI i_0x0' - 'CMOV64rr RDI RDI RBP i_0x0' - 'CMOV64rr RDX RDX R9 i_0x0' - 'CMOV64rr RSI RSI RDI i_0x0' - 'CMOV64rr R9 R9 R12 i_0x0' - 'CMOV64rr R10 R10 R11 i_0x0' - 'CMOV64rr R11 R11 R9 i_0x0' - 'CMOV64rr R12 R12 RBP i_0x0' - 'CMOV64rr R13 R13 RSI i_0x0' - 'CMOV64rr R14 R14 R14 i_0x0' - 'CMOV64rr R15 R15 R10 i_0x0' config: '' register_initial_values: - 'RAX=0x0' - 'R11=0x0' - 'EFLAGS=0x0' - 'RBP=0x0' - 'RSI=0x0' - 'RBX=0x0' - 'R9=0x0' - 'RCX=0x0' - 'RDI=0x0' - 'RDX=0x0' - 'R12=0x0' - 'R10=0x0' - 'R13=0x0' - 'R14=0x0' - 'R15=0x0' cpu_name: bdver2 llvm_triple: x86_64-unknown-linux-gnu num_repetitions: 10000 measurements: - { key: inverse_throughput, value: 0.6083, per_snippet_value: 8.5162 } error: '' info: instruction has tied variables, using static renaming. assembled_snippet: 5541574156415541545348B8000000000000000049BB00000000000000004883EC08C7042400000000C7442404000000009D48BD000000000000000048BE000000000000000048BB000000000000000049B9000000000000000048B9000000000000000048BF000000000000000048BA000000000000000049BC000000000000000049BA000000000000000049BD000000000000000049BE000000000000000049BF000000000000000049B80200000000000000490F40C3480F40EE490F40D9480F40CE480F40FD490F40D1480F40F74D0F40CC4D0F40D34D0F40D94C0F40E54C0F40EE4D0F40F64D0F40FA4983C0FF75C25B415C415D415E415F5DC3 ... $ ./bin/llvm-exegesis --opcode-name=CMOV64rr --mode=inverse_throughput --repetition-mode=min Check generated assembly with: /usr/bin/objdump -d /tmp/snippet-c7a47d.o Check generated assembly with: /usr/bin/objdump -d /tmp/snippet-2581f1.o --- mode: inverse_throughput key: instructions: - 'CMOV64rr RAX RAX R11 i_0x0' - 'CMOV64rr RBP RBP R10 i_0x0' - 'CMOV64rr RBX RBX R10 i_0x0' - 'CMOV64rr RCX RCX RDX i_0x0' - 'CMOV64rr RDI RDI RAX i_0x0' - 'CMOV64rr RDX RDX R9 i_0x0' - 'CMOV64rr RSI RSI RAX i_0x0' - 'CMOV64rr R9 R9 RBX i_0x0' - 'CMOV64rr R10 R10 R12 i_0x0' - 'CMOV64rr R11 R11 RDI i_0x0' - 'CMOV64rr R12 R12 RDI i_0x0' - 'CMOV64rr R13 R13 RDI i_0x0' - 'CMOV64rr R14 R14 R9 i_0x0' - 'CMOV64rr R15 R15 RBP i_0x0' config: '' register_initial_values: - 'RAX=0x0' - 'R11=0x0' - 'EFLAGS=0x0' - 'RBP=0x0' - 'R10=0x0' - 'RBX=0x0' - 'RCX=0x0' - 'RDX=0x0' - 'RDI=0x0' - 'R9=0x0' - 'RSI=0x0' - 'R12=0x0' - 'R13=0x0' - 'R14=0x0' - 'R15=0x0' cpu_name: bdver2 llvm_triple: x86_64-unknown-linux-gnu num_repetitions: 10000 measurements: - { key: inverse_throughput, value: 0.6073, per_snippet_value: 8.5022 } error: '' info: instruction has tied variables, using static renaming. assembled_snippet: 5541574156415541545348B8000000000000000049BB00000000000000004883EC08C7042400000000C7442404000000009D48BD000000000000000049BA000000000000000048BB000000000000000048B9000000000000000048BA000000000000000048BF000000000000000049B9000000000000000048BE000000000000000049BC000000000000000049BD000000000000000049BE000000000000000049BF0000000000000000490F40C3490F40EA490F40DA480F40CA480F40F8490F40D1480F40F04C0F40CB4D0F40D44C0F40DF4C0F40E74C0F40EF4D0F40F14C0F40FD490F40C3490F40EA5B415C415D415E415F5DC35541574156415541545348B8000000000000000049BB00000000000000004883EC08C7042400000000C7442404000000009D48BD000000000000000049BA000000000000000048BB000000000000000048B9000000000000000048BA000000000000000048BF000000000000000049B9000000000000000048BE000000000000000049BC000000000000000049BD000000000000000049BE000000000000000049BF000000000000000049B80200000000000000490F40C3490F40EA490F40DA480F40CA480F40F8490F40D1480F40F04C0F40CB4D0F40D44C0F40DF4C0F40E74C0F40EF4D0F40F14C0F40FD4983C0FF75C25B415C415D415E415F5DC3 ... ``` but i open to suggestions as to how test that. I also have gone with the suggestion to default to this new mode. This was irking me for some time, so i'm happy to finally see progress here. Looking forward to feedback. Reviewers: courbet, gchatelet Reviewed By: courbet, gchatelet Subscribers: mstojanovic, RKSimon, llvm-commits, courbet, gchatelet Tags: #llvm Differential Revision: https://reviews.llvm.org/D76921
291 lines
10 KiB
ReStructuredText
291 lines
10 KiB
ReStructuredText
llvm-exegesis - LLVM Machine Instruction Benchmark
|
|
==================================================
|
|
|
|
.. program:: llvm-exegesis
|
|
|
|
SYNOPSIS
|
|
--------
|
|
|
|
:program:`llvm-exegesis` [*options*]
|
|
|
|
DESCRIPTION
|
|
-----------
|
|
|
|
:program:`llvm-exegesis` is a benchmarking tool that uses information available
|
|
in LLVM to measure host machine instruction characteristics like latency,
|
|
throughput, or port decomposition.
|
|
|
|
Given an LLVM opcode name and a benchmarking mode, :program:`llvm-exegesis`
|
|
generates a code snippet that makes execution as serial (resp. as parallel) as
|
|
possible so that we can measure the latency (resp. inverse throughput/uop decomposition)
|
|
of the instruction.
|
|
The code snippet is jitted and executed on the host subtarget. The time taken
|
|
(resp. resource usage) is measured using hardware performance counters. The
|
|
result is printed out as YAML to the standard output.
|
|
|
|
The main goal of this tool is to automatically (in)validate the LLVM's TableDef
|
|
scheduling models. To that end, we also provide analysis of the results.
|
|
|
|
:program:`llvm-exegesis` can also benchmark arbitrary user-provided code
|
|
snippets.
|
|
|
|
EXAMPLE 1: benchmarking instructions
|
|
------------------------------------
|
|
|
|
Assume you have an X86-64 machine. To measure the latency of a single
|
|
instruction, run:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ llvm-exegesis -mode=latency -opcode-name=ADD64rr
|
|
|
|
Measuring the uop decomposition or inverse throughput of an instruction works similarly:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ llvm-exegesis -mode=uops -opcode-name=ADD64rr
|
|
$ llvm-exegesis -mode=inverse_throughput -opcode-name=ADD64rr
|
|
|
|
|
|
The output is a YAML document (the default is to write to stdout, but you can
|
|
redirect the output to a file using `-benchmarks-file`):
|
|
|
|
.. code-block:: none
|
|
|
|
---
|
|
key:
|
|
opcode_name: ADD64rr
|
|
mode: latency
|
|
config: ''
|
|
cpu_name: haswell
|
|
llvm_triple: x86_64-unknown-linux-gnu
|
|
num_repetitions: 10000
|
|
measurements:
|
|
- { key: latency, value: 1.0058, debug_string: '' }
|
|
error: ''
|
|
info: 'explicit self cycles, selecting one aliasing configuration.
|
|
Snippet:
|
|
ADD64rr R8, R8, R10
|
|
'
|
|
...
|
|
|
|
To measure the latency of all instructions for the host architecture, run:
|
|
|
|
.. code-block:: bash
|
|
|
|
#!/bin/bash
|
|
readonly INSTRUCTIONS=$(($(grep INSTRUCTION_LIST_END build/lib/Target/X86/X86GenInstrInfo.inc | cut -f2 -d=) - 1))
|
|
for INSTRUCTION in $(seq 1 ${INSTRUCTIONS});
|
|
do
|
|
./build/bin/llvm-exegesis -mode=latency -opcode-index=${INSTRUCTION} | sed -n '/---/,$p'
|
|
done
|
|
|
|
FIXME: Provide an :program:`llvm-exegesis` option to test all instructions.
|
|
|
|
|
|
EXAMPLE 2: benchmarking a custom code snippet
|
|
---------------------------------------------
|
|
|
|
To measure the latency/uops of a custom piece of code, you can specify the
|
|
`snippets-file` option (`-` reads from standard input).
|
|
|
|
.. code-block:: bash
|
|
|
|
$ echo "vzeroupper" | llvm-exegesis -mode=uops -snippets-file=-
|
|
|
|
Real-life code snippets typically depend on registers or memory.
|
|
:program:`llvm-exegesis` checks the liveliness of registers (i.e. any register
|
|
use has a corresponding def or is a "live in"). If your code depends on the
|
|
value of some registers, you have two options:
|
|
|
|
- Mark the register as requiring a definition. :program:`llvm-exegesis` will
|
|
automatically assign a value to the register. This can be done using the
|
|
directive `LLVM-EXEGESIS-DEFREG <reg name> <hex_value>`, where `<hex_value>`
|
|
is a bit pattern used to fill `<reg_name>`. If `<hex_value>` is smaller than
|
|
the register width, it will be sign-extended.
|
|
- Mark the register as a "live in". :program:`llvm-exegesis` will benchmark
|
|
using whatever value was in this registers on entry. This can be done using
|
|
the directive `LLVM-EXEGESIS-LIVEIN <reg name>`.
|
|
|
|
For example, the following code snippet depends on the values of XMM1 (which
|
|
will be set by the tool) and the memory buffer passed in RDI (live in).
|
|
|
|
.. code-block:: none
|
|
|
|
# LLVM-EXEGESIS-LIVEIN RDI
|
|
# LLVM-EXEGESIS-DEFREG XMM1 42
|
|
vmulps (%rdi), %xmm1, %xmm2
|
|
vhaddps %xmm2, %xmm2, %xmm3
|
|
addq $0x10, %rdi
|
|
|
|
|
|
EXAMPLE 3: analysis
|
|
-------------------
|
|
|
|
Assuming you have a set of benchmarked instructions (either latency or uops) as
|
|
YAML in file `/tmp/benchmarks.yaml`, you can analyze the results using the
|
|
following command:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ llvm-exegesis -mode=analysis \
|
|
-benchmarks-file=/tmp/benchmarks.yaml \
|
|
-analysis-clusters-output-file=/tmp/clusters.csv \
|
|
-analysis-inconsistencies-output-file=/tmp/inconsistencies.html
|
|
|
|
This will group the instructions into clusters with the same performance
|
|
characteristics. The clusters will be written out to `/tmp/clusters.csv` in the
|
|
following format:
|
|
|
|
.. code-block:: none
|
|
|
|
cluster_id,opcode_name,config,sched_class
|
|
...
|
|
2,ADD32ri8_DB,,WriteALU,1.00
|
|
2,ADD32ri_DB,,WriteALU,1.01
|
|
2,ADD32rr,,WriteALU,1.01
|
|
2,ADD32rr_DB,,WriteALU,1.00
|
|
2,ADD32rr_REV,,WriteALU,1.00
|
|
2,ADD64i32,,WriteALU,1.01
|
|
2,ADD64ri32,,WriteALU,1.01
|
|
2,MOVSX64rr32,,BSWAP32r_BSWAP64r_MOVSX64rr32,1.00
|
|
2,VPADDQYrr,,VPADDBYrr_VPADDDYrr_VPADDQYrr_VPADDWYrr_VPSUBBYrr_VPSUBDYrr_VPSUBQYrr_VPSUBWYrr,1.02
|
|
2,VPSUBQYrr,,VPADDBYrr_VPADDDYrr_VPADDQYrr_VPADDWYrr_VPSUBBYrr_VPSUBDYrr_VPSUBQYrr_VPSUBWYrr,1.01
|
|
2,ADD64ri8,,WriteALU,1.00
|
|
2,SETBr,,WriteSETCC,1.01
|
|
...
|
|
|
|
:program:`llvm-exegesis` will also analyze the clusters to point out
|
|
inconsistencies in the scheduling information. The output is an html file. For
|
|
example, `/tmp/inconsistencies.html` will contain messages like the following :
|
|
|
|
.. image:: llvm-exegesis-analysis.png
|
|
:align: center
|
|
|
|
Note that the scheduling class names will be resolved only when
|
|
:program:`llvm-exegesis` is compiled in debug mode, else only the class id will
|
|
be shown. This does not invalidate any of the analysis results though.
|
|
|
|
OPTIONS
|
|
-------
|
|
|
|
.. option:: -help
|
|
|
|
Print a summary of command line options.
|
|
|
|
.. option:: -opcode-index=<LLVM opcode index>
|
|
|
|
Specify the opcode to measure, by index. Specifying `-1` will result
|
|
in measuring every existing opcode. See example 1 for details.
|
|
Either `opcode-index`, `opcode-name` or `snippets-file` must be set.
|
|
|
|
.. option:: -opcode-name=<opcode name 1>,<opcode name 2>,...
|
|
|
|
Specify the opcode to measure, by name. Several opcodes can be specified as
|
|
a comma-separated list. See example 1 for details.
|
|
Either `opcode-index`, `opcode-name` or `snippets-file` must be set.
|
|
|
|
.. option:: -snippets-file=<filename>
|
|
|
|
Specify the custom code snippet to measure. See example 2 for details.
|
|
Either `opcode-index`, `opcode-name` or `snippets-file` must be set.
|
|
|
|
.. option:: -mode=[latency|uops|inverse_throughput|analysis]
|
|
|
|
Specify the run mode. Note that if you pick `analysis` mode, you also need
|
|
to specify at least one of the `-analysis-clusters-output-file=` and
|
|
`-analysis-inconsistencies-output-file=`.
|
|
|
|
.. option:: -repetition-mode=[duplicate|loop|min]
|
|
|
|
Specify the repetition mode. `duplicate` will create a large, straight line
|
|
basic block with `num-repetitions` copies of the snippet. `loop` will wrap
|
|
the snippet in a loop which will be run `num-repetitions` times. The `loop`
|
|
mode tends to better hide the effects of the CPU frontend on architectures
|
|
that cache decoded instructions, but consumes a register for counting
|
|
iterations. If performing an analysis over many opcodes, it may be best
|
|
to instead use the `min` mode, which will run each other mode, and produce
|
|
the minimal measured result.
|
|
|
|
.. option:: -num-repetitions=<Number of repetitions>
|
|
|
|
Specify the number of repetitions of the asm snippet.
|
|
Higher values lead to more accurate measurements but lengthen the benchmark.
|
|
|
|
.. option:: -max-configs-per-opcode=<value>
|
|
|
|
Specify the maximum configurations that can be generated for each opcode.
|
|
By default this is `1`, meaning that we assume that a single measurement is
|
|
enough to characterize an opcode. This might not be true of all instructions:
|
|
for example, the performance characteristics of the LEA instruction on X86
|
|
depends on the value of assigned registers and immediates. Setting a value of
|
|
`-max-configs-per-opcode` larger than `1` allows `llvm-exegesis` to explore
|
|
more configurations to discover if some register or immediate assignments
|
|
lead to different performance characteristics.
|
|
|
|
|
|
.. option:: -benchmarks-file=</path/to/file>
|
|
|
|
File to read (`analysis` mode) or write (`latency`/`uops`/`inverse_throughput`
|
|
modes) benchmark results. "-" uses stdin/stdout.
|
|
|
|
.. option:: -analysis-clusters-output-file=</path/to/file>
|
|
|
|
If provided, write the analysis clusters as CSV to this file. "-" prints to
|
|
stdout. By default, this analysis is not run.
|
|
|
|
.. option:: -analysis-inconsistencies-output-file=</path/to/file>
|
|
|
|
If non-empty, write inconsistencies found during analysis to this file. `-`
|
|
prints to stdout. By default, this analysis is not run.
|
|
|
|
.. option:: -analysis-clustering=[dbscan,naive]
|
|
|
|
Specify the clustering algorithm to use. By default DBSCAN will be used.
|
|
Naive clustering algorithm is better for doing further work on the
|
|
`-analysis-inconsistencies-output-file=` output, it will create one cluster
|
|
per opcode, and check that the cluster is stable (all points are neighbours).
|
|
|
|
.. option:: -analysis-numpoints=<dbscan numPoints parameter>
|
|
|
|
Specify the numPoints parameters to be used for DBSCAN clustering
|
|
(`analysis` mode, DBSCAN only).
|
|
|
|
.. option:: -analysis-clustering-epsilon=<dbscan epsilon parameter>
|
|
|
|
Specify the epsilon parameter used for clustering of benchmark points
|
|
(`analysis` mode).
|
|
|
|
.. option:: -analysis-inconsistency-epsilon=<epsilon>
|
|
|
|
Specify the epsilon parameter used for detection of when the cluster
|
|
is different from the LLVM schedule profile values (`analysis` mode).
|
|
|
|
.. option:: -analysis-display-unstable-clusters
|
|
|
|
If there is more than one benchmark for an opcode, said benchmarks may end up
|
|
not being clustered into the same cluster if the measured performance
|
|
characteristics are different. by default all such opcodes are filtered out.
|
|
This flag will instead show only such unstable opcodes.
|
|
|
|
.. option:: -ignore-invalid-sched-class=false
|
|
|
|
If set, ignore instructions that do not have a sched class (class idx = 0).
|
|
|
|
.. option:: -mcpu=<cpu name>
|
|
|
|
If set, measure the cpu characteristics using the counters for this CPU. This
|
|
is useful when creating new sched models (the host CPU is unknown to LLVM).
|
|
|
|
.. option:: --dump-object-to-disk=true
|
|
|
|
By default, llvm-exegesis will dump the generated code to a temporary file to
|
|
enable code inspection. You may disable it to speed up the execution and save
|
|
disk space.
|
|
|
|
EXIT STATUS
|
|
-----------
|
|
|
|
:program:`llvm-exegesis` returns 0 on success. Otherwise, an error message is
|
|
printed to standard error, and the tool returns a non 0 value.
|