Skip to content
← Back

src/syntax/process/compile-expressions/retry_site_stats.ghul

1
namespace Syntax.Process is
2
use Ghul.Disposable
3
use Ghul.Pipes
4
5
use Collections.LIST
6
use Collections.MAP
7
use Collections.SET
8
9
enum RetrySiteKind is
10
// Re-walks a subtree under a narrower expected type or other
11
// information an earlier walk produced.
12
REWALK_WITH_INFORMATION,
13
14
// Re-walks a subtree under a different reading or lowering.
15
ALTERNATIVE,
16
17
// Opens a speculation level that the sites above roll back;
18
// walks nothing a second time.
19
DIAGNOSTIC_SCOPE
20
si
21
22
// Prototype measurement, printed alongside RETRY_STATS when
23
// GHUL_RETRY_STATS is set: how often each local re-walk in
24
// compile-expressions fires, how deeply the re-walks nest, and how
25
// many re-walks each body pays for under its whole-body walks.
26
// GHUL_RETRY_STATS_LOG names a file each firing and each body is
27
// appended to, one tab-separated line apiece, for aggregating
28
// several builds.
29
class RETRY_SITE_STATS is
30
enabled: bool static
31
_log: IO.TextWriter? static
32
33
_depth: int static
34
_information_depth: int static
35
36
_sites: MAP[string, RETRY_SITE_TOTALS] static
37
_site_order: LIST[string] static
38
_bodies: LIST[RETRY_BODY_TALLY] static
39
_finished: LIST[RETRY_BODY_TALLY] static
40
_next_body: int static
41
42
init() static is
43
enabled = System.Environment.get_environment_variable("GHUL_RETRY_STATS")?
44
_sites = MAP[string, RETRY_SITE_TOTALS]()
45
_site_order = LIST[string]()
46
_bodies = LIST[RETRY_BODY_TALLY]()
47
_finished = LIST[RETRY_BODY_TALLY]()
48
49
let path = System.Environment.get_environment_variable("GHUL_RETRY_STATS_LOG")
50
51
if enabled /\ path? then
52
let writer = IO.StreamWriter(path, true)
53
writer.auto_flush = true
54
_log = writer
55
fi
56
si
57
58
// Call immediately before a site rolls back to re-walk; the
59
// re-walk is counted as nested inside every other re-walk whose
60
// scope has not yet been disposed.
61
enter(site: string, kind: RetrySiteKind) -> RETRY_SITE_SCOPE static is
62
if Semantic.INFERENCE_TRACE.enabled then
63
Semantic.INFERENCE_TRACE.rewalk(site, "{kind}")
64
fi
65
66
if !enabled then
67
return RETRY_SITE_SCOPE(false, false)
68
fi
69
70
let is_information = kind == RetrySiteKind.REWALK_WITH_INFORMATION
71
72
_depth = _depth + 1
73
74
if is_information then
75
_information_depth = _information_depth + 1
76
fi
77
78
_record(site, kind, _depth)
79
80
if _bodies.count > 0 then
81
let body = _bodies[_bodies.count - 1]
82
83
body.rewalks = body.rewalks + 1
84
body.rewalks_this_walk = body.rewalks_this_walk + 1
85
86
if body.rewalks_this_walk > body.max_rewalks_per_walk then
87
body.max_rewalks_per_walk = body.rewalks_this_walk
88
fi
89
90
if _depth > body.max_depth then
91
body.max_depth = _depth
92
fi
93
94
if is_information then
95
body.information_rewalks = body.information_rewalks + 1
96
body.information_this_walk = body.information_this_walk + 1
97
98
if body.information_this_walk > body.max_information_per_walk then
99
body.max_information_per_walk = body.information_this_walk
100
fi
101
102
103
if _information_depth > body.max_information_depth then
104
body.max_information_depth = _information_depth
105
fi
106
fi
107
fi
108
109
return RETRY_SITE_SCOPE(true, is_information)
110
si
111
112
// A diagnostic scope walks nothing twice, so it is counted but
113
// does not nest.
114
note(site: string) static is
115
if !enabled then
116
return
117
fi
118
119
_record(site, RetrySiteKind.DIAGNOSTIC_SCOPE, _depth)
120
si
121
122
leave(is_information: bool) static is
123
_depth = _depth - 1
124
125
if is_information then
126
_information_depth = _information_depth - 1
127
fi
128
si
129
130
begin_body() -> int static is
131
if !enabled then
132
return -1
133
fi
134
135
let id = _next_body
136
_next_body = _next_body + 1
137
138
_bodies.add(RETRY_BODY_TALLY(id))
139
140
return id
141
si
142
143
begin_walk() static is
144
if !enabled \/ _bodies.count == 0 then
145
return
146
fi
147
148
_bodies[_bodies.count - 1].rewalks_this_walk = 0
149
_bodies[_bodies.count - 1].information_this_walk = 0
150
si
151
152
end_body(id: int, walks: int, function: Semantic.Symbols.Function?, location: Source.LOCATION) static is
153
if !enabled \/ _bodies.count == 0 then
154
return
155
fi
156
157
// A body whose walk threw leaves its tally behind; drop it.
158
while _bodies.count > 1 /\ _bodies[_bodies.count - 1].id != id do
159
_bodies.remove_at(_bodies.count - 1)
160
od
161
162
let body = _bodies[_bodies.count - 1]
163
_bodies.remove_at(_bodies.count - 1)
164
165
body.walks = walks
166
body.name = "{if function? then function.qualified_name else "<no symbol>" fi} at {location}"
167
168
for site in body.sites do
169
_sites[site].bodies = _sites[site].bodies + 1
170
od
171
172
_finished.add(body)
173
174
if let log = _log then
175
log.write_line("body\t{body.name}\t{body.walks}\t{body.rewalks}\t{body.max_rewalks_per_walk}\t{body.max_depth}\t{body.information_rewalks}\t{body.max_information_depth}\t{body.max_information_per_walk}")
176
fi
177
si
178
179
_record(site: string, kind: RetrySiteKind, depth: int) static is
180
if !_sites.contains_key(site) then
181
_sites[site] = RETRY_SITE_TOTALS(kind)
182
_site_order.add(site)
183
fi
184
185
let t = _sites[site]
186
187
t.firings = t.firings + 1
188
t.depth_sum = t.depth_sum + cast long(depth)
189
190
if depth > t.max_depth then
191
t.max_depth = depth
192
fi
193
194
let body_name =
195
if _bodies.count > 0 then
196
let body = _bodies[_bodies.count - 1]
197
198
if !body.sites.contains(site) then
199
body.sites.add(site)
200
fi
201
202
"{body.id}"
203
else
204
t.outside_bodies = t.outside_bodies + 1
205
"-"
206
fi
207
208
if let log = _log then
209
log.write_line("site\t{kind}\t{site}\t{body_name}\t{depth}")
210
fi
211
si
212
213
report() static is
214
if !enabled \/ _finished.count == 0 then
215
return
216
fi
217
218
let error = IO.Std.error
219
220
error.write_line("retry site stats: {_finished.count} bodies")
221
error.write_line(" kind\tsite\tfirings\tbodies\toutside-body\tmax-depth\tmean-depth")
222
223
for site in _site_order do
224
let t = _sites[site]
225
let mean = cast double(t.depth_sum) / cast double(t.firings)
226
227
error.write_line(" {t.kind}\t{site}\t{t.firings}\t{t.bodies}\t{t.outside_bodies}\t{t.max_depth}\t{mean:F2}")
228
od
229
230
let with_rewalks = LIST[RETRY_BODY_TALLY]()
231
let with_information = LIST[RETRY_BODY_TALLY]()
232
let repeated mut = 0
233
let repeated_with_rewalks mut = 0
234
235
for body in _finished do
236
if body.rewalks > 0 then
237
with_rewalks.add(body)
238
fi
239
240
if body.information_rewalks > 0 then
241
with_information.add(body)
242
fi
243
244
if body.walks > 1 then
245
repeated = repeated + 1
246
247
if body.rewalks > 0 then
248
repeated_with_rewalks = repeated_with_rewalks + 1
249
fi
250
fi
251
od
252
253
error.write_line("retry site stats: {with_rewalks.count} bodies with a local re-walk, {with_information.count} with a re-walk with information")
254
error.write_line("retry site stats: {repeated} bodies took more than one outer walk, {repeated_with_rewalks} of them with a local re-walk")
255
256
_write_products("outer walks x most local re-walks in one walk", with_rewalks, body => body.walks * body.max_rewalks_per_walk)
257
_write_products("outer walks x most re-walks with information in one walk", with_information, body => body.walks * body.max_information_per_walk)
258
259
error.write_line(" heaviest bodies: walks\tre-walks\tmost-in-one-walk\tmax-depth\tinformation-re-walks\tmost-information-in-one-walk\tmax-information-depth\tname")
260
261
let remaining = LIST[RETRY_BODY_TALLY](with_rewalks)
262
263
for _ in 0..25 do
264
if remaining.count == 0 then
265
break
266
fi
267
268
let heaviest mut = 0
269
270
for i in 1..remaining.count do
271
if _weight(remaining[i]) > _weight(remaining[heaviest]) then
272
heaviest = i
273
fi
274
od
275
276
let body = remaining[heaviest]
277
remaining.remove_at(heaviest)
278
279
error.write_line(" {body.walks}\t{body.rewalks}\t{body.max_rewalks_per_walk}\t{body.max_depth}\t{body.information_rewalks}\t{body.max_information_per_walk}\t{body.max_information_depth}\t{body.name}")
280
od
281
si
282
283
_weight(body: RETRY_BODY_TALLY) -> long static =>
284
cast long(body.walks * body.max_rewalks_per_walk) * 100000L + cast long(body.rewalks)
285
286
_write_products(title: string, bodies: LIST[RETRY_BODY_TALLY], product: RETRY_BODY_TALLY -> int) static is
287
let products = MAP[int, int]()
288
289
for body in bodies do
290
let p = product(body)
291
let count mut = 0
292
products.try_get_value(p, count ref)
293
products[p] = count + 1
294
od
295
296
let keys = LIST[int](products.keys)
297
keys.sort()
298
299
IO.Std.error.write_line(" {title}:")
300
301
for k in keys do
302
IO.Std.error.write_line(" {k}: {products[k]}")
303
od
304
si
305
si
306
307
struct RETRY_SITE_SCOPE: Disposable is
308
_active: bool
309
_is_information: bool
310
311
init(active: bool, is_information: bool) is
312
_active = active
313
_is_information = is_information
314
si
315
316
dispose() is
317
if _active then
318
RETRY_SITE_STATS.leave(_is_information)
319
fi
320
si
321
si
322
323
class RETRY_SITE_TOTALS is
324
kind: RetrySiteKind public
325
firings: int public
326
bodies: int public
327
outside_bodies: int public
328
max_depth: int public
329
depth_sum: long public
330
331
init(kind: RetrySiteKind) is
332
self.kind = kind
333
si
334
si
335
336
class RETRY_BODY_TALLY is
337
id: int public
338
name: string public
339
walks: int public
340
rewalks: int public
341
rewalks_this_walk: int public
342
max_rewalks_per_walk: int public
343
max_depth: int public
344
information_rewalks: int public
345
information_this_walk: int public
346
max_information_per_walk: int public
347
max_information_depth: int public
348
sites: SET[string] public
349
350
init(id: int) is
351
self.id = id
352
name = ""
353
sites = SET[string]()
354
si
355
si
356
si