Skip to content

Commit 39eb23a

Browse files
committed
laaber + schultz's method of estimating slowdown
1 parent 8b3c84a commit 39eb23a

17 files changed

Lines changed: 712 additions & 4653 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
data/
2+
out/
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"This notebook explores variability in hail's python (macro)-benchmarks when\n",
8+
"said benchmarks are executed on the hail batch service. The analyses within \n",
9+
"are based off the methods proposed in [1], albeit slightly modified for long\n",
10+
"running benchmarks. The goals of these analyses are\n",
11+
"\n",
12+
"- to determine if we can detect slowdowns of 5% or less reliably when running\n",
13+
" benchmarks on hail batch.\n",
14+
"- to identify configurations (number of batch jobs x iterations) that allow us\n",
15+
" to detect slowdowns efficiently (ie without excesssive time and money).\n",
16+
"\n",
17+
"[1] Laaber et al., Software Microbenchmarking in the Cloud.How Bad is it Really?\n",
18+
" https://dl.acm.org/doi/10.1007/s10664-019-09681-1"
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": null,
24+
"metadata": {},
25+
"outputs": [],
26+
"source": [
27+
"from pathlib import Path\n",
28+
"\n",
29+
"from benchmark.tools.impex import import_timings\n",
30+
"from benchmark.tools.plotting import plot_mean_time_per_instance, plot_trial_against_time\n",
31+
"from benchmark.tools.statistics import (\n",
32+
" analyze_benchmarks,\n",
33+
" bootstrap_mean_confidence_interval,\n",
34+
" laaber_mds,\n",
35+
" schultz_mds,\n",
36+
" variability,\n",
37+
")\n",
38+
"from plotly.io import renderers\n",
39+
"\n",
40+
"import hail as hl\n",
41+
"\n",
42+
"renderers.default = 'notebook_connected'"
43+
]
44+
},
45+
{
46+
"cell_type": "code",
47+
"execution_count": null,
48+
"metadata": {},
49+
"outputs": [],
50+
"source": [
51+
"hl.init(backend='spark', idempotent=True)\n",
52+
"hl._set_flags(use_new_shuffle='1', lower='1')"
53+
]
54+
},
55+
{
56+
"cell_type": "code",
57+
"execution_count": null,
58+
"metadata": {},
59+
"outputs": [],
60+
"source": [
61+
"# Import benchmark data\n",
62+
"# ---------------------\n",
63+
"#\n",
64+
"# benchmarks under `hail/python/benchmarks` are executed with a custom pytest\n",
65+
"# plugin and their results are output as json lines (.jsonl).\n",
66+
"# Unscrupulously, we use hail to analyse itself.\n",
67+
"\n",
68+
"# Plotting the time vs iteration for all instances provides a visual way of\n",
69+
"# identifying the number of burn-in iteration required to reach a steady-state.\n",
70+
"# Note that a steady state is never reached in some cases.\n",
71+
"\n",
72+
"ht = import_timings(Path('data/100x120.jsonl'))\n",
73+
"ht = ht.checkpoint('out/imported.ht', overwrite=True)\n",
74+
"benchmarks = ht.aggregate(hl.agg.collect_as_set(ht.name))\n",
75+
"print(*benchmarks, sep='\\n')\n",
76+
"plot_trial_against_time(ht)"
77+
]
78+
},
79+
{
80+
"cell_type": "code",
81+
"execution_count": null,
82+
"metadata": {},
83+
"outputs": [],
84+
"source": [
85+
"# This is an iterative process. Select the minimum number of burn-in iterations\n",
86+
"# required for each benchmark. Replot and verify that the graph is more-or-less\n",
87+
"# flat. This may not be possible in all cases.\n",
88+
"\n",
89+
"\n",
90+
"def filter_burn_in_iterations(ht: hl.Table) -> hl.Table:\n",
91+
" ht = ht.annotate_globals(\n",
92+
" first_stable_index={\n",
93+
" 'benchmark_export_range_matrix_table_row_p100': 20,\n",
94+
" 'benchmark_import_gvcf_force_count': 10,\n",
95+
" 'benchmark_matrix_table_take_col': 30,\n",
96+
" 'benchmark_ndarray_matmul_int64': 23,\n",
97+
" 'benchmark_sample_qc': 14,\n",
98+
" 'benchmark_shuffle_key_rows_by_mt': 10,\n",
99+
" 'benchmark_union_partitions_table[100-100]': 40,\n",
100+
" },\n",
101+
" )\n",
102+
"\n",
103+
" return ht.select(\n",
104+
" instances=ht.instances.map(\n",
105+
" lambda instance: instance.annotate(\n",
106+
" trials=(instance.trials.filter(lambda t: t.iteration >= ht.first_stable_index[ht.name]))\n",
107+
" )\n",
108+
" ),\n",
109+
" )\n",
110+
"\n",
111+
"\n",
112+
"ht = filter_burn_in_iterations(ht)\n",
113+
"plot_trial_against_time(ht)"
114+
]
115+
},
116+
{
117+
"cell_type": "code",
118+
"execution_count": null,
119+
"metadata": {},
120+
"outputs": [],
121+
"source": [
122+
"# As a final step of cleaning, we'll filter out trials that differ by some\n",
123+
"# multiplier of the median for each instance\n",
124+
"\n",
125+
"\n",
126+
"def filter_outliers(ht: hl.Table, factor: hl.Float64Expression) -> hl.Table:\n",
127+
" # Filter out failures and\n",
128+
" return ht.select(\n",
129+
" instances=ht.instances.map(\n",
130+
" lambda instance: instance.annotate(\n",
131+
" trials=hl.bind(\n",
132+
" lambda median: instance.trials.filter(\n",
133+
" lambda t: hl.max([t.time, median]) / hl.min([t.time, median]) < factor\n",
134+
" ),\n",
135+
" hl.median(instance.trials.map(lambda t: t.time)),\n",
136+
" )\n",
137+
" ),\n",
138+
" ),\n",
139+
" )\n",
140+
"\n",
141+
"\n",
142+
"ht = filter_outliers(ht, hl.float64(10))\n",
143+
"plot_trial_against_time(ht)"
144+
]
145+
},
146+
{
147+
"cell_type": "code",
148+
"execution_count": null,
149+
"metadata": {},
150+
"outputs": [],
151+
"source": [
152+
"# These plots show the mean time per instance. This provides a visual way of\n",
153+
"# identifying differences in instance type if there are multiple distinct layers\n",
154+
"\n",
155+
"plot_mean_time_per_instance(ht)"
156+
]
157+
},
158+
{
159+
"cell_type": "code",
160+
"execution_count": null,
161+
"metadata": {},
162+
"outputs": [],
163+
"source": [
164+
"ht = ht.select(instances=ht.instances.trials.time).checkpoint('out/pruned.ht', overwrite=True)"
165+
]
166+
},
167+
{
168+
"cell_type": "code",
169+
"execution_count": null,
170+
"metadata": {},
171+
"outputs": [],
172+
"source": [
173+
"# laaber et al. section 4\n",
174+
"\n",
175+
"variability(ht).show()"
176+
]
177+
},
178+
{
179+
"cell_type": "code",
180+
"execution_count": null,
181+
"metadata": {},
182+
"outputs": [],
183+
"source": [
184+
"# laaber et al. section 5 - boostrapping confidence intervals of the mean\n",
185+
"\n",
186+
"bootstrap_mean_confidence_interval(ht, 1000, 0.95).show()"
187+
]
188+
},
189+
{
190+
"cell_type": "code",
191+
"execution_count": null,
192+
"metadata": {},
193+
"outputs": [],
194+
"source": [
195+
"# Laaber et al - Minimal-Detectable Slowdown\n",
196+
"\n",
197+
"laaber = laaber_mds(ht).checkpoint('out/laaber-mds.ht', overwrite=True)\n",
198+
"schultz = schultz_mds(ht).checkpoint('out/schultz-mds.ht', overwrite=True)"
199+
]
200+
},
201+
{
202+
"cell_type": "code",
203+
"execution_count": null,
204+
"metadata": {
205+
"slideshow": {
206+
"slide_type": "fragment"
207+
}
208+
},
209+
"outputs": [],
210+
"source": [
211+
"\n",
212+
"mds = laaber.select(laaber=laaber.row_value, schultz=schultz[laaber.key])\n",
213+
"mds.show(100_000)"
214+
]
215+
}
216+
],
217+
"metadata": {
218+
"kernelspec": {
219+
"display_name": ".venv",
220+
"language": "python",
221+
"name": "python3"
222+
},
223+
"language_info": {
224+
"codemirror_mode": {
225+
"name": "ipython",
226+
"version": 3
227+
},
228+
"file_extension": ".py",
229+
"mimetype": "text/x-python",
230+
"name": "python",
231+
"nbconvert_exporter": "python",
232+
"pygments_lexer": "ipython3",
233+
"version": "3.9.18"
234+
}
235+
},
236+
"nbformat": 4,
237+
"nbformat_minor": 4
238+
}

0 commit comments

Comments
 (0)