Skip to content

Commit 96be695

Browse files
committed
ENH: Pass optimizations arguments to asv build
This patch allows passing `-j`, `--cpu-baseline`, `--cpu-dispatch` and `--disable-optimization` to ASV build when argument `--bench-compare` is used.
1 parent 74712a5 commit 96be695

File tree

3 files changed

+198
-3
lines changed

3 files changed

+198
-3
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ benchmarks/results
182182
benchmarks/html
183183
benchmarks/env
184184
benchmarks/numpy
185+
benchmarks/_asv_compare.conf.json
185186
# cythonized files
186187
cythonize.dat
187188
numpy/random/_mtrand/_mtrand.c

benchmarks/asv_compare.conf.json.tpl

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// This config file is almost similar to 'asv.conf.json' except it contains
2+
// custom tokens that can be substituted by 'runtests.py' and ASV,
3+
// due to the necessity to add custom build options when `--bench-compare`
4+
// is used.
5+
{
6+
// The version of the config file format. Do not change, unless
7+
// you know what you are doing.
8+
"version": 1,
9+
10+
// The name of the project being benchmarked
11+
"project": "numpy",
12+
13+
// The project's homepage
14+
"project_url": "https://www.numpy.org/",
15+
16+
// The URL or local path of the source code repository for the
17+
// project being benchmarked
18+
"repo": "..",
19+
20+
// List of branches to benchmark. If not provided, defaults to "master"
21+
// (for git) or "tip" (for mercurial).
22+
"branches": ["HEAD"],
23+
24+
// The DVCS being used. If not set, it will be automatically
25+
// determined from "repo" by looking at the protocol in the URL
26+
// (if remote), or by looking for special directories, such as
27+
// ".git" (if local).
28+
"dvcs": "git",
29+
30+
// The tool to use to create environments. May be "conda",
31+
// "virtualenv" or other value depending on the plugins in use.
32+
// If missing or the empty string, the tool will be automatically
33+
// determined by looking for tools on the PATH environment
34+
// variable.
35+
"environment_type": "virtualenv",
36+
37+
// the base URL to show a commit for the project.
38+
"show_commit_url": "https://github.com/numpy/numpy/commit/",
39+
40+
// The Pythons you'd like to test against. If not provided, defaults
41+
// to the current version of Python used to run `asv`.
42+
"pythons": ["3.7"],
43+
44+
// The matrix of dependencies to test. Each key is the name of a
45+
// package (in PyPI) and the values are version numbers. An empty
46+
// list indicates to just test against the default (latest)
47+
// version.
48+
"matrix": {
49+
"Cython": [],
50+
},
51+
52+
// The directory (relative to the current directory) that benchmarks are
53+
// stored in. If not provided, defaults to "benchmarks"
54+
"benchmark_dir": "benchmarks",
55+
56+
// The directory (relative to the current directory) to cache the Python
57+
// environments in. If not provided, defaults to "env"
58+
// NOTE: changes dir name will requires update `generate_asv_config()` in
59+
// runtests.py
60+
"env_dir": "env",
61+
62+
63+
// The directory (relative to the current directory) that raw benchmark
64+
// results are stored in. If not provided, defaults to "results".
65+
"results_dir": "results",
66+
67+
// The directory (relative to the current directory) that the html tree
68+
// should be written to. If not provided, defaults to "html".
69+
"html_dir": "html",
70+
71+
// The number of characters to retain in the commit hashes.
72+
// "hash_length": 8,
73+
74+
// `asv` will cache wheels of the recent builds in each
75+
// environment, making them faster to install next time. This is
76+
// number of builds to keep, per environment.
77+
"build_cache_size": 8,
78+
79+
"build_command" : [
80+
"python setup.py build {numpy_build_options}",
81+
"PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}"
82+
],
83+
// The commits after which the regression search in `asv publish`
84+
// should start looking for regressions. Dictionary whose keys are
85+
// regexps matching to benchmark names, and values corresponding to
86+
// the commit (exclusive) after which to start looking for
87+
// regressions. The default is to start from the first commit
88+
// with results. If the commit is `null`, regression detection is
89+
// skipped for the matching benchmark.
90+
//
91+
// "regressions_first_commits": {
92+
// "some_benchmark": "352cdf", // Consider regressions only after this commit
93+
// "another_benchmark": null, // Skip regression detection altogether
94+
// }
95+
}

runtests.py

+102-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252

5353

5454
import sys
55-
import os
55+
import os, glob
5656

5757
# In case we are run from the source directory, we don't want to import the
5858
# project from there:
@@ -310,8 +310,16 @@ def main(argv):
310310
out = subprocess.check_output(['git', 'rev-parse', commit_a])
311311
commit_a = out.strip().decode('ascii')
312312

