domvm 2.x-dev
See original GitHub issueThe 1.x view module’s codebase is getting increasingly harder to reason about and properly optimize. Rather than trying to fight perf death by 1,000 papercuts, I’ve decided to test a clean-slate rewrite to see what can be learned.
The current codebase does a lot of type-checking and value testing to process the JSONML templates (often in multiple places and several preprocessing passes). In addition there is the non-congruence in the template structure because everything must be in array-style declarative form. On top of that there is value coercion and other “helpful” features that are rarely used but always degrade perfomance even when they arent.
What I’ve done with the rewrite is to start with a hyperscript API & explicit child arrays that delegates some of the type-checking to the user and generates vnodes directly, reducing many allocations and preproc code. In addition, I’ve added a fluent API that can be used to set special vnode properties directly rather than forcing object allocation (though both will still be supported with different speed implications):
// 1.x
[".test", {_key: "abc"}, "hello world"];
// 2.x
h(".test", {_key: "abc"}, "hello world"); // faster than 1.x
h(".test").key("abc").body("hello world"); // fastest
// 2.x child arrays must be explicit
h(".test", [...]);
h(".test", {_ref: "myNode"}, [...]);
h(".test").ref("myNode").body([...]);
In addition to a much leaner/cleaner codebase, the performance of 2.x is significantly better than 1.x. While it’s still early and most features have not been ported. One of the things that 2.x excels at is “doing nothing”. The current 1.x branch has 3.9ms overhead at 0% mutation while 2.x is 1.5ms. This is a massive improvement, besting the current Mithril rewrite by 1.1ms (2.6ms overhead). At 50% mutation, 2.x gains an extra 5fps, going from 47fps => 52fps. The code changes are trivial:
Current JSONML implementation:
let render = (vm, dbs) =>
["div",
["table.table.table-striped.latest-data",
["tbody",
dbs.map(db =>
["tr",
["td.dbname", db.dbname],
["td.query-count",
["span", { class: db.lastSample.countClassName }, db.lastSample.nbQueries]
],
db.lastSample.topFiveQueries.map(query =>
["td", { class: query.elapsedClassName },
["span", query.formatElapsed],
[".popover.left",
[".popover-content", query.query],
[".arrow"],
]
]
)
]
)
]
]
]
};
2.x hyperscript:
let h = domvm.view.createElement;
let dbmon = (vm, dbs) =>
h("div", [
h("table.table.table-striped.latest-data", [
h("tbody", dbs.map(db =>
h("tr", [
h("td.dbname", db.dbname),
h("td.query-count", [
h("span", { class: db.lastSample.countClassName }, db.lastSample.nbQueries)
]),
db.lastSample.topFiveQueries.map(query =>
h("td", { class: query.elapsedClassName }, [
h("span", query.formatElapsed),
h(".popover.left", [
h(".popover-content", query.query),
h(".arrow"),
])
])
)
])
))
])
])
If anyone still wishes to use JSONML (and for migration purposes), something like a domvm.jsonml
module will be made to preprocess old-style templates into a properly formed vtree. This way, with known opt-in overhead, you can still use JSONML definitions:
let jml = domvm.jsonml;
var tpl = jml(["div", {},
["br"],
["table",
["tr"]
],
]);
I’m continuing to test which aspects of 1.x imposed penalties that were paid regardless of actual usage. One thing that may go away is objects as keys. this Map
and Weakmap
are slow, so keys will likely need to be either numbers or strings to be used in a js hash. This will allow much faster keyed matching and make destroyed view’s dom reuse possible, among other things. Another thing that may change is ancestor redraw(level)
and emit(ev, args...)
may be moved out to modules as they only depend on each vnode having a parent
reference, so can be cleanly implemented in optional modules.
As soon as I reimplement the important aspects of the lib and rewrite the tests, I will push the 2.x branch to Github.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1
- Comments:54 (31 by maintainers)
Top GitHub Comments
Regarding
prop.update()
, I think that’s a convenience function that not everyone will need (or may need to work in a different way than just refetching the data, e.g. with an additional processing step for the data returned from fetch).Maybe if there was some way to trivially implement the glue between fetch and prop and the possibility to extend prop to get something like
prop.update()
, it could be dropped. If it’s removed from the API, though, it should definitely be introduced in the manual on prop as an obvious and often useful way to couple prop with fetch - just like the examples of how to couple model and views.The
html
andpatch
addons have been ported to be prototype extensions likeemit
:node.patch()
,node.html()
,vm.html()
. I’ve renamed_html
back to_raw
as in1.x
to avoid confusion within the similar context.Also some prelim benchmarks for
2.x-dev
[1][1] https://cdn.rawgit.com/krausest/js-framework-benchmark/d43a0057820aac05073b917eef6317a3bd91fde8/webdriver-ts/table.html