Skip to content

Commit 794a3a6

Browse files
committed
[benchmark] notebook for interactively computing benchmark config
1 parent 052c838 commit 794a3a6

File tree

16 files changed

+1212
-296
lines changed

16 files changed

+1212
-296
lines changed

hail/python/benchmark/conftest.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import os
23

34
import pytest
@@ -10,36 +11,46 @@
1011

1112
@pytest.hookimpl
1213
def pytest_addoption(parser):
13-
parser.addoption("--log", type=str, help='Log file path', default=None)
14-
parser.addoption("--output", type=str, help="Output file path.", default=None)
15-
parser.addoption("--data-dir", type=str, help="Data directory.", default=os.getenv('HAIL_BENCHMARK_DIR'))
16-
parser.addoption('--iterations', type=int, help='override number of iterations for all benchmarks', default=None)
17-
parser.addoption('--cores', type=int, help='Number of cores to use.', default=1)
18-
parser.addoption(
14+
group = parser.getgroup('benchmark')
15+
group.addoption("--log", type=str, help='Log file path', default=None)
16+
group.addoption("--output", type=str, help="Output file path.", default=None)
17+
group.addoption("--data-dir", type=str, help="Data directory.", default=os.getenv('HAIL_BENCHMARK_DIR'))
18+
group.addoption(
19+
"--burn-in-iterations", type=int, help="override number of burn-in iterations for all benchmarks", default=None
20+
)
21+
group.addoption('--iterations', type=int, help='override number of iterations for all benchmarks', default=None)
22+
group.addoption('--cores', type=int, help='Number of cores to use.', default=1)
23+
group.addoption(
1924
'--profile',
2025
choices=['cpu', 'alloc', 'itimer'],
2126
help='Run with async-profiler.',
2227
nargs='?',
2328
const='cpu',
2429
default=None,
2530
)
26-
parser.addoption(
31+
group.addoption(
2732
'--max-duration',
2833
type=int,
2934
help='Maximum permitted duration for any benchmark trial in seconds, not to be confused with pytest-timeout',
3035
default=200,
3136
)
32-
parser.addoption('--max-failures', type=int, help='Stop benchmarking item after this many failures', default=3)
33-
parser.addoption(
37+
group.addoption('--max-failures', type=int, help='Stop benchmarking item after this many failures', default=3)
38+
group.addoption(
3439
'--profiler-path', type=str, help='path to aysnc profiler', default=os.getenv('ASYNC_PROFILER_HOME')
3540
)
36-
parser.addoption('--profiler-fmt', choices=['html', 'flame', 'jfr'], help='Choose profiler output.', default='html')
41+
group.addoption('--profiler-fmt', choices=['html', 'flame', 'jfr'], help='Choose profiler output.', default='html')
3742

3843

3944
@pytest.hookimpl
4045
def pytest_configure(config):
4146
init_logging(file=config.getoption('log'))
4247

48+
if (nburn_in_iterations := config.getoption('burn_in_iterations')) is not None:
49+
logging.info(f'benchmark: using {nburn_in_iterations} burn-in iterations.')
50+
51+
if (niterations := config.getoption('iterations')) is not None:
52+
logging.info(f'benchmark: using {niterations} iterations.')
53+
4354

4455
@pytest.hookimpl(tryfirst=True)
4556
def pytest_collection_modifyitems(config, items):
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import tempfile
2+
3+
import pytest
4+
5+
import hail as hl
6+
from benchmark.tools.impex import import_benchmarks
7+
from benchmark.tools.statistics import analyze_benchmarks, laaber_mds, schultz_mds
8+
9+
10+
def test_analyze_benchmarks(local_tmpdir, onethreetwo, onethreethree):
11+
tables = (import_benchmarks(v, local_tmpdir) for v in (onethreetwo, onethreethree))
12+
tables = (t.select(instances=t.instances.iterations.time) for t in tables)
13+
tables = (t._key_by_assert_sorted(*t.key.drop('version')) for t in tables)
14+
tables = (t.checkpoint(tempfile.mktemp(suffix='.ht', dir=local_tmpdir)) for t in tables)
15+
analyze_benchmarks(*tables, n_bootstrap_iterations=1000, confidence=0.95)._force_count()
16+
17+
18+
@pytest.fixture(scope='session')
19+
def _100_instances_100_iterations(resource_dir):
20+
rows = lambda n, _: [hl.struct(id=0, instances=hl.repeat(hl.repeat(1.0, n), n))]
21+
ht = hl.Table._generate(contexts=[100], partitions=1, rowfn=rows)
22+
ht = ht._key_by_assert_sorted(ht.id)
23+
return ht.checkpoint(f'{resource_dir}/100_instances_100_iterations.ht')
24+
25+
26+
@pytest.mark.parametrize('mds', [laaber_mds, schultz_mds])
27+
def test_minimal_detectable_slowdown(_100_instances_100_iterations, mds):
28+
mds(_100_instances_100_iterations, n_experiments=1)._force_count()

hail/python/benchmark/hail/benchmark_table.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ def test_table_range_force_count():
2929
hl.utils.range_table(100_000_000)._force_count()
3030

3131

32-
def test_table_range_join_1b_1k():
33-
ht1 = hl.utils.range_table(1_000_000_000)
34-
ht2 = hl.utils.range_table(1_000)
35-
ht1.join(ht2, 'inner').count()
36-
37-
38-
def test_table_range_join_1b_1b():
39-
ht1 = hl.utils.range_table(1_000_000_000)
40-
ht2 = hl.utils.range_table(1_000_000_000)
32+
@pytest.mark.parametrize(
33+
'm, n',
34+
[
35+
(1_000_000_000, 1_000),
36+
(1_000_000_000, 1_000_000_000),
37+
],
38+
)
39+
def test_table_range_join(m, n):
40+
ht1 = hl.utils.range_table(m)
41+
ht2 = hl.utils.range_table(n)
4142
ht1.join(ht2, 'inner').count()
4243

4344

hail/python/benchmark/hail/conftest.py

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
many_strings_ht,
7373
many_strings_tsv,
7474
onekg_chr22,
75+
onethreethree,
76+
onethreetwo,
7577
profile25_mt,
7678
profile25_vcf,
7779
random_doubles_mt,
@@ -119,39 +121,29 @@ def pytest_runtest_protocol(item, nextitem):
119121
# - each benchmark runs in a clean hail session
120122
# - our means of getting max task memory is quite crude (regex on logs)
121123
# and a fresh session provides a new log
122-
with init_hail(item.config):
123-
if (num_iterations := item.config.getoption('iterations')) is not None:
124-
burn_in_iterations = 0
125-
logging.info(
126-
msg=(
127-
f'Picked up iterations override. Config: '
128-
f'burn_in_iterations: {burn_in_iterations}, '
129-
f'iterations: {num_iterations}.'
130-
)
131-
)
132124

133-
else:
134-
burn_in_iterations = 1
135-
num_iterations = 5
125+
nburn_in_iterations = item.config.getoption('burn_in_iterations', 3)
126+
niterations = item.config.getoption('iterations', 3)
127+
128+
with init_hail(item.config):
129+
logging.info(
130+
msg=(
131+
f'Executing "{item.nodeid}" with '
132+
f'{nburn_in_iterations} burn in iterations and '
133+
f'{niterations} timed iterations.'
134+
),
135+
)
136136

137137
s = item.stash
138138
s[start] = datetime.now(timezone.utc).isoformat()
139139
s[iterations] = []
140140
s[consecutive_fail_count] = 0
141141
s[end] = None
142142

143-
logging.info(
144-
msg=(
145-
f'Executing "{item.nodeid}" with '
146-
f'{burn_in_iterations} burn in iterations and '
147-
f'{num_iterations} timed iterations.'
148-
)
149-
)
150-
151143
max_failures = item.config.getoption('max_failures')
152144

153145
s[context] = 'burn_in'
154-
for k in range(burn_in_iterations):
146+
for k in range(nburn_in_iterations):
155147
if max_failures and s[consecutive_fail_count] >= max_failures:
156148
break
157149

@@ -165,8 +157,8 @@ def pytest_runtest_protocol(item, nextitem):
165157
runner.pytest_runtest_protocol(item, nextitem=item.parent)
166158

167159
s[context] = 'benchmark'
168-
total_iterations = burn_in_iterations + num_iterations
169-
for k in range(burn_in_iterations, total_iterations):
160+
total_iterations = nburn_in_iterations + niterations
161+
for k in range(nburn_in_iterations, total_iterations):
170162
if max_failures and s[consecutive_fail_count] >= max_failures:
171163
break
172164

@@ -179,7 +171,7 @@ def pytest_runtest_protocol(item, nextitem):
179171

180172
if max_failures and s[consecutive_fail_count] >= max_failures:
181173
logging.error(
182-
msg=(f'Benchmarking "{item.nodeid}" aborted due to too many consecutive failures (max={max_failures})')
174+
msg=f'Benchmarking "{item.nodeid}" aborted due to too many consecutive failures (max={max_failures})',
183175
)
184176

185177
# prevent other plugins running that might invoke the benchmark again
@@ -333,6 +325,8 @@ def new_query_tmpdir(tmp_path):
333325
'many_strings_tsv',
334326
'new_query_tmpdir',
335327
'onekg_chr22',
328+
'onethreethree',
329+
'onethreetwo',
336330
'profile25_mt',
337331
'profile25_vcf',
338332
'random_doubles_mt',

hail/python/benchmark/tools/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import logging
22
from typing import Any, Callable, Generator, List, Optional, Sequence, TypeVar
33

4+
from hail.expr import ArrayStructExpression
5+
from hail.expr import enumerate as hl_enumerate
6+
47
A = TypeVar('A')
58

69

@@ -24,5 +27,9 @@ def select(keys: List[str], **kwargs) -> List[Optional[Any]]:
2427
return [kwargs.get(k) for k in keys]
2528

2629

30+
def annotate_index(arr: ArrayStructExpression) -> ArrayStructExpression:
31+
return hl_enumerate(arr).map(lambda t: t[1].annotate(idx=t[0]))
32+
33+
2734
def init_logging(file=None):
2835
logging.basicConfig(format="%(asctime)-15s: %(levelname)s: %(message)s", level=logging.INFO, filename=file)

hail/python/benchmark/tools/compare.py

Lines changed: 0 additions & 146 deletions
This file was deleted.

0 commit comments

Comments
 (0)