ralphbecket / Web

Webbish projects.
8 stars 1 forks source link

threaditjs implementation #1

Open leeoniya opened 8 years ago

leeoniya commented 8 years ago

Hey @ralphbecket,

I was reading your Od README and noticed we have a lot of the same ideas.

Mithril is slow, we both know it. Take a look at my Threaditjs implementation [1] source [2] in domvm [3]. It is 2.1x faster than Mithril, also has sub-views, is fully async and optimized to only redraw a tiny portion of the sub-trees. Has routing and error handling as well. It is likely close to the fastest possible impl and the LOC count is the same as the others (sub-250), including helpful comments.

Feel free to steal ideas if you'd like :) domvm was written cause the MVC paradigm and Mithril's app structure and globals/auto-redraw just made no sense to me.

Do you have a dbmonster bench written yet? I'd be interested how Od performs.

cheers! Leon

[1] http://leeoniya.github.io/domvm/demos/threaditjs/ [2] https://github.com/leeoniya/domvm/tree/master/demos/threaditjs [3] https://github.com/leeoniya/domvm

ralphbecket commented 8 years ago

Hi, many thanks for your feedback. DbMonster is next on my to-do list; looking at it (what is it with web developers and never documenting these things properly?) I'm not sure DbMonster is a great showcase for Od's performance because so much changes at once there's little value in independently regenerating separate regions of the UI. Still, I expect pretty good performance since Od's vDOM structure is as lightweight as anyone's and it diffs directly against the DOM. I'll let you know when I have something up and running.

I looked at your domvm threaditjs code, very elegant. One bonus I get from observables is that I never have to explicitly call redraw (indeed, there is no such method in Od!). The down-side with observables is that a dependency sets up a two-way relationship, which means you sometimes have to be careful about disposing of roots otherwise you'll end up retaining garbage.

leeoniya commented 8 years ago

One bonus I get from observables is that I never have to explicitly call redraw (indeed, there is no such method in Od!).

How does your app know which sub-view(s) need redrawing from simply seeing the mutations of observables? I imagine your app's compilation/setup needs to track within which views which observables are used to resolve what should be redrawn?

domvm, also has a concept of observable similar to Mithril's, but they are decoupled, opt-in, and no scary globals:

// set up an observables factory with handler
var w = domvm.watch(function() {
   someView.redraw();
});

// create var with initial value
var foo = w.prop("bar");

// mutation fires handler
foo("baz");

// ajax/fetch fires handler after success/fail callbacks
w.post("/123", {...}, [success, fail]);

You still need some wiring, but creating different observer contexts allows for some good flexibility in what you want to actually redraw/do. With Threaditjs I didn't feel like turning everything into observables was a good idea since it de-purifies dumb json/data.

ralphbecket commented 8 years ago

Hi, I have a distinct observables library called Obs, which is independent of Od. In Od, each component is really just an observable with a vdom value and a subscribed function to tell Od to patch the corresponding DOM subtree whenever the observable changes. When I get home I'll post links to the jsfiddle tutorials I've been writing.

Sent from my Windows Phone


From: Leon Sorokinmailto:notifications@github.com Sent: ‎19/‎03/‎2016 9:32 AM To: ralphbecket/Webmailto:Web@noreply.github.com Cc: Ralphmailto:ralphbecket@gmail.com Subject: Re: [Web] threaditjs implementation (#1)

One bonus I get from observables is that I never have to explicitly call redraw (indeed, there is no such method in Od!).

How does your app know which sub-view(s) need redrawing from simply seeing the mutations of observables? I imagine your app's compilation/setup needs to track within which views which observables are used to resolve what should be redrawn?

domvm, also has a concept of observable similar to Mithril's, but they are decoupled, opt-in, and no scary globals:

// set up an observables factory with handler
var w = domvm.watch(function() {
   someView.redraw();
});

// create var with initial value
var foo = w.prop("bar");

// mutation fires handler
foo("baz");

// ajax/fetch fires handler
w.post("/123", {...});

You still need some wiring, but creating different observer contexts allows for some good flexibility in what you want to actually redraw/do. With Threaditjs I didn't feel like turning everything into observables was a good idea since de-purifies dumb json/data.


You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub: https://github.com/ralphbecket/Web/issues/1#issuecomment-198566885

ralphbecket commented 8 years ago

@leeoniya Check out the README again; I've added a link to some interactive tutorials I've been working on. Also, re: DbMonster, I do have a sneaky idea how to implement it in such a way as to show off Od's performance credentials via observable components (well, assuming it really has the performance I intended!).

ralphbecket commented 8 years ago

@leeoniya Woo hoo! Just got DbMonster up and running. I implemented it using Od the way one would if one's sole purpose were to maximise throughput. On my PC, under Chrome 48, with the mutation rate at 100%, I'm getting 20.5 FPS under Od and 17.5 FPS from the DomVM implementation. That's not bad for a project with zero effort expended on performance tuning :-)

ralphbecket commented 8 years ago

Odd, I must have been doing something wrong. I just re-ran the test and DomVM is hitting about 28 FPS at 100% mutation. Still, not a bad result.

leeoniya commented 8 years ago

Woo hoo! Just got DbMonster up and running.

any links?

I just re-ran the test and DomVM is hitting about 28 FPS at 100% mutation. Still, not a bad result.

domvm started out around that 20fps perf also. Here are some tips:

Just doing those optims will help a lot. Obviously avoid writing JS that causes engines to deoptimize functions, etc..

If you want some sanity checks, feel free to use my instrumentor [1] to make sure your lib is not performing excessive dom ops.

[1] https://github.com/leeoniya/domvm/blob/master/test/lib/dominstr.js

ralphbecket commented 8 years ago

@leeoniya Thanks, will try all those suggestions out this evening. I'll put my code up this morning, but the first experiment I'm going to try is not trying to be clever. Od is intended to transparently provide subtree patching, but that comes with a modest bookkeeping cost. I wonder whether DbMonster makes that bookkeeping cost expensive when you have thousands of tiny subtrees being updated at once, rather than just redoing the whole thing in one go.

leeoniya commented 8 years ago

but the first experiment I'm going to try is not trying to be clever

At 100% mutation rate the bottleneck is almost entirely the DOM operations which are performed. At 0% mutation rate, it's entirely the lib's rebuild/diff routines.

The above advice is not something "clever", but something that will directly impact the fps at 100% mutation. Where more of the cleverness comes into play is at 0% mutation and how you can optimize what is static and what isnt, and what needs to be re-built and diffed. With "pure" nodes, the diffs are cheap since you can directly diff the function arguments and then avoid rebuild & diff if nothing has changed.

This is the reason why domvm at 0% mutation gets 70 fps while Inferno reaches 170+fps. But at 100% mutation, domvm gets 28fps while Inferno gets 31 fps.

Here's a link to a discussion from a while back: https://github.com/trueadm/inferno/commit/134a90005cc8a3900bd601666dd1aa474c0abb6f#commitcomment-15365143

ralphbecket commented 8 years ago

Just did a quick experiment on a different PC comparing Option A (using components everywhere) against Option B (recreating the whole vDOM each update).

Option A: 200 FPS at 1% mutation; 29 FPS at 100% mutation.

Option B: 70 FPS at 1% mutation; 33 FPS at 100% mutation.

DomVM: 115 FPS at 1% mutation; 44 FPS at 100% mutation.

Mithril: 42 FPS at 1% mutation; 23 FPS at 100% mutation.

[Edit.] Hah, I just looked at http://mathieuancelin.github.io/js-repaint-perfs/canvas/ which is getting about 100 FPS regardless of the mutation rate -- so that's how you should do it :-)

leeoniya commented 8 years ago

200 at 1% means you have cheap diff. 29 at 100% means you have sub-optimal dom ops :)

