Repository navigation
move _data from BufferLine to new LogicalLine class - #5797
PerBothner wants to merge 22 commits into
Conversation
Instead to change isWrapped state use a Buffer.setWrapped method. This allows for potential flexibility in how BufferLine and line-wrapping are implemented.
A BufferLine is now the sub-range of a LogicalLine for a specific visible line, while LogicalLine is independent of window width.
|
Note this PR assumes PR #5788 has been merged first. |
|
FWIW, I'm looking into adding (A possible future improvement I'm thinking about is to store the actual values of |
|
I merged from upstream/master (with some effort thanks to the new string aching). However, the image addon breaks the BuferLine abstraction. I'll take a look at that shortly. I may focus on fixing that in the context PR #5853, which I haven't merged yet. (I would also like to further simplify the reflow logic, which I hope allow me fix the remaining testsuite failures.) |
|
The integration failures are because Compared to PR #5853, this PR is quite conservative. It does not make changes to Markers. For buffer resize/reflow the keeps the existing algorithm and much of the same code, but it should be substantially faster because it does not have to copy cells. (PR #5853 makes reflow even faster and simpler.) If preferred, I can create a PR that is "in-betwen" this one and PR #5853 - we can attach Markers to LogicalLines, but leave Decoration handling unchanged. Note the string-cache works on |
|
I will be happy to update this PR the latest master - as soon as I get some feedback about this proposal, and some indications that my contributions are at least considered. As long as I am being totally ignored (see issue #6022) trying to keep this PR updated is a waste of my time. |
|
I just perf tested this PR with the quickfixes from #6106 (applied where applicable):
Is this yet optimized by any means? Because 5x slower is a clear no-go. |
|
Hm. That is disappointing. I have not done any measurement or performance tuning. I can't think of a reason for the slowdown - I'm hoping it is some specific hotspot or screw-up that is causing the regression. I may not have time to investigate today, but hopefully tomorrow. Hoping reflow at least is significant faster (and should be even faster with followup changes). |
|
A quick test finds that |
|
I checked in a tweak to |
|
Yupp this changed the picture massively:
_resizeData is still quite high in the books - could we somehow tweak it to run only once per print invocation? (Background: a print call should not be longer than a logical line, but might be shorter) On a sidenote - what happens, if a console app spits out a 10GB line without any NL in between? |
I though of something similar. It is conceptually easy to bundle a run of "simple" code-points to a single call to something like a An alternative could be to pre-process all the codepoints, storing the the width and combining data into a helper Uint8Array. Then we know the actual width in columns and hence the number of cells needed. However, that is a bigger change than I would like to make right now, partly because changes like these might change how we go about making such a change. I'm inclinded to use a heurtistic (guess). Before the main call to This works exactly if all the characters in the call to |
|
Is there some writeup on recommended ways to test for performance? Perhaps part of or linked to from |
|
For the parser side of input chain you can use this to get a rough number: npm run benchmark out-test/benchmark/Terminal.benchmark.jsIt skips the renderer path, so is just WriteBuffer, parser an terminal buffer handling. If the number are totally off, you have to use your browser with devtools profiler. Current issue there is, that the setTimout/clearTimeout cascades skew the numbers heavily. To get halfway reliable numbers in devtools you'd have to apply half of the quickfixes listed in #6106 (at least the TimeoutTimer fixes). It is annoying, that we are in this situation, but it is what it is. |
|
Possible optimization/trick for scroll: Allocate a fresh Then effectively you only get one allocation for each line, and that allocation is the optimal size. However, each line requires extra copy at the end, but presumably that should be a highly efficient operation. The tradeoffs and the logic becomes more complicated if old lines are frequently updated. Not sure the best strategy to handle that. Possible use different strategies for normal buffer and alternate buffer. |
That sounds very much like what recycle + copyFrom does - it takes the memory of an old bufferline to clear it with a single .set call to empty it. Whether .set empties or pulls over data, is the same speed-wise. But the additional attributes are much more heavy to pull over (the reason why copyFrom slowed down that much, although it does not need to look at additional attributes). But I still wonder - how does LogicalLine deal with overlong lines in MBs or GBs? You would need a sanity upper limit, where lines get artificially split again to not cause OOMs. Wouldn't that negate the whole effort of LogicalLine? |
I believe the scrollback limit will "trim" excess visible (non-logical) lines. So if a line is extremely long it will be wrapped in I'm not sure that using LogicalLine makes much difference in terms of memory - roughly the same amount is used either way. A really long line will require one long At a higher level: Are we really that concerned if the the terminal runs out of memory on being fed GBs of garbage? |
|
Is there a way to run the benchmark inside a non-headless terminal? That might be helpful to find hotspots and otherwise debug issues. |
No that is sadly not possible anymore (a previous version of xterm-benchmark had chrome-timeline package for a browser based runner, but that package is outdated, as the chrome changed the devtools interfaces). The repro steps in #6106 are basically what that benchmark does. the OSC commands are needed to spot the end of data condition. This is needed as long as the code still contains the toxic setTimeout chains, and for closing devtools in between to see the real throughput (the devtool profiler sucks hard on IO heavy benchmarks always screwing up the numbers).
Yes that is where the concern comes from. The ring buffer logic cannot run into that hazard, as it always introduces cuts in memory at terminal width. But since LogicalLine tries to merge things I was just wondering but degraded data. And this can happen pretty easily by catting /dev/urandom or any endless producer into a terminal. Not very helpful to anyone, but a terminal may not crash from that either. |
This is used by InputHandler.print to "batch" multiple characters.
This typically reduces memory usage, allocation, and copying.
Also restored CircularList.recycle, pending possible future use - though the initial attempt actually slowed things down.
Seems slightly faster to recycle BufferLine but not LogicalLine.
The image addon now builds but has some problems still.
I think this is slightly cleaner and possibly faster, since no cloning needed.
|
Fully updated and tests pass. There is still a significant slowdown running That said, I believe this re-factoring will enable some other optimizations:
Let me know if you prefer me to complete to resize/reflow optimization before or after review of this PR. I can also implement the |
This moves cell data from a
BufferLineinto a newLogicalLineclass which contains the actual data for a line. This data does not change ifBufferwidth changes. EachLogicalLineis rendered as one or moreBufferLines, depending on terminal width; conversely eachBufferLinespecifies a sub-range of the parentLogicalLine.This implements issue #5673 .
Some benefits:
Buffer.resize.No need for cleaning up memory after resize.
getBlankLine.LogicalLineis the "model" (conceptual buffer data) while the set ofBufferLinesis the "view" (how the LogicalView is rendered given a specifical line width).Memory cleanup: There is no need for queing up memory-cleanup items when doing a resize, since resize doesn't create garbage (except when lines are trimmed). However, it may be worthwhile doing memory cleanup when lines are erased and/or when lines are unwrapped (commonly done together). We might also cleanup memory when lines move out above
ybase.Merging BufferReflow.ts into Buffer.ts: An optional follow-up would be to merge the rest of
BufferReflow.tsintoBuffer.ts- since there isn't much left inBufferReflow.ts. See this changeset. That would make more slightly more efficient and (in my opinion) more readable code, but I didn't do this in the interest of a more manageable PR.