How can more layers be more efficient?
From OO to Clojure Workshop!
Watch my free workshop to help you learn Clojure faster and shift your paradigm to functional.
Summary: It's common that adding more layers of abstraction or indirection will make things slower. However, React and ClojureScript make web pages faster than doing it by hand --- essentially programming the bare web. The lesson is that if you choose your layers well, they can actually make your system faster.
David Nolen produced an article showing how, out of the box, using React with immutable data structures was faster than using the more common MVC frameworks, for instance Backbone. He demonstrated it using the popular TodoMVC, which is a collection of many, many, many implementations of the same basic app. When implementing the TODO app in a straightforward way, React with ClojureScript beat the others when performing lots of operations.
It's a bit of a perplexing problem. On the one hand, you've got the Backbone implementation that is a very thin layer and is manipulating the DOM directly. It's using mutable state. On the other hand, you've got React with ClojureScript. Everything in ClojureScript is known to be slower than everything in Backbone.
- Backbone manipulates the DOM elements directly, while React generates a "virtual dom", analyzes it, and modifies the DOM indirectly. React definitely does more work.
- Backbone was using regular JS arrays and objects. Modification and reading are very fast. ClojureScript, on the other hand, uses sophisticated immutable data structures that are known to be slower for random modification and access.
So it's no wonder it's a little hard to believe. But there's some facts left out of the balance above.
Before we go into those, I should mention that a lot of people argue with the results David Nolen got because he used a very uncommon use case that the other apps were not optimized for. He added a large number of TODO items at once. But David Nolen didn't optimize his for that, either. It only reinforces the point: why is it that when the number of operations gets large, React + CLJS starts to win?
The high level is this: each of the layers that React and ClojureScript add are optimizers. Insert some optimizers in the equation and things start getting faster.
- ClojureScript's immutable data structures are technically slower for each operation, but because they're immutable, you get something you could never have in a mutable data structure. How do you know if the data associated with your View has changed? If it's mutable, you have to walk through the data, all the way down to the leaves, and compare each thing to a copy that you have not changed. But with an immutable data structure? You just have to compare the pointers to your two values. If the pointers are the same, it hasn't changed, and you don't have to revisit that data. Immutable data structures are a change detection optimizer. It's a huge win that can cut out tons of work and repainting.
It's a bit like whether it's faster to fix your toilet yourself or to get a plumber to do it. Sure, there's some overhead of scheduling an appointment, travel, etc., but it might be faster in the long run for a plumber to do it. They know just how to diagnose the problem, where to buy replacement parts, and how to use the tools. If you did it yourself, I'm sure you could figure it out, but you'd be reading books, maybe you buy the wrong part, and your toilet might be out of commission for a while. A plumber is an optimizer that eliminates the wrong plumbing operations.
That's my answer to the question of how React+ClojureScript is faster than MVC and sometimes even faster than React by itself. If you add optimizers that are faster than the operations they're eliminating, it's a performance win. Virtual DOM uses quick checks to eliminate expensive DOM operations. Immutable data uses fast pointer comparison to eliminate expensive whole-tree comparisons. It's not much of a paradox that as things get complicated, the more sophisticated system starts outperforming the brute-force approach.
If you'd like to explore ClojureScript and React (through Om), I recommend the excellent LispCast Single Page Applications with ClojureScript and Om. It's an interactive course with videos, exercises, and lots of source code. It takes you from zero to single page app.