The low mutation test is actually much more important for real-life apps. This metric (along with gc stats) tests lib efficiency; the diff should be as cheap as possible. I'm gonna evaluate some optimizations in domvm for the low-mutation case. It's not bad, but could be cheaper, hopefully without giving up too much of its api flexibility.

Hah, I just looked at http://mathieuancelin.github.io/js-repaint-perfs/canvas/ which is getting about 100 FPS regardless of the mutation rate -- so that's how you should do it :-)

yeah, someone spent valuable time making that :) hopefully for 4/1. btw, the domvm impl on js-repaint-perfs is outdated relative to the one in the repo (by a few fps), i will re-pull-request it after 1.0.

ralphbecket commented 8 years ago

I used the domvm version from your site rather than js-repaint-perfs.

On a pragmatic note, I'm wary of spending too much time tweaking performance when it's already fine -- at least, I'd rather keep the code clean and simple than make things more complex for a modest gain. That said, domvm has a 33% advantage under pressure, which is not small beer.

ralphbecket commented 8 years ago

Hmm, some light simplification (I removed a premature "optimization") and Od is about par with domvm, give or take an FPS or two.

leeoniya commented 8 years ago

domvm finally turns 1.0 tomorrow :birthday: so i'm busy doing final prep. will check back later.

leeoniya commented 8 years ago

btw, the Mithril rewrite will be about on par with the high-perf libs. Leo and I had a chat recently [1]. I still can't swallow Mithril's semantics, plus domvm's perf accounts for only 20% of its features.

[1] https://www.reddit.com/r/javascript/comments/4bmjxx/hierarchical_composed_state_with_reactjs/

ralphbecket commented 8 years ago

Congratulations on 1.0! I agree: I think Mithril requires way too much knowledge of it's internal algorithms. As a user, I want to know that composition just works.

leeoniya commented 8 years ago

Thanks, 1.1 is out today :)

BTW, I recently discovered an optimization in dbmonster that is almost certainly taken advantage of by libs which get high perf at 1% mutations.

Mutations only happen as full-row replacements, so you can diff oldrow === newRow to avoid all rebuild/diff of vtree branches. After i realized this, i made a slight tweak in domvm's dbmon code by splitting each row into its own sub-view and pre-diffing. The perf went from 70fps at 1% to 145fps - huge difference. Domvm's sub-views carry a slight overhead, so the 100% mutation case dropped from 27fps to 25.5fps, but the low mutation scenario is much more realistic in the real world.

You can see the new code: http://leeoniya.github.io/domvm/test/bench/dbmonster/

ralphbecket commented 8 years ago

Hi Leon (congrats again, by the way). Thanks for the tip; the Od version which got the crazy high update performance at 1% mutation used pretty much the same strategy you describe. I changed it to use the naive "redraw everything every time" scheme because my impression of DbMonster is that is really what it's testing. I haven't devoted much thought to optimisation here because, well, at low mutation rates, virtually anything will be good enough, while at high mutation rates I'm already hitting 27 FPS on my old PC and, well, it's just a crazy, unrealistic situation! That said, there's plenty of marketing value in getting high numbers across the board :-)

I've just finished my v1.0 "release candidate" of Od, but I'm going to do some more testing before making any grand announcements. I'm rather pleased that the whole thing, including optional extras for syntactic sugar for creating elements, promises, AJAX library, routing, etc. comes in around 5.3 KBytes minified and gzipped -- that's small enough to go on MicroJs!