Does anyone know on a technical level why the Monaco editor feels so much faster than the Atom editor? Is there any mechanism Microsoft is employing that Atom could adopt, or are the two editors that fundamentally different?
I'm working on the editor since almost 5 years now. Phew, time flies.
There is no silver bullet, we mostly try to keep all computations limited to the viewport size (if you have 20 lines visible, then typing, colorizing, painting a frame, etc. should all end up being computed with loops covering those 20 lines and not the entire buffer size).
We also use extensively the profilers, and most recently (last month) I learned about this great tool called IR Hydra[1]. The gains from eliminating bailouts in the hot code paths are probably too small to notice (5-10% per render), but I like to think that everything adds up.
We use translate3d for scrolling (except in Firefox which has a known bug[2]) and that brings down the browser painting times considerably when scrolling.
I've also found insertAdjacentHTML to be the fastest way to create dom nodes (from big fat strings) across all browsers.
Sort of silly to mention, but we use binary search a lot :).
I don't buy it: that would only make sense if you could copy/paste the source or something, and even JS respects function scoping so you can't. You'd have to wait until later anyway, so why not count AST nodes or something instead?
I can't make useful guesses about the V8 developers' reasoning, but I'd assume they considered several options and chose this one for a reason, concerning how much performance is at stake. I don't know about Crankshaft's inner workings, though, so I can't make qualified comments about it. Maybe something like http://jayconrod.com/posts/54/a-tour-of-v8-crankshaft-the-op... can answer some of your questions?
> Graph generation: Crankshaft builds the Hydrogen control flow graph using the AST, scope info, and type feedback data extracted from the full-compiled code. Inlining also happens at this stage. Hydrogen is Crankshaft's high-level architecture-independent intermediate representation.
> Inlining: Performed during the graph generation phase. The inlining heuristic is very simple: basically, if the function being called is known, and it is safe to inline, the function will be inlined. Large functions (>600 source characters including whitespace or 196 AST nodes) will not be inlined. Cumulatively, a total of 196 AST nodes can be inlined in a single function.
So they do use AST nodes as a heuristic; I don't really understand why they would also use "source characters including whitespace" though.
Guess I better keep my comments outside the function body from now on when possible.
Well, unreasonable now was probably perfectly reasonable at the time of implementation. Perhaps they even have or had a plan to improve upon it, but given other tasks (ES6, etc.) it hasn't been given much priority.
I mean, why would they fix it with high priority if the engine already has reasonable performance and the benchmarks are happy as well?
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?
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!
[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
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...]
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.
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):
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...
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).
Thanks for this. :) Learning the type of tricks needed to get DOM performance are hard-won, I imagine, so hearing it straight from a developer is extremely valuable to me.
Awesome work, I love the speed. What it needs is a useable vim plugin, otherwise, it would be too difficult to use regularly, or for any real work. BTW, I also appreciate that it's cross- platform and runs on linux.
Contributor to the VSC Vim extension here. I've been working on it frantically over the last few weeks and I'd strongly recommend giving it another try. I believe it's made some big strides in usability.
And obviously creating issues on the github repo for things you're missing is a big help for me. I try to have a fast turnaround on these things.
Missing from (or aside) this vim plugin is ex mode, similar to this[0]. I read that this is out of scope with the project, but being able to build it as another plugin could be great. This is the approach taken by vim-mode/ex-mode on Atom.
This is very true. I let my own usage patterns and github issues be a guide to what I do next. I don't use ex mode very much, outside from simple substitutions, and I haven't gotten any issues yet either.
I remember reading that the Atom Vim authors believed separating the two was a mistake; they were too intertwined. As much as I'd like to have it be a separate project and not think about it, apparently that doesn't work so well...
"Why all these web workers and why should I care?"
"A: Language services create web workers to compute heavy stuff outside the UI thread. They cost hardly anything in terms of resource overhead and you shouldn't worry too much about them, as long as you get them to work (see above the cross-domain case)."
Does Atom still feel slower? I've used Atom before and it did feel sluggish, and using VSCode for TypeScript really felt lightyears ahead from Atom. (maybe a bit unfair of a comparison since it's most likely nicely integrated for it, but whatever)
To be fair I haven't caught up with their current versions (I'm a vimmer, I just tried them) but I think I tried Atom after the React move.
Atom has gotten incrementally faster and I have few problems using it as my main editor at work, on a Macbook Pro with 16GB of RAM.
It isn't pleasant to use on my personal laptop (MBA with 4GB RAM) where I frequently switch projects.
While I am a happy Atom user, I am disappointed with performance; I think there's an order-of-magnitude jump in overall speed that the editor could really use.
You are talking about 16 GB of RAM, rights? My emacs is very happy with 32 MB of RAM and my vim with even less.
I understand that Atom is doing a lot, but if you need 16 GB of RAM to run your text editor, this simply means that the developers have not been using the right data structures to manage the state of the application or maybe the new code editors have an implementation of an AI coding for you so you can outsource yourself.
I was going to say that my emacs uses an awful lot more memory than that, but actually it's got 190 files open in at least 3 languages and it's using 89.8 Mb of RAM at the moment.
Atom is at a fundamental disadvantage - it's built on a much more complicated and abstracted stack of technologies. Javascript engines do spectacular things these days but they have a harder job to do than executing compiled lisp, and that's before you look at the whole of the rest of the stack involved.
I've been an avid Sublime user for many years and still am, and this is what puts me off any electron based editor or app in general. The requirements for the most trivial things are so high, it is insane. Try running Atom on a AMD E1 laptop. TLDR, not very nice.
I don't think that's a fair assessment given the what is now minimum expectations around syntax highlighting, syntax/grammar validation, autocomplete, etc. We're no longer dealing with "plain text".
So that's even less of an excuse for Atom's resource hogging. Vim and neovim use so little memory and are probably the most responsive editors I've ever used.
Atom is essentially the same class of editor as emacs. Both have designs that can accomodate IDE features (so does vim, but it's more of an afterthought in that case), but normal usage is not IDE-like. Atom is essentially emacs built atop javascript+web rather than lisp+unix.
Last time I seriously tried using it, it seemed unnecessarily CPU-hungry and my battery life suffered as a result. That fact alone was enough to get me to stop using it. I have a weird thing about inefficient software in general, but in this case its inefficiency was actually inconveniencing me. With my good old Vim+tmux workflow, my 12" Macbook claims it'll go for 14-15 hours, though I haven't ever fully tested that claim.
I used Atom for 3 months over New Year's on a 4 GB 2015 MBA.
I had no issues whatsoever.
Interestingly enough, Atom seems to really struggle on my Windows laptop, it's often incredibly choppy. It actually runs better in a Linux VM than natively on Windows.
The only consistent issue that I have had with Atom since I started using it in late 2014 was the handling of large files. It has gotten incrementally better, as others have stated, but trying to open up, for instance, a JSON file larger than 10-20 megabytes in the editor causes the editor to be brought to a grinding halt.
The editor is essentially a virtual list - sliding buffer of DOM elements - only visible lines are represented in the DOM. That's probably the only reasonable way of doing such editors effectively in modern browsers.
Drawback of this approach: hard to integrate platform scrollbar to that, quite a lot of JS code to support virtualization, etc.
Speaking about syntax/text highlighting in HTML ...
In Sciter I've added an option [1] to mark character runs without creating tons of heavy weight DOM elements (The Monaco uses <span>'s for that). Plus an option to style those run marks in CSS:
In fact such marks are needed not only for syntax colorizing but for other things like misspelling
highlighting, text found highlighting and other cases where you need to highlight text but DOM change is highly non-desirable.
Not an expert here so I might be wrong, but I've been reading about Framework7(http://framework7.io/) lately, which uses both virtual list and native scrolling. So maybe you might not have to implement custom scrolling here?
It could also be that mobile CSS has its own quirks so different implementation can be used, again, not a DOM expert here.
Check this: http://sergimansilla.com/blog/virtual-scrolling
it is an example of virtual list showing 1,000,000 records with native browser's scrollbars. So "yes" it is doable in principle but with JS help of course. And yet only for items of the same height.
After trying it out, I feel like their implementation of scrolling might be an important factor. The editor scrolls significantly faster than my browser normally does.