ClojureScript, Core.Async, and Om

Thursday, February 20, 2014

Summary: I combined ClojureScript, Core.Async, and Om (React.js), and then optimized based on benchmarks. Sample Page

While playing with an older iPad, David Nolen's 10,000 dom updates with core.async example came to mind. But, when I opened the page, the browser just didn’t run the sample code well at all. I knew ClojureScript worked perfectly well on mobile browsers, so I thought it might be the DOM manipulation itself. The source on the page didn’t show any obvious requests for animation frames. So, I thought, if I started synchronizing the DOM manipulations around animation frames it might work.

After some research, I found that Facebook's React.js will synchronize all DOM manipulations, performed on it’s virtual DOM, with animation frames automatically. So, instead of trying to write manual synchronization code, I decided to push all the rendering to React.js. I chose to use the Om library, which wraps React.js, because of it’s nice ClojureScript interface. (Interestingly, David Nolen is responsible for Om as well as the original Core.Async example.)


All of the following tests were performed in Google Chrome 32.0.1700.107, on a Macbook Air running OSX 10.9. And, each test consisted of multiple runs, but I am only showing a single “good example” for brevity. Each test builds on each other, in the order they are shown.

Project Configuration

One Channel To Rule Them All

200 Frames
Min 90.257 ms
Avg 113.301 ms (9 FPS)
Max 162.476 ms
Standard Deviation 14.002 ms
Function Calls 14.12 seconds (60.03%)

Removing Source Maps:
200 Frames
Min 86.879 ms
Avg 112.068 ms (9 FPS)
Max 169.367 ms
Standard Deviation 15.046 ms
Function Calls 13.87 seconds (56.66%)

I didn’t expect any change here, and the numbers show that, but I thought it could be a variable. So, I removed them from the test project.

Compilation with Advanced Optimizations:
200 Frames
Min 58.056 ms
Avg 81.560 ms (12 FPS)
Max 150.629 ms
Standard Deviation 16.589 ms
Function Calls 7.62 seconds (46.10%)

Compiling ClojureScript with “Advanced Optimizations” enables all the optimizations available to the Google Closure compiler. In this mode I did need to change the project.clj file to include the React.js extern file, but the test code did not change. Similar to a C-style header file, the extern file informs the compiler what functions it can, and cannot, optimize. It makes a big difference in both size and, in this case, performance. This test really shows why you should turn on advanced optimizations when compiling for production.

Many Channels To Rule Them All

One Channel per Table Cell:
200 Frames
Min 17.239 ms
Avg 58.557 ms (17 FPS)
Max 109.993 ms
Standard Deviation 24.080 ms
Function Calls 4.31 seconds (35.47%)

The previous architecture used a single channel and had all the table cells read from it. It was the simplest thing I could think of originally. But, I decided to better mimic the original example and move to a channel per cell arrangement. This architecture, while improving the frame rate, tripled the average heap size, from ~8mb to ~23mb.

And, a closer look at the animation frame timing shows the total time between frames is now being dominated by the random delay between updates, instead of actual computation.

Tuning the Random Delay:
200 Frames
Min 17.582 ms
Avg 36.219 ms (28 FPS)
Max 64.888 ms
Standard Deviation 8.091 ms
Function Calls 4.43 seconds (59.62%)

After tuning the random delay limit, from a maximum of 100 milliseconds, down, to a maximum of 20 milliseconds, I achieved close to an average of 30 frames per second. I wanted to maintain some level of randomness between the updates, so I did not completely remove the delay.

Code Cleanup

Some Code Cleanup:
200 Frames
Min 16.051 ms
Avg 34.972 ms (29 FPS)
Max 57.388 ms
Standard Deviation 7.898 ms
Function Calls 4.33 seconds (60.82%)

Over the course of the tests, I noticed the Chrome developer tools, while profiling, appeared to degrade the overall javascript performance, so all the above absolute times should be taken as “under test” numbers. The animations appeared to run faster without the Chrome developer tools enabled.

Circling back to my original goal of running David Nolen’s demo, my modified version appears to run at a good speed on iOS 7 Mobile Safari and the Android version of Google Chrome. When I ran my code on the iOS version of Google Chrome, the performance still suffered. I can only assume Mobile Safari is cheating.


Upgrading to Om 0.5.0-RC1:
200 Frames
Min 26.105 ms
Avg 84.024 ms (12 FPS)
Max 148.848 ms
Standard Deviation 29.089 ms
Function Calls 11.88 seconds (69.43%)

While writing this post, Facebook released version 0.9.0-RC1 of React.js. I reran the last test with the updated version to check for breakage, and found a large performance degradation. It is a release candidate, so I would expect the final release version to improve.

Centering a Div Tag with Physics, Instead of CSS

Friday, February 07, 2014

Summary: I combined a Javascript CSS parser and a Javascript physics engine to layout DOM elements with physics.

After reading yet another guide to layout with CSS, and yet another flame-war over CSS’s flaws, I just wanted to ditch CSS entirely. Tex has had a box and glue model, that has functioned well, for decades in print, but the tooling looks unusable for rendering webpage layouts requiring dynamic flexing and reflowing. So, why can’t I just connect two DOM nodes with a spring and have it flex as the the document resizes? (And, this is 2014. Where is my flying car?)

Using the real physical laws of motion, that people expect out of real world objects, actually makes some sense when you think of a document as in motion. CSS grids, using frameworks such as Twitter Bootstrap, have different layout configurations for ranges of different window sizes, based on CSS media queries. But, you can also see it as a page squeezing and stretching based on the user’s window size, with the DOM nodes connected by springs, colliding as the window is squeezed and stretching apart as the window is stretched. So, I built a Physics-based Page Layout system (Live demo).

Conceptual Springs
I am, currently, using a modified physics engine, originally built by by jonobr1, but there are numerous other engines to choose from. If I get the urge, I may swap it out for one that does continuous collision detection. This would help layouts squeeze correctly in response to other nodes, but would require a better abstraction around the current physics engine.

All the DOM nodes mentioned in a selector, or a declaration, are lazily assigned a particle group, containing one particle for each side (top, right, bottom, left) of a box. If the node is only mentioned inside a declaration, the particles are fixed in position, relative to the DOM node. This allows nodes to be used as anchors, without taking them out of the normal document flow. If the node is mentioned in a selector, the node is taken out of the document flow (absolute positioning) and given internal springs connecting each edge particle in a diamond pattern (top to right, right to bottom, bottom to left, left to top). This allows the particles, and thus the node, to flex in response to external springs. It would be interesting to conditionally disable those internal springs to provide partially-rigid boxes. But, that is not currently in the code base.

Originally, I kept the physics animation, of the springs flexing, as the system sought equilibrium. This behavior was fun to watch, but it resulted in terrible performance for document layout. So, now, it runs a fixed number of steps in time (500) without updating the DOM, and repeating as necessary to reach equilibrium. It might be nice to be able to conditionally re-enable the animation, as a way to assist debugging complex layouts, but that is not yet a priority.

After building this system, I think I now have a better understanding of the complexities around document layout. The current implementation is not much more than a proof-of-concept. To make it usable would require adding rigid connections, one-dimensional springs, minimum and maximum spring lengths, layers, cross-layer connections, shorthand declaration syntax, better performance, and more. So, for right now, it is just a neat proof-of-concept.