313+
# generate config file with the required build options
314+
asv_cfpath = [
315+
'--config', asv_compare_config(
316+
os.path.join(ROOT_DIR, 'benchmarks'), args,
317+
# to clear the cache if the user changed build options
318+
(commit_a, commit_b)
319+
)
320+
]
313321
cmd = ['asv', 'continuous', '-e', '-f', '1.05',
314-
commit_a, commit_b] + bench_args
322+
commit_a, commit_b] + asv_cfpath + bench_args
315323
ret = subprocess.call(cmd, cwd=os.path.join(ROOT_DIR, 'benchmarks'))
316324
sys.exit(ret)
317325

@@ -361,7 +369,6 @@ def main(argv):
361369
else:
362370
sys.exit(1)
363371

364-
365372
def build_project(args):
366373
"""
367374
Build a dev version of the project.
@@ -491,6 +498,98 @@ def build_project(args):
491498

492499
return site_dir, site_dir_noarch
493500

501+
def asv_compare_config(bench_path, args, h_commits):
502+
"""
503+
Fill the required build options through custom variable
504+
'numpy_build_options' and return the generated config path.
505+
"""
506+
conf_path = os.path.join(bench_path, "asv_compare.conf.json.tpl")
507+
nconf_path = os.path.join(bench_path, "_asv_compare.conf.json")
508+
509+
# add custom build
510+
build = []
511+
if args.parallel > 1:
512+
build += ["-j", str(args.parallel)]
513+
if args.cpu_baseline:
514+
build += ["--cpu-baseline", args.cpu_baseline]
515+
if args.cpu_dispatch:
516+
build += ["--cpu-dispatch", args.cpu_dispatch]
517+
if args.disable_optimization:
518+
build += ["--disable-optimization"]
519+
520+
is_cached = asv_substitute_config(conf_path, nconf_path,
521+
numpy_build_options = ' '.join([f'\\"{v}\\"' for v in build]),
522+
)
523+
if not is_cached:
524+
asv_clear_cache(bench_path, h_commits)
525+
return nconf_path
526+
527+
def asv_clear_cache(bench_path, h_commits, env_dir="env"):
528+
"""
529+
Force ASV to clear the cache according to specified commit hashes.
530+
"""
531+
# FIXME: only clear the cache from the current environment dir
532+
asv_build_pattern = os.path.join(bench_path, env_dir, "*", "asv-build-cache")
533+
for asv_build_cache in glob.glob(asv_build_pattern, recursive=True):
534+
for c in h_commits:
535+
try: shutil.rmtree(os.path.join(asv_build_cache, c))
536+
except OSError: pass
537+
538+
def asv_substitute_config(in_config, out_config, **custom_vars):
539+
"""
540+
A workaround to allow substituting custom tokens within
541+
ASV configuration file since there's no official way to add custom
542+
variables(e.g. env vars).
543+
544+
Parameters
545+
----------
546+
in_config : str
547+
The path of ASV configuration file, e.g. '/path/to/asv.conf.json'
548+
out_config : str
549+
The path of generated configuration file,
550+
e.g. '/path/to/asv_substituted.conf.json'.
551+
552+
The other keyword arguments represent the custom variables.
553+
554+
Returns
555+
-------
556+
True(is cached) if 'out_config' is already generated with
557+
the same '**custom_vars' and updated with latest 'in_config',
558+
False otherwise.
559+
560+
Examples
561+
--------
562+
See asv_compare_config().
563+
"""
564+
assert in_config != out_config
565+
assert len(custom_vars) > 0
566+
567+
def sdbm_hash(*factors):
568+
chash = 0
569+
for f in factors:
570+
for char in str(f):
571+
chash = ord(char) + (chash << 6) + (chash << 16) - chash
572+
chash &= 0xFFFFFFFF
573+
return chash
574+
575+
vars_hash = sdbm_hash(custom_vars, os.path.getmtime(in_config))
576+
try:
577+
with open(out_config, "r") as wfd:
578+
hash_line = wfd.readline().split('hash:')
579+
if len(hash_line) > 1 and int(hash_line[1]) == vars_hash:
580+
return True
581+
except IOError:
582+
pass
583+
584+
custom_vars = {f'{{{k}}}':v for k, v in custom_vars.items()}
585+
with open(in_config, "r") as rfd, open(out_config, "w") as wfd:
586+
wfd.write(f"// hash:{vars_hash}\n")
587+
wfd.write("// This file is automatically generated by runtests.py\n")
588+
for line in rfd:
589+
for key, val in custom_vars.items():
590+
line = line.replace(key, val)
591+
wfd.write(line)
592+
return False
494593

495594
#
496595
# GCOV support

0 commit comments

Comments
 (0)