Appearance
| 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 |