diff options
Diffstat (limited to 'experiments')
| -rw-r--r-- | experiments/pg-09.png | bin | 303363 -> 302306 bytes | |||
| -rw-r--r-- | experiments/pg-10.png | bin | 66687 -> 220988 bytes | |||
| -rw-r--r-- | experiments/pg-11.png | bin | 0 -> 255697 bytes | |||
| -rw-r--r-- | experiments/report.typ | 105 |
4 files changed, 97 insertions, 8 deletions
diff --git a/experiments/pg-09.png b/experiments/pg-09.png Binary files differindex 704b63d..d076622 100644 --- a/experiments/pg-09.png +++ b/experiments/pg-09.png diff --git a/experiments/pg-10.png b/experiments/pg-10.png Binary files differindex 9a76b56..ec8372b 100644 --- a/experiments/pg-10.png +++ b/experiments/pg-10.png diff --git a/experiments/pg-11.png b/experiments/pg-11.png Binary files differnew file mode 100644 index 0000000..26069e3 --- /dev/null +++ b/experiments/pg-11.png diff --git a/experiments/report.typ b/experiments/report.typ index 1601cef..0403a1a 100644 --- a/experiments/report.typ +++ b/experiments/report.typ @@ -839,23 +839,112 @@ here — the grapheme walk, the per-cell writes, the fill — returned between n 21 µs, because the cost is not in any one of those but in doing the whole frame again. Nothing in this repository can avoid work pardes has already done. += Experiment 5 --- how big can the window be + +The 40×12 grid was never a decision about the screen. It was memory, and the constraint was +four copies of every cell: vaxis keeps a `Screen` and an `InternalScreen`, pardes keeps its +`Surface`, and the shell keeps a shadow of it to diff against. Two of those four became dead +weight the moment the emitter started writing the wire itself — allocated every session, +never read. Sizing them to *one cell* removes the memory ceiling outright: the heap now +reports 336 KB free at every geometry tried, including ones that used to fail outright. + +What is left is the honest limit. Every frame walks the whole grid, so the round trip is +linear in the cell count at *0.87 µs per cell*, measured. + +#figure( + table( + columns: (auto, auto, auto, 1fr), + align: (left, right, right, left), + stroke: none, + table.hline(), + table.header([geometry], [cells], [round trip], []), + table.hline(stroke: 0.5pt), + [40×12], [480], [3 628 µs], [the old ceiling], + [*56×14*], [*784*], [*3 930 µs*], [*the new default: +63% area, +40% width*], + [56×16], [896], [3 965 µs], [over 4 ms on the phase-randomised instrument], + [60×18], [1 080], [4 114 µs], [], + [64×20], [1 280], [4 281 µs], [], + [80×24], [1 920], [4 809 µs], [the classic terminal, one flag away], + [100×30], [3 000], [5 743 µs], [], + [120×36], [4 320], [6 923 µs], [], + [140×42], [5 880], [8 310 µs], [the largest that runs at all], + [160×48], [7 680], [---], [links, then traps at boot], + [200×60], [12 000], [---], [does not link], + table.hline(), + ), + caption: [`-Dp4-cols` / `-Dp4-rows`, because none of this is a constant. The two failures at + the bottom are different failures and both are worth naming: 200×60 is refused by the + linker — `.bss will not fit in region l2mem, overflowed by 76 036 bytes`, that `.bss` being + the shadow grid, sized at compile time — while 160×48 links and then traps, the same + pressure arriving at runtime as a collision instead of as a diagnostic. Neither is a heap + problem any more, which is the surprise: the heap has 336 KB spare while `.bss` runs out.], +) + += Three bugs the latency work walked straight past + +All three were invisible to every number in this document, and two of them had been there +since the port began. + +*Keystrokes were being lost.* Reported as "a key is stuck and is only sent when I send a new +event". It was neither stuck nor late. The loop is read, apply, render, write, and the write +blocks while the transmit FIFO is full — real backpressure, and it should stay, because half +an escape sequence leaves the host terminal in the wrong colour for the rest of the session. +But nothing drained the RECEIVE FIFO during that wait, and that FIFO is 128 bytes, or 11 ms +of wire. Measured by counting what the firmware's loop actually took off the UART: a 200-byte +burst arrived as 197, a 300 as 257, a 600 as 478. Draining the receiver inside the wait fixes +the first window; a second one — applying the input, which costs 44 µs a keystroke on an empty +line and 63 µs at 640 characters — needed the loop to hand the editor eight bytes at a time +instead of a hundred and twenty-eight. With both, 4 096 bytes in a single write arrive intact, +and past that the loss is *counted* rather than silent. + +*Every escape sequence was being shredded.* `vaxis.Parser` resolves a buffer containing +nothing but `0x1b` as the Escape key — deliberately, and correctly for a terminal, where the +kernel hands over a whole sequence in one read. This wire hands over one byte at a time, 87 µs +apart, so the first byte of every sequence arrived alone and was resolved as Escape and the +rest arrived as ordinary keys. A mouse click came through as ten key presses, and the `0` +among them is "go to column zero" in normal mode — which is exactly where the cursor kept +landing, and why this looked at first like a coordinate bug. Arrow keys, function keys and the +host's own in-band resize reports were all being taken apart the same way. The shell now holds +a lone ESC for 10 ms, two orders of magnitude longer than the wire needs and imperceptible to +the person pressing it. + +Finding it took instrumenting the ABI to print the tag of every event the shell applied. Ten +`key_press` where one `mouse` belonged is not something any amount of reading the coordinate +arithmetic would have shown, and it had been read twice. + +*The mouse was never enabled.* With the sequences intact, that was one line: the shell already +handled mouse events, because it mirrors the tty shell. Spelled out rather than taken from +`vx.setMouseMode`, which asks for `1002;1003;1004;1006` — 1003 being any-motion tracking, a +report per cell the pointer crosses with no button held. On this line that is dozens of +15-byte reports for one sweep, arriving as input the editor must parse while it paints, and +arriving whether anyone wants it or not. The console thins drag reports to 25 a second on the +way in, newest-wins, which is right for motion and only for motion: where the pointer passed +through is not information an editor can use. + + #figure( table( columns: (auto, 1fr), align: (left, left), stroke: none, table.hline(), - table.header([found], [not fixed]), + table.header([found], [outcome]), table.hline(stroke: 0.5pt), - [`vx.resize` fails], [A runtime geometry change takes its allocation-failure path, - restores the previous size and returns: 80 bytes go out where 1 392 should, and - the screen keeps its old shape. Reproduced with the shadow grid compiled out, so - it predates it. The board has one geometry per session — which is also why the - staleness test compares two firmwares rather than forcing a repaint by resizing.], + [`vx.resize` fails], [A runtime geometry change took its allocation-failure path, restored + the previous size and returned: 80 bytes went out where 1 392 should, and the screen kept + its old shape. Reproduced with the shadow grid compiled out, so it predated it, and left + unrepaired through three experiments --- which is also why the staleness test compares two + firmwares rather than forcing a repaint by resizing. + + *Fixed, and not on purpose.* The failing allocation was vaxis's two grids, and the emitter + stopped needing them; there is nothing left to reallocate, so the path that failed is gone + rather than repaired. Verified on the die: an in-band report re-lays the screen from 56 + columns to 30 and back. The pleasant kind of consequence, and worth recording as a + consequence rather than as a fix --- nobody set out to repair it.], table.hline(), ), - caption: [A bug the optimisation work walked into and characterised but did not - repair.], + caption: [A bug that three experiments characterised and did not repair, removed by a change + aimed at something else entirely.], ) = Ranked by measured benefit |
