LLVM-EXEGESIS(1)
π LLVM-EXEGESIS π
okay bestie, sit down, hydrate, because we're about to talk about llvm-exegesis, straight out of the LLVM manual, section 1. she's a machine instruction benchmark and she's literally so powerful it's giving main character energy. no cap.
β¨ NAME β¨
llvm-exegesis β LLVM Machine Instruction Benchmark π
β¨ SYNOPSIS β¨
llvm-exegesis [options]
that's it. that's the whole vibe. just you, some options, and the raw truth about your CPU. π
β¨ DESCRIPTION β¨
so like, llvm-exegesis is a benchmarking tool that uses the info LLVM already knows about the world to measure host machine instruction characteristics β think latency, throughput, or port decomposition. she's nosy in the best way. π¦
here's the tea: you give her an LLVM opcode name and a benchmarking mode, and she 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 snippet gets jitted and executed on the host subtarget. the time taken (resp. the resource usage) is measured using hardware performance counters. and the result? printed out as YAML to standard output. slay. β¨
the main goal of this tool is to automatically (in)validate LLVM's TableDef scheduling models β like, is the model telling the truth or is it lying to us?? π and to that end, we also provide analysis of the results, because receipts matter.
oh and pookie, llvm-exegesis can also benchmark arbitrary user-provided code snippets. she's flexible like that. π«Ά
πΈ EXAMPLE 1: BENCHMARKING INSTRUCTIONS πΈ
okay so say you have an X86-64 machine (iconic). to measure the latency of a single instruction, run:
$ llvm-exegesis -mode=latency -opcode-name=ADD64rr
measuring the uop decomposition or inverse throughput of an instruction works basically the same way, no drama:
$ llvm-exegesis -mode=uops -opcode-name=ADD64rr
$ llvm-exegesis -mode=inverse_throughput -opcode-name=ADD64rr
the output is a YAML document β and the default is to write to stdout, but you can redirect the output to a file using -benchmarks-file. bestie options are everything. π
---
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
'
...
look at her. the structure. the little ... at the end. obsessed. π
now, to measure the latency of all instructions for the host architecture, run:
$ llvm-exegesis -mode=latency -opcode-index=-1
yes, -1. that's not a typo, that's a mood. π₯
π¦ 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). cute.
$ echo "vzeroupper" | llvm-exegesis -mode=uops -snippets-file=-
real-life code snippets typically depend on registers or memory. so llvm-exegesis checks the liveliness of registers β meaning 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.
llvm-exegesiswill automatically assign a value to the register. this can be done using the directiveLLVM-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".
llvm-exegesiswill benchmark using whatever value was in this registers on entry. this can be done using the directiveLLVM-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):
# LLVM-EXEGESIS-LIVEIN RDI
# LLVM-EXEGESIS-DEFREG XMM1 42
vmulps (%rdi), %xmm1, %xmm2
vhaddps %xmm2, %xmm2, %xmm3
addq $0x10, %rdi
look at those directives just sitting there being all declarative. we love a self-describing snippet. π
π 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:
$ 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:
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
...
ngl those scheduling class strings are giving keyboard smash but they're doing their best and honestly? relatable. π
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]
β οΈ heads up bestie: note that the scheduling class names will be resolved only when 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. so don't panic, the data is still the data. π«Ά
okay that's the first chunk, and there's more coming so don't go anywhere β we're only getting started π
β¨ OPTIONS β¨
Okay bestie, this is the whole menu. Every single flag, no skips, we're eating the entire plate π½οΈβ¨
-helpβ prints a lil summary of all the command line options. It's giving "what are my choices" energy.-opcode-index=<LLVM opcode index>β pick the opcode you wanna measure by index, bestie. And listen: if you set it to-1, it will measure every existing opcode (yes, all of them, absolute maximalist behavior π ). Peep example 1 for the deets. Also mandatory: you HAVE to set one of-opcode-index,-opcode-name, or-snippets-file. Not optional. Not negotiable. Periodt.-opcode-name=<opcode name 1>,<opcode name 2>,...β same idea but you name the opcode instead of numbering it. And yes you can stack several of them as a comma-separated list, we love a bundle deal π. See example 1. And again: one of-opcode-index,-opcode-name, or-snippets-filemust be set.-snippets-file=<filename>β this is where you hand over your own custom code snippet to be measured. Check example 2. And yep, the trio rule again:-opcode-index,-opcode-name, or-snippets-fileβ at least one, always.-mode=[latency|uops|inverse_throughput|analysis]β picks your run mode. Heads up though, some of these modes come with extra requirements and extra options, it's giving β¨high maintenanceβ¨ but we respect it.
latency mode can make use of either RDTSC or LBR. The latency[LBR] flavor is only available on X86 (at least Skylake, so don't try it on your ancient laptop bestie π). And to actually run in latency mode, you need a positive value for x86-lbr-sample-period and -repetition-mode=loop.
In analysis mode you also need to specify at least one of -analysis-clusters-output-file= and -analysis-inconsistencies-output-file=.
-x86-lbr-sample-period=<nBranches/sample>β sets the LBR sampling period, aka how many branches go by before we grab a sample. If you give it a positive value and the mode islatency, we'll use LBRs to measure. Choosing the "right" sampling period: a small value is preferred, but if you sample too frequently you might get throttling, so likeβ¦ chill a little πΈ. Also use a prime number so you don't consistently skip certain blocks. Math girlies stay winning.-repetition-mode=[duplicate|loop|min]β picks how we repeat stuff:
duplicate builds one big straight-line basic block stuffed with num-repetitions instructions (it repeats the snippet num-repetitions/snippet size times).
loop will, optionally, duplicate the snippet until the loop body holds at least loop-body-size instructions, and then wraps the whole thing in a loop that executes num-repetitions instructions (so yeah, again it repeats the snippet num-repetitions/snippet size times). The loop mode, especially with loop unrolling, tends to hide the CPU frontend's effects better on architectures that cache decoded instructions β but it eats a register for counting iterations, so that's the tradeoff π.
And if you're doing analysis over a whole bunch of opcodes, it may be best to use min mode instead, which runs every other mode and gives you the minimal measured result. Tiny but mighty.
-num-repetitions=<Number of repetitions>β sets the target number of executed instructions. Note that the actual repetition count of the snippet will benum-repetitions/snippet size. Higher values = more accurate measurements, but the benchmark gets longer. The eternal struggle fr fr.-loop-body-size=<Preferred loop body size>β only does anything for-repetition-mode=[loop|min]. Instead of looping straight over the snippet, it first duplicates it so the loop body has at least this many instructions. Why? Because that can get the loop body cached in the CPU Op Cache / Loop Cache, which may have higher throughput than the CPU decoders. Cache girlies, we love you π-max-configs-per-opcode=<value>β caps how many configurations can be generated for each opcode. Default is1, meaning we assume one single measurement is enough to characterize an opcode. But that's not true for every instruction: for example, the performance characteristics of theLEAinstruction on X86 depend on the value of assigned registers and immediates π. Setting-max-configs-per-opcodelarger than1letsllvm-exegesisexplore more configurations to discover whether some register or immediate assignments lead to different performance characteristics. Detective mode activated π-benchmarks-file=</path/to/file>β the file to read (analysis mode) or write (latency/uops/inverse_throughput modes) benchmark results. And"-"means use stdin/stdout. Flexible queen.-analysis-clusters-output-file=</path/to/file>β if you provide it, the analysis clusters get written as CSV to this file."-"prints to stdout. By default, this analysis is not run.-analysis-inconsistencies-output-file=</path/to/file>β if it's non-empty, the inconsistencies found during analysis get written to this file.-prints to stdout. By default, this analysis is not run.-analysis-clustering=[dbscan,naive]β picks the clustering algorithm. By defaultDBSCANis used. The naive clustering algorithm is better for doing further work on the-analysis-inconsistencies-output-file=output β it creates one cluster per opcode and checks that the cluster is stable (all points are neighbours). Simple, honest, no drama.-analysis-numpoints=<dbscan numPoints parameter>β sets the numPoints parameter used for DBSCAN clustering (analysis mode, DBSCAN only, so don't @ it otherwise).-analysis-clustering-epsilon=<dbscan epsilon parameter>β sets the epsilon parameter used for clustering of benchmark points (analysis mode).-analysis-inconsistency-epsilon=<epsilon>β sets the epsilon parameter used for detecting when the cluster differs from the LLVM schedule profile values (analysis mode).-analysis-display-unstable-clustersβ if there's more than one benchmark for an opcode, those benchmarks might not end up clustered into the same cluster when the measured performance characteristics are different. By default all such opcodes get filtered out. This flag instead shows you only the unstable opcodes. Unstable? No bestie, we're calling it eclectic π-ignore-invalid-sched-class=falseβ if set, ignore instructions that don't have a sched class (class idx =0).-mcpu=<cpu name>β if set, measure the cpu characteristics using the counters for this CPU. This is useful when you're creating new sched models and the host CPU is unknown to LLVM. The "who even are you" option π«Ά--dump-object-to-disk=trueβ by default,llvm-exegesisdumps the generated code to a temporary file so you can inspect it. You may disable it to speed up the execution and save disk space. Same energy as clearing your camera roll, honestly π
π EXIT STATUS π
llvm-exegesis returns 0 on success. Otherwise, an error message is printed to standard error, and the tool returns a non 0 value. So if you see anything other than zero, something went wrong bestie, read the stderr.
π AUTHOR π
Maintained by the LLVM Team (https://llvm.org/).
π COPYRIGHT π
2003-2025, LLVM Project
And that's the whole page, bestie, we made it to the end π This was LLVM-EXEGESIS(1), manual version 14, dated 2025-05-22 β go measure something iconic, you absolute main character. Slay, no cap, periodt. πβ¨π¦
π¦ where this page comes from
- ubuntu questing: llvm-14 1:14.0.6-20ubuntu3
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms