Hacker News new | ask | show | jobs
by OmarIsmail 3653 days ago
Some more questions on performance:

It looks like you're adding and removing line divs, did you run benchmarks on trying to reuse the line divs and change the contents of them?

By using translate3D you're not using the browser's native scrolling correct? Are you using an open source library to replace the scrollbar? What was the actual performance benefit of using translate3D vs native scrolling?

1 comments

I love these questions!

I've actually never tried to compute a diff and apply it inside a line, maybe I'll try it tomorrow :). A line is basically a list of spans, each having a certain class name and a certain text content. I've just always assumed that iterating over the previous spans, adjusting their class names, adjusting their text content, appending new spans or removing extra left overs would be slower than a big fat innerHTML call (given that each dom read/write access leaves the JS VM and I always thought there's a certain penalty associated with each dom call). But I will definitely try it out!

Here is what we do now - https://github.com/Microsoft/vscode/blob/master/src/vs/edito...

[It might not be the best method, but it was guided by measuring]:

* if there is no overlap between frames (e.g. you jump to a completely different location), the whole thing does a single innerHTML call

* otherwise:

* all the old lines that leave the viewport are removed via multiple domNode.removeChild

* all the new lines that enter the viewport are added via a single domNode.insertAdjacentHTML

* all the old lines that have changed are parsed in one single off-dom innerHTML and then cherry picked via multiple domNode.replaceChild

That is what I could come up with in my attempts to minimize the count of dom calls and not pay for reparsing/repainting the entire viewport on each frame. Maybe there are better ways?

If I remember correctly, we ended up not using native browser scrolling for multiple reasons:

* [if we would not set scrollTop ourselves] the browser would just happily jump to a certain scrollTop, painting nothing (white), then we'd get an `onscroll` and we'd be able to paint the lines. But you'd always get this white flash.

* if we would set scrollTop ourselves:

* AFAIK setting the scrollTop causes a stop the world sort of synchronous layout - I don't know why

* We wanted to have an overview ruler that sits inside the scrollbar and highlights things (find matches, diffs, etc.)

* IE has or had a limit around 2.5M px. That meant we would have had to do something special anyways around 80.000 lines @ 19px line height

The scrollbars are custom implemented (https://github.com/Microsoft/vscode/tree/master/src/vs/base/...). Quick tip: do not implement custom scrollbars.

PS:

Some anecdotal evidence I got that making less calls with larger chunks of data might be better was when I was investigating why creating an editor buffer was slow for very large files (100k+ lines). One of the first things the model (buffer) code did was to split the text into an array of lines.

I implemented this as any sane person would, with a nice for loop, iterating over the string, grabbing the character code at each offset and checking if it was \r or \n or a \r followed by a \n. I would then remember the last cut off index and do a simple substring to extract each line, building an array of lines. I thought that must be the best way one could possibly do this (I don't know a better way than a for loop even in C++).

If I remember correctly, that was taking 50ms in some browser for a pretty large string. I replaced that simple for loop with a lame split with a regex! - /\r\n|\r|\n/ - and the time dropped to 3ms. I can only think that looping in C++ must be a lot better than looping in JS [here's the code today - https://github.com/Microsoft/vscode/blob/master/src/vs/edito...]

Thanks for the detailed answers!

I'm maintaining a web spreadsheet that supports 10s of thousands of rows and have spent a lot of time on optimization. We have to handle some richer content (i.e. contact pictures) so simple spans aren't always sufficient.

The way we're doing the rendering each cell is its own div and we reuse divs as they scroll out of the viewport (changing their top position). Previously I was using innerHTML on each div but I found that constructing the dom nodes manually (document.createTextNode, etc) and then doing a dom.appendChild turned out to he slightly faster (full "wipe" = 16% lower render time). I then cached those prebuilt DOM nodes and then doing a full wipe ended up being 3x faster.

So there was a small speedup on initial scroll and then when you're scrolling around and seeing rows/cells that you've seen before there's a large speedup. Not sure if that's helpful, but maybe worth investigating.

And yes, I know what you mean about scroll events not getting called synchronously. There seems to be a difference in how some browsers handle scrolling vs painting. I actually filed a bug with Chrome (https://bugs.chromium.org/p/chromium/issues/detail?id=619796...) as they introduced an issue in Jan 2015 that I just discovered.

And yes, I really don't want to implement custom scrollbars so I'm hoping to get optimized enough to not need them... we'll see though.

You don't ever need to add or remove divs when you're not resizing the window. You just need one more than your window is able to display and move the divs up and down with a tiny offset. The other times you can just change their content.
Why? Can you elaborate?

In our framework, we have an option to yank complex components out of the DOM and replace them with empty containers. And when they scroll back into view, we put them back (and activate them if they are newly rendered).

Tangential remark: I remember the days when the drivers for the first mice with scroll wheels were simulating clicking on the scroll bar arrows.

And now we're painting our own scroll bars and simulate mouse wheel interactions (which then probably still invoke some legacy scroll bar based facility internally in the OS):

https://github.com/Microsoft/vscode/blob/master/src/vs/base/...

You definitely need to do some sort of a talk. This are some great leanings. Is react like vdom diffing used anywhere in Monaco / VSCode ?
I know atom used to be based on react, but it turned out to be a bad idea: https://github.com/atom/atom/pull/5624
> Quick tip: do not implement custom scrollbars.

Wasn't this part of the motivation for Java? I seem to recall reading something about the heinousness of debugging a half dozen different platform-specific scrollbar implementations...

Thanks for all the info, this is awesome. :)

FWIW, if you don't have to deal with '\r' only line breaks (do those still exist?), successive calls to strchr or memchr can be much faster than a for loop over characters. The reason is that strchr and memchr are typically already optimized to check a machine word at a time or even using SIMD instructions. If you do have to deal with '\r' only line breaks, it might be worth open-coding the strchr/memchr optimizations, but I never tried.

If you're curious to see the difference, compare the running time of `wc` vs. `wc -l` on large / many files (you might have to force the locale to C for wc -l to hit the fast path).

Yeah, but the overview ruler is awesome :)