Repository navigation
Conversation
a41e775 to
ae35362
Compare
…d too bendlang#1385 brought main's comp.ts to 63,975 ttok, which put this branch at 64,017. These rewrites take 38 back, with the same output: js_host moves into js_lib, its one caller, with the js_marshal calls in the same order; six OPERATIONS entries with a short C template take two lines, not four; lay_box, val_box and val_unbox read lay.arms as truthiness, since it is a record when present. The branch is at 63,979, and merged with bendlang#1386 (which takes its own share) at 63,989.
|
Thanks @Lorenzobattistela! Added 9377930 on top of your rebase: 30 more ttok of rewrites of the same kinds, same output.
|
|
Thanks @Iann29, and thanks for chasing the token cap. The compile_book change looks good, but we can't merge the trimming commits (ae35362 and 9377930) as they are. They save tokens by writing comp.ts in a style the rest of the repo doesn't use. bend.ts is our reference for style, and these commits move away from it:
Behavior is the same, so this is only about style, but the cap only does its job if we stay under it with simplifications that read at least as well. Where things stand: main is now at 63,848 ttok after #1395, which removed 443 tokens. Your compile_book commit (8c94532) adds about 317 tokens. It also conflicts with main, because #1395 rewrote compile_book as one loop. Could you drop the two trimming commits, rebase the feature onto current main, and find the missing room with real simplifications (dead code, duplicated logic, helpers that can be shared) instead of compressing code? We're also discussing whether to raise the token cap, so if you prefer, you can wait for that decision before doing the cuts. We'll review once the gate passes. |
9377930 to
58065f1
Compare
Lorenzobattistela
left a comment
There was a problem hiding this comment.
We measured this PR against #1262 (C emission memory) on a synthetic 27.6k-line program (2,634 defs, 14.4 MB of C). The C is byte-identical and it emits about 21% fewer defs, about 10% faster, but peak memory goes up by 40-65%:
| program | main (dbb3157) | main + this PR |
|---|---|---|
| 18k lines | 1.56 GB / 4.5 s | 2.53-2.61 GB / 4.0 s |
| 27k lines | 2.18-2.28 GB / 6.5 s | 3.08-3.61 GB / 5.8 s |
| 36k lines | 2.89 GB / 8.6 s | 3.80 GB / 7.9 s |
The cause is the traces: Facts.has pushes a closure for every missed lookup, so the same key missed many times by one def is recorded many times. At 27k lines that's 1.84M closures after the first pass.
Could you record each missed key only once per trace (keyed by set and key)? Something like:
if (!got && TRACE) {
const id = (this === FL.hot ? "h" : "s") + k;
if (!TRACE.seen.has(id)) { TRACE.seen.add(id); TRACE.miss.push(() => super.has(k)); }
}with seen: new Set() added where the trace is made. Locally that drops misses at 27k from 1.84M to 73k, brings peak back to about main's (2.0 vs 2.2 GB), and keeps the speedup and the byte-identical C at every size we tried.
A def's emission reads facts through FL.hot.has, FL.stat.has and its callees' borrow rows. hot and stat now note each miss of the def being emitted (Facts), and brw_of notes the rows it hands out with the own facts that would clear their borrowed slots. In a later pass, a def none of whose misses was added would emit what it did, so it is replayed instead: its row asks and its lends, in its place. After a pass that adds no fact, one more pass emits every def, and its C is the result. The AMAGE Eco demo's 7 passes of 1,253 defs become 1,253, 911, 263, 62, 16, 8 and 5, then 1,253, with the same C.
A def looks up the same key in hot and stat many times, and Facts.has pushed a closure at every miss. On three copies of the repo's demos (28.1k lines) the traces held 1.83M closures after the first pass, and the peak of -o main.c went from main's 1.92 GB to 2.93 GB. A trace now keeps the keys it missed, by set and key, and pushes one closure per key: 73k after the first pass, and a 1.87 GB peak. The closures it drops answered the same question as the one it keeps, so a def is emitted again exactly when it was before, and the passes and the C are the same.
58065f1 to
f935178
Compare
|
Thanks @Lorenzobattistela, for the restyle and for tracking the peak down to the traces. The dedupe is in as you suggested, in f935178: The branch is rebased on main (e5527d5), over #1617, with your two commits unchanged. The one conflict was the lend graph at the end of a pass, which main now builds with I rebuilt your synthetic program from #1262's description: copies of the repo's 13 demos whose
The heap left after a full collection at the end of the first pass shows the same without the collector's timing: at 28.1k lines, 301 MB on main, 565 MB before the dedupe and 320 MB with it. The synthetic programs take 5 passes on main, and this PR emits 21% fewer defs (10,477 instead of 13,280 at 28.1k lines). The Eco demo takes 8 passes of 2,520 defs on main and emits 7,780 here. Its traces are small (191k misses after the first pass, 52k with the dedupe; 461 and 449 MB left after that collection, 435 MB on main), so its peak only follows the collector: 2.03-2.81 GB over the 13 runs, and 2.41-3.80 GB on main. Same output:
|
|
Thanks @Iann29, for this and for the dedupe follow-up. We reproduced it on current main (257f896): byte-identical C, about 13-15% faster on a synthetic program built from copies of the demos, and peak memory now matches main. We're closing it in favor of #1601, which tracks the same facts at the level of each def and spin: a missing fact records who read it, and adding it re-emits only those readers, callee-first. On the same synthetic it emits about 3.4x faster than its base with 25% less peak memory, and it replaces the per-pass loop this PR hooks into, so the two can't both land. comp.ts would also be at 66,309 ttok with this PR, over the 66k cap. Your measurements (the Eco numbers and the miss counts per pass) were useful in judging #1601. Thanks again. |
compile_bookemits every live def in every pass until a pass adds no fact (own,hot,stat). Big programs take several passes, and each one re-emits everything, although after the first pass or two only a few defs see a new fact. This PR emits a def again only when a fact it missed was added. The C output is the same.How
hotandstatare aSetsubclass,Facts, whosehasrecords each miss of the def being emitted (TRACE).brw_ofrecords each row it hands out, and theownfacts that would turn its borrowed slots owned.Foreign defs, which main now emits in the same loop, are traced and skipped the same way.
This branch is the core commit only, rebased on main. The two token-trim commits are dropped.
Same output
.bendfiles undertests/,demos/,bench/andevals/: same exit codes and stderr as main, and every emitted.c(803),.js(833) and.mjs(1,139) is byte-identical.amage-si/chromi,examples/eco/main.bend): byte-identical C (13.3 MB).Speed
bend main.bend -o main.con an Apple M-series Mac, Bun 1.4.0, median of 3 interleaved runs:demos/app_slash_boss_3ddemos/pure_hvm5_miniEco's passes emit 2,526, 1,849, 545, 197, 86, 54, 24 and 2 defs, then the final 2,526 (main: every pass emits all 2,526). Peak memory on Eco: 3.17 GB on main, 2.79 GB here.
Checks
gates/repo.ts54/54,comp.tsat 63,998 ttok (cap 64,000)gates/test.ts1623/1623,gates/ping.ts58/58,gates/perf.ts124/124