Skip to content

Commit e06b0cf

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

File tree

16 files changed

+1241
-296
lines changed

16 files changed

+1241
-296
lines changed

hail/python/benchmark/conftest.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,34 @@
1010

1111
@pytest.hookimpl
1212
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(
13+
group = parser.getgroup('benchmark')
14+
group.addoption("--log", type=str, help='Log file path', default=None)
15+
group.addoption("--output", type=str, help="Output file path.", default=None)
16+
group.addoption("--data-dir", type=str, help="Data directory.", default=os.getenv('HAIL_BENCHMARK_DIR'))
17+
group.addoption(
18+
"--burn-in-iterations", type=int, help="override number of burn-in iterations for all benchmarks", default=None
19+
)
20+
group.addoption('--iterations', type=int, help='override number of iterations for all benchmarks', default=None)
21+
group.addoption('--cores', type=int, help='Number of cores to use.', default=1)
22+
group.addoption(
1923
'--profile',
2024
choices=['cpu', 'alloc', 'itimer'],
2125
help='Run with async-profiler.',
2226
nargs='?',
2327
const='cpu',
2428
default=None,
2529
)
26-
parser.addoption(
30+
group.addoption(
2731
'--max-duration',
2832
type=int,
2933
help='Maximum permitted duration for any benchmark trial in seconds, not to be confused with pytest-timeout',
3034
default=200,
3135
)
32-
parser.addoption('--max-failures', type=int, help='Stop benchmarking item after this many failures', default=3)
33-
parser.addoption(
36+
group.addoption('--max-failures', type=int, help='Stop benchmarking item after this many failures', default=3)
37+
group.addoption(
3438
'--profiler-path', type=str, help='path to aysnc profiler', default=os.getenv('ASYNC_PROFILER_HOME')
3539
)
36-
parser.addoption('--profiler-fmt', choices=['html', 'flame', 'jfr'], help='Choose profiler output.', default='html')
40+
group.addoption('--profiler-fmt', choices=['html', 'flame', 'jfr'], help='Choose profiler output.', default='html')
3741

3842

3943
@pytest.hookimpl
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: 23 additions & 24 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,
@@ -120,38 +122,33 @@ def pytest_runtest_protocol(item, nextitem):
120122
# - our means of getting max task memory is quite crude (regex on logs)
121123
# and a fresh session provides a new log
122124
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-
)
125+
if (nburn_in_iterations := item.config.getoption('burn_in_iterations')) is not None:
126+
logging.info(f'override: using {nburn_in_iterations} burn-in-iterations.')
132127

133-
else:
134-
burn_in_iterations = 1
135-
num_iterations = 5
128+
if (niterations := item.config.getoption('iterations')) is not None:
129+
logging.info(f'override: using {niterations} iterations.')
136130

137-
s = item.stash
138-
s[start] = datetime.now(timezone.utc).isoformat()
139-
s[iterations] = []
140-
s[consecutive_fail_count] = 0
141-
s[end] = None
131+
nburn_in_iterations = nburn_in_iterations or 3
132+
niterations = niterations or 5
142133

143134
logging.info(
144135
msg=(
145136
f'Executing "{item.nodeid}" with '
146-
f'{burn_in_iterations} burn in iterations and '
147-
f'{num_iterations} timed iterations.'
148-
)
137+
f'{nburn_in_iterations} burn in iterations and '
138+
f'{niterations} timed iterations.'
139+
),
149140
)
150141

142+
s = item.stash
143+
s[start] = datetime.now(timezone.utc).isoformat()
144+
s[iterations] = []
145+
s[consecutive_fail_count] = 0
146+
s[end] = None
147+
151148
max_failures = item.config.getoption('max_failures')
152149

153150
s[context] = 'burn_in'
154-
for k in range(burn_in_iterations):
151+
for k in range(nburn_in_iterations):
155152
if max_failures and s[consecutive_fail_count] >= max_failures:
156153
break
157154

@@ -165,8 +162,8 @@ def pytest_runtest_protocol(item, nextitem):
165162
runner.pytest_runtest_protocol(item, nextitem=item.parent)
166163

167164
s[context] = 'benchmark'
168-
total_iterations = burn_in_iterations + num_iterations
169-
for k in range(burn_in_iterations, total_iterations):
165+
total_iterations = nburn_in_iterations + niterations
166+
for k in range(nburn_in_iterations, total_iterations):
170167
if max_failures and s[consecutive_fail_count] >= max_failures:
171168
break
172169

@@ -179,7 +176,7 @@ def pytest_runtest_protocol(item, nextitem):
179176

180177
if max_failures and s[consecutive_fail_count] >= max_failures:
181178
logging.error(
182-
msg=(f'Benchmarking "{item.nodeid}" aborted due to too many consecutive failures (max={max_failures})')
179+
msg=f'Benchmarking "{item.nodeid}" aborted due to too many consecutive failures (max={max_failures})',
183180
)
184181

185182
# prevent other plugins running that might invoke the benchmark again
@@ -333,6 +330,8 @@ def new_query_tmpdir(tmp_path):
333330
'many_strings_tsv',
334331
'new_query_tmpdir',
335332
'onekg_chr22',
333+
'onethreethree',
334+
'onethreetwo',
336335
'profile25_mt',
337336
'profile25_vcf',
338337
'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)