Profile-guided optimization
aspect pgo measures what profile-guided optimization (PGO) does for
krabka-broker, and what building it for x86-64-v3 does. It builds the
broker in up to four variants:
baseline: the normal release broker.pgo: the broker built from a profile of benchmark load.v3: the release broker built with-Ctarget-cpu=x86-64-v3.v3-pgo: both, from the same profile aspgo.
Then it runs every variant against the same scenarios, and it writes a report that compares each one with the baseline.
The task is an experiment. The shipped image uses neither PGO nor x86-64-v3.
What x86-64-v3 is
x86-64-v3 is a microarchitecture level of the x86-64 psABI. Beyond the
x86-64-v2 set (SSE4.2, POPCNT, CMPXCHG16B), it lets the compiler use AVX,
AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE and XSAVE anywhere in the binary.
A build without -Ctarget-cpu assumes only the baseline x86-64 set, which
ends at SSE2.
Intel CPUs from Haswell (2013) on, and AMD CPUs from Zen 1 (2017) on, have
the level. Some later Intel Pentium, Celeron and Atom parts do not. A v3
binary dies with SIGILL on a CPU that lacks it, at the first instruction it
cannot run. The task reads the flags line of /proc/cpuinfo and refuses
to build or run a v3 variant on a host that lacks any of the level’s
flags. A dry run prints a warning instead.
The build host needs the level as well as the compare host. RUSTFLAGS
also applies to build scripts, and cargo runs them on the build host.
Requirements
- Linux. The task reads
/proc/<pid>/statand pins processes withtaskset. - At least 4 CPUs. The compare phase puts the broker on CPUs 2 and 3, and the driver on CPUs 0 and 1.
llvm-profdatafrom the pinned toolchain. Install it withrustup component add llvm-tools.- Free ports 19092 and 19093 on
127.0.0.1. - An
x86-64-v3CPU for thev3andv3-pgovariants. Leave them out of--variantson an older CPU. - Disk for a release target directory for each variant, and one for the instrumented build: five with the default variants. A saturating run also writes several GB to its log directory. The task deletes each log directory when its run ends.
Run it
aspect pgo
A full run takes a long time. Five of the steps are fat-LTO release builds, and the compare phase runs 5 rounds of two scenarios on four brokers.
The options are:
--work-dir DIR: where the builds, profiles, logs and results go. The default ispgo-workbeside the repository root.--phase NAME: the first phase to run. The task runs that phase and every phase after it. The default isall.--phase NAME,NAME,...: a comma list runs exactly the phases it names, in pipeline order, whatever order the list gives them in.--variants NAME,...: the variants thatbuildandoptimizebuild and thatcompareruns. The default isbaseline,pgo,v3,v3-pgo. The list must includebaseline, because every change is against it.--trials N: the number of compare rounds. The default is 5. Use a multiple of the variant count, so that every variant goes first equally often.--dry-run: print every command with its environment, and run nothing.
The phases run in this order:
buildbuilds the baselinekrabka-broker,krabka-formatandkrabka-bench-driverwith the repository’s[profile.release]. It then builds a broker withRUSTFLAGS=-Cprofile-generate, and thev3broker withRUSTFLAGS=-Ctarget-cpu=x86-64-v3. It always makes the baseline and the instrumented broker, and makesv3only when--variantsselects it.trainruns the instrumented broker under six short scenarios. They are copies of the files inbench/scenarios/with a 2s warmup and a 20s measured window. They cover small records, large records with lz4, small records with zstd, four producers and four consumers, a fixed rate, andacks=all. The task stops each broker with SIGTERM. An instrumented binary writes its.profrawfile only when it exits cleanly. The task gives each driver run 600 seconds and retries a hung session once.mergemerges the.profrawfiles intomerged.profdata.optimizebuilds thepgobroker withRUSTFLAGS=-Cprofile-use, and thev3-pgobroker withRUSTFLAGS="-Ctarget-cpu=x86-64-v3 -Cprofile-use". It makes only the ones that--variantsselects. The instrumented broker is a generic build, so LLVM can find that a few functions of thev3-pgobuild no longer match the profile. It warns, and it builds those functions without profile data.compareruns every selected broker, and writesreport.md. The driver exits 0 even when one of its producer or consumer tasks fails, and such a run did less work than the scenario asks for. The task runs a trial whose driver output listserrorsonce more, and stops if the second run lists them too. A trial with errors never goes into the report.
Each variant builds into target-<variant> under the work directory, and the
instrumented broker into target-instrumented.
If a phase fails, fix the cause and start again from that phase:
aspect pgo --phase merge
To add a variant to a work directory that already has a profile, skip
train and merge. train rewrites the .profraw files, and a new merge
would rewrite merged.profdata, which calls for both PGO builds again:
aspect pgo --phase build,optimize,compare
Cargo finds the builds that exist up to date, so this builds only the new variants, then compares all of them.
Before a phase runs, the task checks that the work directory holds the files
the phase reads, such as every selected variant’s broker for compare. A
real run stops at the first missing file and names the phase that makes it.
--dry-run makes nothing, so it checks only the files that no phase of the
run makes, and it prints a missing line for each one that a real run would
stop on.
What the compare phase measures
The evaluation set is small-msg-saturate as it is in bench/scenarios/,
and large-msg with a 30s measured window. Each round runs every scenario
once on each selected broker. The order rotates from round to round: round 1
runs the brokers in the order baseline, pgo, v3, v3-pgo, and each
later round starts one broker further along that list. With four variants,
round 2 runs pgo, v3, v3-pgo, baseline. Over as many rounds as there
are variants, every broker runs once in every position, so a drift over the
run falls on all of them alike. Each run starts from a freshly formatted log
directory.
The task records four values for each run:
- Producer throughput in messages a second: the driver’s
msgs_produceddivided by the scenario’sduration. - Producer throughput in MB a second: the driver’s
bytes_individed by theduration. A MB is 10^6 bytes. - The p99 producer-ack latency: the driver’s
producer_latency.p99. - Broker CPU seconds:
utimeplusstimefrom/proc/<pid>/statjust before the SIGTERM, divided byCLK_TCK. This count includes the broker’s startup.
Read the report
The task prints the path of report.md when it ends. The report has one
table for each scenario, and one row for each value. A row gives the
baseline median, then a median and a change for each other selected variant,
then the value of every trial of every variant.
A change is the variant’s median minus the baseline median, as a percent of
the baseline median. The Better column says which direction is an improvement.
A positive change is better for throughput. A negative change is better for
latency and CPU seconds.
Compare the change with the spread of the trial values. A change that is
smaller than the spread inside one broker’s trials is noise. To tell how much
of v3-pgo comes from each half, set its change beside those of pgo and
v3. Run more
trials with --trials before you trust it.
The work directory also holds the data behind the report:
compare/results.jsonholds every trial’s values. The task writes it after each trial.compare/<trial>.jsonis the driver’s own output for each trial.logs/<trial>.outandlogs/<trial>.errhold each broker’s output.train/<scenario>.jsonis the driver’s output for each training run.