Skip to content

Commit ff71014

Browse files
committed
[𝘀𝗽𝗿] initial version
Created using spr 1.3.6-beta.1
1 parent 4b1f1f7 commit ff71014

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed

lld/utils/run_benchmark.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
# See https://llvm.org/LICENSE.txt for license information.
5+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
#
7+
# ==------------------------------------------------------------------------==#
8+
9+
import argparse
10+
import os
11+
import shutil
12+
import subprocess
13+
import tempfile
14+
15+
# The purpose of this script is to measure the performance effect
16+
# of an lld change in a statistically sound way, automating all the
17+
# tedious parts of doing so. It copies the test case into /tmp as well as
18+
# running the test binaries from /tmp to reduce the influence on the test
19+
# machine's storage medium on the results. It accounts for measurement
20+
# bias caused by binary layout (using the --randomize-section-padding
21+
# flag to link the test binaries) and by environment variable size
22+
# (implemented by hyperfine [1]). Runs of the base and test case are
23+
# interleaved to account for environmental factors which may influence
24+
# the result due to the passage of time. The results of running hyperfine
25+
# are collected into a results.csv file in the output directory and may
26+
# be analyzed by the user with a tool such as ministat.
27+
#
28+
# Requirements: Linux host, hyperfine [2] in $PATH, run from a build directory
29+
# configured to use ninja and a recent version of lld that supports
30+
# --randomize-section-padding, /tmp is tmpfs.
31+
#
32+
# [1] https://github.com/sharkdp/hyperfine/blob/3cedcc38d0c430cbf38b4364b441c43a938d2bf3/src/util/randomized_environment_offset.rs#L1
33+
# [2] https://github.com/sharkdp/hyperfine
34+
#
35+
# Example invocation for comparing the performance of the current commit
36+
# against the previous commit which is treated as the baseline, without
37+
# linking debug info:
38+
#
39+
# lld/utils/run_benchmark.py \
40+
# --base-commit HEAD^ \
41+
# --test-commit HEAD \
42+
# --test-case lld/utils/speed-test-reproducers/result/firefox-x64/response.txt \
43+
# --num-iterations 512 \
44+
# --num-binary-variants 16 \
45+
# --output-dir outdir \
46+
# --ldflags=-S
47+
#
48+
# Then this bash command will compare the real time of the base and test cases.
49+
#
50+
# ministat -A \
51+
# <(grep lld-base outdir/results.csv | cut -d, -f2) \
52+
# <(grep lld-test outdir/results.csv | cut -d, -f2)
53+
54+
# We don't want to copy stat() information when we copy the reproducer
55+
# to the temporary directory. Files in the Nix store are read-only so this will
56+
# cause trouble when the linker writes the output file and when we want to clean
57+
# up the temporary directory. Python doesn't provide a way to disable copying
58+
# stat() information in shutil.copytree so we just monkeypatch shutil.copystat
59+
# to do nothing.
60+
shutil.copystat = lambda *args, **kwargs: 0
61+
62+
parser = argparse.ArgumentParser(prog = 'benchmark_change.py')
63+
parser.add_argument('--base-commit', required=True)
64+
parser.add_argument('--test-commit', required=True)
65+
parser.add_argument('--test-case', required=True)
66+
parser.add_argument('--num-iterations', type=int, required=True)
67+
parser.add_argument('--num-binary-variants', type=int, required=True)
68+
parser.add_argument('--output-dir', required=True)
69+
parser.add_argument('--ldflags', required=False)
70+
args = parser.parse_args()
71+
72+
test_dir = tempfile.mkdtemp()
73+
print(f'Using {test_dir} as temporary directory')
74+
75+
os.makedirs(args.output_dir)
76+
print(f'Using {args.output_dir} as output directory')
77+
78+
def extract_link_command(target):
79+
# We assume that the last command printed by "ninja -t commands" containing a
80+
# "-o" flag is the link command (we need to check for -o because subsequent
81+
# commands create symlinks for ld.lld and so on). This is true for CMake and
82+
# gn.
83+
link_command = None
84+
for line in subprocess.Popen(['ninja', '-t', 'commands', target],
85+
stdout=subprocess.PIPE).stdout.readlines():
86+
commands = line.decode('utf-8').split('&&')
87+
for command in commands:
88+
if ' -o ' in command:
89+
link_command = command.strip()
90+
return link_command
91+
92+
def generate_binary_variants(case_name):
93+
subprocess.run(['ninja', 'lld'])
94+
link_command = extract_link_command('lld')
95+
96+
for i in range(0, args.num_binary_variants):
97+
print(f'Generating binary variant {i} for {case_name} case')
98+
command = f'{link_command} -o {test_dir}/lld-{case_name}{i} -Wl,--randomize-section-padding={i}'
99+
subprocess.run(command, check=True, shell=True)
100+
101+
# Make sure that there are no local changes.
102+
subprocess.run(['git', 'diff', '--exit-code', 'HEAD'], check=True)
103+
104+
# Resolve the base and test commit, since if they are relative to HEAD we will
105+
# check out the wrong commit below.
106+
resolved_base_commit = subprocess.check_output(['git', 'rev-parse', args.base_commit]).strip()
107+
resolved_test_commit = subprocess.check_output(['git', 'rev-parse', args.test_commit]).strip()
108+
109+
test_case_dir = os.path.dirname(args.test_case)
110+
test_case_respfile = os.path.basename(args.test_case)
111+
112+
test_dir_test_case_dir = f'{test_dir}/testcase'
113+
shutil.copytree(test_case_dir, test_dir_test_case_dir)
114+
115+
subprocess.run(['git', 'checkout', resolved_base_commit], check=True)
116+
generate_binary_variants('base')
117+
118+
subprocess.run(['git', 'checkout', resolved_test_commit], check=True)
119+
generate_binary_variants('test')
120+
121+
def hyperfine_link_command(case_name):
122+
return f'../lld-{case_name}$(({{iter}}%{args.num_binary_variants})) -flavor ld.lld @{test_case_respfile} {args.ldflags or ""}'
123+
124+
results_csv = f'{args.output_dir}/results.csv'
125+
subprocess.run(['hyperfine', '--export-csv', os.path.abspath(results_csv),
126+
'-P', 'iter', '0', str(args.num_iterations - 1),
127+
hyperfine_link_command('base'),
128+
hyperfine_link_command('test')],
129+
check=True, cwd=test_dir_test_case_dir)
130+
131+
shutil.rmtree(test_dir)

0 commit comments

Comments
 (0)