ultralight-ux / Ultralight

Lightweight, high-performance HTML renderer for game and app developers.
https://ultralig.ht
4.69k stars 196 forks source link

Performance Optimization Guidelines #351

Open GenuineAster opened 3 years ago

GenuineAster commented 3 years ago

Are there any resources on performance optimization for Ultralight?

This is a bit of a long one, I tried to include our user story so the case would be clearer and if my approach is bad, someone could tell me. We are basically prototyping Ultralight replacing our in-house GUI framework. We are a product in development, no revenue yet, but we of course are looking at a commercial license if this integration is successful.

I had the grand idea of replacing our in-house GUI framework (which is missing expensive features to develop like animations, drop shadows, stateful inputs, etc.) with something like JSX/ReactJS and HTML5. The vision was that we could get our UI/UX designers to take a course in insert-high-level-markup-lang-here and then they could solo-fly the GUI and we could save lots of iteration time and give everyone involved more freedom.

I found Ultralight as an obvious framework for integrating that into our game engine, and embarked on my moonlit journey of prototyping this integration. I picked a more technically complex part of our GUI to try to "replace" to see how hard the integration would be in the worst case, and get a feel for the overall complexity of maintenance & development for my team. Rendering went great, plan to implement GPU renderer but the CPU one hasn't given me too much trouble yet for our (relatively) simple UI. Then I had to wrap my head around calling C++ from JS so we could get the relevant data in, wrote a small library for helping with bindings and now it's pretty straightforward.

Then, I got to work on my React prototype. I prototyped in the browser, and eventually got everything functionally working. I moved over to in-engine, and "rendering" (i.e React creating DOM elements for everything) my 5-ish accordions and 20-something sliders in React (with Material UI) takes a whopping 50ms! Not a frame spike I'm happy with. This plunged me into the world of optimizing JavaScript for a couple days (which, by the way, is nowhere near my high, C++ standards). I enabled a few fancy build flags, virtualized my accordions, and tried again. 20ms! I've never been so unhappy with a >2x performance gain.

Profiling this in Chrome puts it at just 7ms, not a great number but way better than in-engine. (An explanation for that one would be great, I'm just boiling it down to different JS engines and potentially JIT being disabled? But I understand that's only on consoles.)

Worth mentioning that I'm looking at the time taken in either the MouseEvent handler or when running a JS callback (or onClick in Chrome) which all do the same thing: causing most of the content to be re-"rendered". I have profiling zones in our C++ code to make sure we're not what's slow. Here's an image of what that looks like in our profiler: image

Okay, React is probably just slow. As I understand it, it's actually material-ui that's slow but I'm not sure we can make our own component library much faster if we want things like touch ripples and animated accordions.

I looked at other options, there are a few constraints here:

I'm not a web developer, so I'm not expert in these things, but I went with Angular and Material again, hoping that web-components would save me. 7ms in Chrome and.. 21ms in-engine.

I'm not really sure how to do this better at this point. I guess in Chrome stuff feels smooth because there's no game running behind the GUI that hitches when you click on stuff. I suppose I need to run Ultralight updates on another thread? And that's where I'd like some guidance:

We are targeting low-end hardware so we don't have infinite cores to dedicate, I think anywhere between 1/4-1/3rd of a core is fine-ish for now, we can have it on the same core as our physics engine.

Any pertinent information is absolutely welcome, thanks!

mcpiroman commented 3 years ago

To OP (@GenuineAster):

Interesting thread as I'm in somewhat similar situation technical-wise. I plan to build the whole 2D game in the web land, but because of lower level stuff I'd prefer, or have to, use ultralight, instead of e.g. ElectronJS. Also, I don't use React but self crafted, very lightweight framework (base on fritz2) to do only necessary DOM updates, with minimal JS (though, I don't need any pre-built components whatsoever). It also integrates well with server side. So, performance are in the top of my biggest concerns here. I started with chrome and they seem to be kinda whacky for a basic structures, but I hope that will scale in a convex manner, and, with feature optimizations here and there (potentially WASM), will be acceptable. But as you mentioned there is a 20ms to 7ms gap between chrome and ultralight, I begin to worry even more. Also, how often the 20ms thing happens: initialization, rerender of single property, per frame?

(My background: I started with quite low-level, OpenGl-based framework (libgdx), but I want to migrate because:

)

To maintainers (@adamjs ?):

I'd like to kindly ask a more general question: how, from your side of knowledge, the ultralight is, and how is it going to be in terms of performance compared to major web browsers. Are there any major, long-statning reasons for it to be slower or faster in given regard? For js relates to #304, but e.g. maybe web-kit is just known to be slower than blink (render engine of chrome) and we can do nothing about it?

GenuineAster commented 3 years ago

@mcpiroman this runs every time the user selects an entity in our editor, so it's based on user input but we still never want hitches. I think you're probably fine if your DOM is fairly static, unlike ours. I am not sure if you are writing a game in JS, but we are just doing GUI, not game.

I did some more testing, this time using Squirrelly (JS templating engine) and Materialize (Material UI with minimal JS). I got my times down to a very unpredictable 10-18ms in Ultralight, and a very consistent 5.5ms in Chrome. I'm not sure what causes the variance in Ultralight. Still, this is too slow for our application and I will wait for input from @adamjs before going further.

image image

adamjs commented 3 years ago

I'm assuming this is on Windows, right?

My hunch would be that it's actually down to the fact that we're not using BMalloc on Windows which helps optimize small allocation performance in JavaScriptCore (last time I did ETW profiling on a React app I saw quite a bit of small heap objects being continuously allocated/deallocated per frame).

The reason BMalloc isn't currently enabled is because A) it's not ported yet to Windows (it's currently only officially supported on Linux/macOS) and B) it adds a bit of memory overhead.

Regarding point A), there are a few WIP patches in WebKit's bugtracker I'm planning to evaluate for use in our port, see this thread: https://bugs.webkit.org/show_bug.cgi?id=143310

adamjs commented 3 years ago

I've applied a number of performance improvements to the CPU renderer, layout engine, and JavaScript engine that should tackle some of these issues (in my profiling, a lot of the slowdown seemed to be related to box-shadows and compositing of transparency layers, both should be improved now-- also I've integrated mimalloc into the tree and enabled it on all platforms, seems to provide a very noticeable boost in JS perf in synthetic benchmarks).

yamaken93 commented 3 years ago

@GenuineAster hey, i wish to hear more about your development with ultralight since i got the same reason to use ultralight as you. Did you check the performance after the improvements stated by adam in his last comment?