9 ms·
The Glimmer VM: Boots Fast and Stays Fast
- mwcampbell 9y agoWhat is the advantage of implementing your own bytecode VM rather than generating JavaScript code and using eval()? It seems to me that the latter would be more efficient, since you wouldn't have a VM within a VM.
- recursive 9y agoI don't know the details in this case, but very often, any use of eval() prevents a lot of optimizations. That's because for anything the eval() can see, all static analysis goes out the window. In a dynamic language like javascript, there may have been precious little of it in the first place, but a lot of smart people have figured out a way to scrape together some run-time optimizations based on them. The existence of eval() tends to kill them.
- derefr 9y agoThe parent isn't talking about runtime-eval (where you hit the interpreter with strings over and over in a hot loop), but rather "manual JITing", the technique where you take code that would otherwise be very dynamic (looking up method names using variables, etc.), generate a string containing a function definition of one particular concrete specialization of said code, and then eval that string to get a native function handle—just as if said code had been turned into a blob URI and shoved into a <script src=""> attribute. Those functions will then get statically analyzed just like any other functions.
- ohyes 9y agoIt's a fun thing to do and it gives you a lot of insight into what is efficient to implement in a programming language that you don't get by simply using someone else's language.
- mixonic 9y agoFor one, JavaScript code is actually pretty verbose. It both has a large payload size and high parsing overhead. Earlier versions of Ember's rendering engine worked this way and the change to a data based wire format in 2.10 made a large improvement: Intercom saw a 28% reduction in their whole application payload size. LinkedIn's uncompressed compiled template size dropped by 71%. As the wire format is all data (arrays for the most part) you would expect some parsing speed improvements as well. And second, generating imperitive code makes it harder to perform runtime optimizations. For example during initial render Glimmer could note if a property lookup yields an Immutable object and use that knowledge to disregard change tracking for properties off that object. There are some fun things we can do here. It turns out JavaScript engines are very good at iterating flat arrays and running small, highly optimized functions and that's what Glimmer is doing at its core. When your compiler output generates functions, you're creating lots of functions specialized for each template which means a larger surface for the v8 JIT to worry about optimizing. By instead shipping lots of small, hot functions to be re-used (instructions fired via opcodes), we can get a better optimization result from v8 and other engines.
- ibdknox 9y agoIn this particular case, I believe there are 3 reasons: 1) Size - turning each op into a couple of bytes means that the size of your template is significantly smaller. If each instruction is 4 bytes, I could get ~20 instructions in the space of just one function with a setAttribute call: function t(e) {e.setAttribute("id", "bar")} Size is much more important on the web than it is in other places especially as the next billion people start using the mobile web. 2) Parsing speed - given the size of the JS that templates produce, you start running into JS parsing performance. Just by volume you're going to eat an insane amount of time not just downloading the JS but then trying to turn it into something executable. A correctly implemented bytecode VM could easily beat the cost of parsing. 3) Scheduling - If you just produce raw JS code, you don't have much room to dictate how it executes. Since glimmer's goal is to never miss a frame, they're going to have to take control of the work that gets executed to make sure that they always pause at a frame boundary. That's a much more straightforward thing to do in a VM, where pausing work is just a matter of yielding the interpreter loop. This gives you complete control over how you schedule the work from the ground up. Have some huge dom tree to render? Split it across 10 frames without doing a bunch of control inversion. In terms of cost, I haven't looked at their implementation, but I assume these guys did their homework. You can implement interpreters that execute instructions in a few nanoseconds without too much effort. If you really put in the effort, you can do it subnano, but that's outside of the scope of handwritten JS. Even if the overhead was 20x a normal call, the cost of the operations this interpreter is running makes that a rounding error. The DOM is slow and the other benefits almost assuredly outweigh whatever tiny cost they're paying at the per instruction level. There are lots of other potential benefits as well: opportunities for specialized optimizations over the bytecode (you could basically do your own domain specific jit), ease of implementing the base VM for different targets, and so on. There are relatively few times when writing your own interpreter probably makes sense, but it seems like this architecture would give them a ton of headroom to do some great stuff down the line.
- curveship 9y agoFor 1) and 2), some frameworks solve the size issue by creating functions for the basic DOM ops, which then get minimized to single character names in production. So in your example, there might be a setNodeAttribute(node, name, val) function, such that the final code isn't `e.setAttribute("id", "bar")` but just `a(e,"id","bar")`, where `a` is the minimized name of setNodeAttribute. There's really not much noise in that expression, just the "(,,)", so maybe 4 chars that an opcode could save. As for 3), is that really possible? If you profile most modern frameworks, they're already fast enough that most of the time is spent in rendering, not in javascript DOM manipulation. So even if you cut short your js before 16ms (60fps), you have no idea how long the browser is going to take to render your changes. Plus, the browser will be doing extra work, since it needs to render all the frames in which you've only done part of your updates.
- Touche 9y agoWhy does it need to generate code at all? Why can't it parse the template, create DOM nodes, listen to the "home" binding and do textNode.nodeValue = newVal whenever it changes. What does the bytecode VM provide?
- steveklabnik 9y agoThis is literally what the article is about.
- sroussey 9y agoeval() and Content Security Policy don't mix well, btw.
- analognoise 9y agoIsn't this just re-solving a decades old problem, with the (unnecessary) constraints caused by modern web systems?
- matthewmacleod 9y agoWhile being technically correct, your statement is pretty much pointless. All development is 'just' re-solving an existing problem with better performance, or 'just' extending an existing algorithm to be more resilient, or 'just' implementing a legacy interface on a new platform. There are lots of very obvious reasons that the web has the constraints that it does and describing them as 'unnecessary' adds literally nothing useful to that conversation.
- analognoise 9y agoVery obvious? Could you elaborate? I don't mind changing my opinion, but it looks like the web is a mishmash of poor implementations made possible by increasing bandwidth and ram.
- coldtea 9y ago>All development is 'just' re-solving an existing problem with better performance, or 'just' extending an existing algorithm to be more resilient, or 'just' implementing a legacy interface on a new platform. Only this is "re-solving an existing problem with worse performance that we had decades ago (on native), but slightly faster than the previous speeds we achieved having it run with its feet tied". (Where by "feet tied" we refer to the performance penalty imposed by having everything run on the web stack).
- analognoise 9y agoThis is what I meant!
- zedadex 9y agoIf the problems were solved for people on native, there wouldn't be this much focus on reinventing the wheel on mobile. Clearly for some subset of people, "something" is missing for those native solutions (I can guess - portability, convenient 'moddability', etc) which they believe the standardization-reliant web can better address for them.
- chubot 9y agoWhat IS the glimmer VM? I didn't see any links on the page. Is this it? https://github.com/glimmerjs/glimmer-vm https://github.com/glimmerjs/glimmer-vm It is JavaScript code or native code?
- steveklabnik 9y agoThat is it. It's TypeScript.
- chubot 9y agoOK, my confusion is why it's called a "VM": Glimmer is a flexible, low-level rendering pipeline for building a "live" DOM from Handlebars templates that can subsequently be updated cheaply when data changes. This sounds like something written on top of a JavaScript VM, not a VM itself. Is it implemented with VM-like techniques? What does the instruction set look like?
- whichdan 9y agohttps://github.com/glimmerjs/glimmer-vm/tree/master/packages/%40glimmer https://github.com/glimmerjs/glimmer-vm/tree/master/packages...
- steveklabnik 9y ago> This sounds like something written on top of a JavaScript VM, not a VM itself. You can write VMs in languages that are implemented with a VM. > Is it implemented with VM-like techniques? More than that, it is literally a virtual machine. > What does the instruction set look like? IIRC, it's a stack-based VM. Here's the opcodes, I believe: https://github.com/glimmerjs/glimmer-vm/blob/master/packages/%40glimmer/wire-format/lib/opcodes.ts https://github.com/glimmerjs/glimmer-vm/blob/master/packages... (I have mostly a high-level understanding on this, after talking to lots of people and watching presentations; I don't hack on Glimmer myself.)
- abecedarius 9y ago
- MatthewPhillips 9y agoI love technical articles about how libraries are built. This is really great! I don't follow the last part of the article though. Specifically this part: > We accomplish this by (under the hood) allowing every bit of data that goes into a template to separate the process of computing its current value from the process of determining whether the value might have changed. When you say "bit of data", what exactly do you mean? I assume you mean some field that is bound to a template. Like a property on glimmer component. Using KVO this would be a compute of some kind, that triggers an event when its value changes. Perhaps you're doing something totally different, but I don't see how? Breaking it down, we have an object like: { "foo": "bar" } I think you're saying that you don't observe this object (using some kind of obj.set('foo', 'baz') convention) but rather determine that the value changed by some other means. If that is so, what is the other means?
- MatthewPhillips 9y agoThinking about it a bit more I think might know what is going on here. In a traditional KVO view layer (I've worked on a couple of these myself, so my thoughts here are biased) when a property changes it triggers an event. The view layer is listening to this event and so it knows to update the DOM to reflect the changed value. What this means is that the view layer only works with evented KVO objects. What Glimmer has achieved is the ability to work with other types of data management systems. Knowing that is key to understanding (for me, at least). So given that, what must be going on is that the view layer gets the value of a property with some interface. That interface implementation is provided by the view model so it differs. For plain objects it might just be `return obj.foo`. This will make getting a value really fast. No notification systems are required. For updating the view layer there must be some generic way of telling it "hey, the obj.foo property might have changed (or maybe not, shrug), figure it out for yourself". Then using the update VM it is able to figure if it really did change, and only update the subtrees if it did. Or something, this my interpretation. Sounds smart!
- tomdale 9y agoYep, this is pretty spot on! At a high level, the Glimmer VM is built on top of two primitives: 1. References (as you describe, an interface implementation to provide the underlying value) 2. Tags, an interface that communicates "is it possible this value has changed?" Every reference has an associated tag, so once you have a reference, at any time you can ask: Is it possible this thing has changed? If so, what is it's value? In KVO systems, every time a dynamic value is used in the template, that view usually adds an observer on to the root object. This adds a fair bit of overhead to both rendering and teardown. And if multiple properties change, you have to figure out the optimal re-rendering strategy. (E.g., if a value inside an `if` changes, you probably don't want to re-render it if the conditional also changed from truthy to falsy!) Today in Ember, the view layer no longer sets up observers. Instead, during rendering, we create a tag for each property. When you mutate a property (this.set('firstName', 'Matthew') for example), two things happen: 1. That tag is marked as dirty. 2. A revalidation of the entire tree is scheduled. The revalidation process starts from the top of the render tree and walks down, asking every reference/tag "is it possible you've changed?" Because this is just an integer comparison, it's very very fast on modern JavaScript VMs, even if you have lots of data on screen. The tag is like a Bloom filter, though. It means a change MAY have happened, not that one necessarily did. If the tag is dirty, we do a last chance identity check for primitive values. Only if it has actually changed do we update the DOM. One nice thing that falls out of this is that the application developer can change as much component state as they'd like at once, and we can avoid doing any expensive computation to figure out the optimal place to start re-rendering (the `if` case I mentioned above). By revalidating the tree from the top down and keeping constant time factors low, you get that optimization "for free." The other nice thing is that you can express all sorts of cool semantics on top. For example, if you have immutable data you can attach a tag that always says "I'm never modified." If you don't want to do any bookkeeping at all, you can attach a tag that says "Always recheck me to see if I've changed." Best of all, as Yehuda mentioned, you can mix and match these semantics in your components. It also allows the data to drive the change semantics, not the component, which you often don't want to have care about how model data might change. If this is interesting to you, there is some WIP documentation in the Glimmer VM repository that talks more about the philosophy behind references and tags: https://github.com/glimmerjs/glimmer-vm/blob/master/guides/04-references.md https://github.com/glimmerjs/glimmer-vm/blob/master/guides/0... https://github.com/glimmerjs/glimmer-vm/blob/master/guides/05-validators.md https://github.com/glimmerjs/glimmer-vm/blob/master/guides/0...
- mercer 9y agoApologies ahead of time if this is a stupid question. I am pretty much the walking stereotype of a web developer with very little experience with anything below javascript/ruby/python/php. Considering that Glimmer goes quite far in optimizing stuff, at which point would it perhaps make more sense to just emulate HTML elements on a pixel-level? Or am I vastly underestimating how complex the standard UI elements really are? Or overestimating how much effort went into Glimmer? And if so, as perhaps a more constructive question: are there any areas of 'web' where someone feels a shortcut should be taken? (On the latter, I'm inclined to feel that WYSIWYG textareas and CSS above class-scope are ripe for 'shortcuts', but perhaps that's opening some cans of whole other worms...)
- JoshTriplett 9y ago> Considering that Glimmer goes quite far in optimizing stuff, at which point would it perhaps make more sense to just emulate HTML elements on a pixel-level? Even if this could be done with reasonable performance (and people have tried this), you shouldn't do this in any browser environment. You can't possibly emulate the behavior of every browser in a satisfactory way; you'll end up behaving gratuitously differently, with subtle breakage of platform conventions, browser conventions, user expectations (including potential extensions), hardware requirements, scaling, rendering, and accessibility.
- LocalPCGuy 9y agoIn addition to what others have said, this would have a significant impact in accessibility and break a lot of the tools used for those with some sort of disability or accessibility issue. You are basically talking about what Flash used to do, and that's not a road we want to revisit.
- okket 9y agoWhole websites were done in Flash, not long ago. It was not a good idea.
- dbbk 9y agoIt feels like a bad dream now, but yeah this really did happen. It was awful.
- unlmtd 9y agoTruly, when it is time for the user of an application to load it with content, then is not the best time to download the application itself. Think of all this 'web app' period as just a great big experiment. One day soon, only content will flow when content is needed. It will be standardized so that everyone can see all the items for sale that match his/her filters, and not just 'this one website' list of articles for sale, along with a download of the entire unique application needed to view this one particular store. Water flows downhill, and the 'web app' days are numbered.
- bluepnume 9y agoI'm a big fan of what I'm seeing in Glimmer so far. I had fun adding support for xcomoponent yesterday, making Glimmer components work as cross-domain components. https://medium.com/@bluepnume/introducing-support-for-cross-domain-glimmer-components-with-xcomponent-21287c9f91f1 https://medium.com/@bluepnume/introducing-support-for-cross-... I'd love it if it were a little more flexible to get started with though. Rather than fire up ember-cli, I'd love to just be able to: 1. Load glimmer.js from a CDN 2. Extend glimmer.Component 3. Render my component into an element I know ember-cli solves this problem for a lot of users, but I'd love for the number of steps to get a "hello world" working to be as simple as the above. I think that's one of the reasons React has been so successful: it's stupid-simple to get started with, and it draws you in from there.
- okket 9y agoThis is simply not possible with what Glimmer tries to achieve. (Did you even read the article?)
- bluepnume 9y agoIt's not possible to have a slower development build which does template->bytecode conversion on page load? The point I'm trying to make it, there's already a huge amount of churn around the vast amount build tooling needed to write any javascript these days. Part of making a framework with a low barrier to entry is having the build tooling stay out of your way until you're ready to deploy. I get that's not necessarily Glimmer/Ember's philosophy, I just think it's a shame that using the framework from day 1 requires buying into all of the technology choices around ember-cli.
- okket 9y agoMinimising time to first render/interaction is on the list what is important for Glimmer. I don't see how this is compatible with moving the conversion engine to the client. (Not to mention that the app size zealots will eat you alive)
- conradk 9y agoHow does Vue.js compiled templates compare to Glimmer ?
- qaq 9y agoSomeone needs to make a lightweight framework with MobX and Glimmer
- bozonil 9y agoIs Glimmer VM actually a virtual machine? The term suggests some kind of Turing complete language with sandboxing and memory management. Without these features "domain-specific language" would be more precise description.
- iamleppert 9y agoIt's not correct to call this a VM. It's an optimized template language/renderer. I'd like to see an example of the same static gzipped HTML content delivered to the browser and a template rendered with this. I have doubts this is any more efficient than the browser's built-in gzip decoder on a time basis nor do I think it beats gzip in terms of space efficiency. It's hard to take seriously those who claim to be optimizing things when they don't have any measurements on what they are trying to improve vs. the new method.
- swsieber 9y agoWhy not? They have byte code (sort of) that translates to actions... I don't get the gzip comparison because this is about updating views in an SPA framework (Ember)
- dlcmh 9y agoBased on some of the comments about what a VM actually is, I think this 2-part series might help those who are wondering (me included) about the technicalities of building one's own VM: - (Part 1) https://www.codeproject.com/Articles/43176/How-to-create-your-own-virtual-machine https://www.codeproject.com/Articles/43176/How-to-create-you... - (Part 2) https://www.codeproject.com/Articles/61924/How-to-create-your-own-virtual-machine-Part https://www.codeproject.com/Articles/61924/How-to-create-you...
- pier25 9y agoI wonder why the author ignored other big players in those benchmarks such as Angular or Vue.