Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webworkers #3092

Closed
SanderSpies opened this issue Feb 9, 2015 · 30 comments
Closed

Webworkers #3092

SanderSpies opened this issue Feb 9, 2015 · 30 comments

Comments

@SanderSpies
Copy link
Contributor

There has been discussion about webworkers before, @petehunt made already an implementation and @sebmarkbage has some ideas on how he wants to accomplish it within the current code base. If I remember correctly, React should not just run in a webworker but both in the "main thread" and a webworker.

So, what are we waiting for? @sebmarkbage would be great if you could give a more detailed outline of your ideas so we can get this moving :-).

@sophiebits
Copy link
Collaborator

@SanderSpies
Copy link
Contributor Author

So I'm currently thinking of taking the following steps:

  • implement prerender as a function of ReactClass
  • implement createCallbackElement next to createElement which initially just returns an empty dummy element that can be updated by the webworker
  • create a WebWorker wrapper component (<WebWorker />)
  • optimize render call with using prerender

Not exactly sure yet about:

  • starting webworkers (wonder what the performance penalties are)
  • syncing UI changes
  • dealing with events
  • passing React inside the webworker, perhaps it should be something smaller?
  • deal with non-webworker browsers

Feedback wanted :-)

@sebmarkbage
Copy link
Collaborator

@cecilemuller The problem of just using the DOM is that you can no longer integrate with synchronous APIs. So you have to build high level wrapper APIs on the main thread. IMO, the best way of building high level APIs on the main thread is to build them as React components that expose asynchronous APIs. Which is why I think that the solution is react->react rendering.

@SanderSpies The top section requires invasive changes to the core but we're working on doing them anyway. What we need help with is all the basic stuff.

  • How do you start the worker?
  • How do you load modules in a worker?
  • How do you determine the root to render into?
  • How do we unit test the system?

@SanderSpies
Copy link
Contributor Author

How do you start a worker?
For now I can see the following solutions for this problem:

  • separate file that includes only what is necessary for the worker, which would require extra build steps
  • create a worker on the fly (blob), which will not work in every browser and I expect would have performance penalties. Also resolving dependencies here for the worker is going to be painful - if not impossible without extra build steps.
  • start the entire build in multiple workers, still this would still require the usage of a build tool

So yeah, for now I don't see this working without a build tool. My preference would go to the first one.

How do you determine the root to render into?
I would expect the "main" React to always start in the main thread, and components leaving stubs in this thread to which they can write when they want to. Of course writing to the DOM still needs to be done via the normal React reconciliation mechanism.

It should be possible to have a single worker which is used for multiple components, which makes it a bit more challenging. Probably an extra id needs to be given to communicate to the right component.

How do we unit test the system?
If you would be testing a render function, it would initially only show the webworker stubs - and testing the result of a webworker would be something different. Something like a callback for a webworker result could work here (waitFor(webworkerId) comes to mind).

If there are other options here or I'm missing something, I would definitely like to hear it!

@mathew-kurian
Copy link

What progress has been made towards this?

@sophiebits
Copy link
Collaborator

@bluejamesbond None explicitly for web workers, though we've done a lot of refactoring recently with the goal of making it possible to have multiple reconcilers and make things more serializable, which helps with this goal.

@pfrazee
Copy link

pfrazee commented Oct 24, 2015

Any new work in this area?

@sophiebits
Copy link
Collaborator

No, same as my last message.

@pfrazee
Copy link

pfrazee commented Oct 24, 2015

Ok. I may be able to work on this later. Are there technical blockers, or is it just a matter of getting the code written?

@axemclion
Copy link

I wrote a custom renderer for ReactJS using Web Workers to try and get some performance numbers. Here is the demo page and the repo.

I noticed that for small applications, the cost associated with postMessage seems to make the WebWorker approach slower than using React-dom. For applications like DBMonster when the number of nodes is high, this pays off. The performance also depends on the batching the messages when sent between the web worker and the main UI thread and the batch size needs to be chosen carefully.

Here is the detailed analysis.

@jimfb
Copy link
Contributor

jimfb commented Dec 2, 2015

@axemclion Thanks for the writeup, that's pretty cool! Your writeup mentioned that the bottle neck appears to become the mutations on the UI thread. Presumably this is because you are mutating the values in every row as frequently as possible, but this is obviously not representative of real-world use cases. It is my belief that most rerenders result in very few changes (That is, the majority of a typical application remains unchanged from one frame to the next). Under the assumption that a typical rerender would do a lot of "work" (calculating diffs) but affect a small number of dom nodes, I wonder if batching becomes less significant and the overall performance benefits of running in a worker become more evident. Especially with respect to things like scroll performance while running a rerender in the background.

@axemclion
Copy link

@jimfb You are right about the real world use case - most of the time, there would be expensive dom-diffing, but would result in very little change to the browser DOM. The batching would still be an interesting problem since postMessage seems to be another issue.

In the experiment, when I set the batch size to 1 (i.e.) just call postMessage for every DOM mutation, the speed is worse than when setting the batch size to something a little bigger (say 10 or 50).

I think there is scope for some clever batching, where the UI thread handing the DOM mutations tells us how long those mutations take, and based on that the worker determines the batch size. This way, we don't send so many mutations that we take more than 16ms, or too less that we start building a backup.

For scroll, I also tried running the DOM mutations in a requestAnimationFrame, and that did seem to make it better.

@pfrazee
Copy link

pfrazee commented Dec 2, 2015

Forgive me if this is an obvious suggestion, but have you tried benchmarking transferrable objects?

@axemclion
Copy link

@pfraze That is a good idea, i could try that. Converting DOM mutations into transferrable objects may be hard though.

@axemclion
Copy link

@pfraze I tried transferrable objects, and more simply, just use JSON.stringify - the perf is actually pretty good. It continues to stay that way (better than react-dom), even as the number of objects increase. I am currently running the tests on my machines and will post out the results.

Thanks for the suggestion :)

@pfrazee
Copy link

pfrazee commented Jan 5, 2016

@axemclion good deal. Have you given any thought to using react-workers as a plugin sandbox?

@axemclion
Copy link

Update to the experiment - using JSON.stringify does indeed make web workers implementation faster than react running in a UI thread - here are the numbers - http://blog.nparashuram.com/2016/02/using-webworkers-to-make-react-faster.html.

I was also able to get events working, and created a simple todo app

@ pfraze, did not understand your comment about react-workers as a plugin sandbox. Can you please explain ?

@pfrazee
Copy link

pfrazee commented Feb 11, 2016

@axemclion workers run in a different thread and VM than the page, making them better for sandboxing than iframes. The thread-independence 1. scales better, and 2. prevents a DoS attack (while(true){} capturing the thread). But workers havent been good for UI work because they didnt have DOM access, which you've solved using React. I think you could build a good plugins framework for react apps with this, though you'll need to evaluate the worker-sandboxing somewhat first (CSPs should help)

@sebmarkbage
Copy link
Collaborator

My latest thinking on workers:

We definitely want to solve concurrency. It is a major issue to let some work get out of the way. Particularly scrolling and other high-priority interaction that requires immediate feedback.

One way to solve that is through cooperative scheduling. I.e. making React and everything around React more "yieldy". Another is by adding preemptive scheduling (OS threads / workers) which adds additional guarantees about hitting frame deadlines.

One initial step we could take is move everything into a Worker as proposed here. We can reimplement/replace the DOM. There is, however, one major limitation. Text measurement. Browser layout is currently only available from the main thread. Even if we implement our own layout algorithm ( https://github.com/facebook/css-layout ), we wouldn't have access to text measurement. Even if we implemented our own text measurement, we wouldn't have access to the browser font files for internationalization fallback. Unless browsers are willing to give us text measurement in a Worker, our only option is to do layout on the main thread.

React Native doesn't have that limitation because it can get text measurements in the Worker thread.

That means that as soon as any of your work in the Worker is dependent on layout, you're going to be sending blocking work to be executed on the main thread. You'll have to wait for the round trip and probably stall the animation/scrolling while doing so.

However, luckily for us Compositor Workers are moving along as a spec:

https://github.com/w3c/css-houdini-drafts/blob/master/composited-scrolling-and-animation/Explainer.md

This would allow us to keep executing on the main thread, but also put code on the Compositor thread. We could build a React for the Compositor to allow us to easily build components for that thread.

If we had that, then it is not clear if React in a Web Worker buys us much more than some potential parallelism at the cost of lost scheduling ability, module initialization overhead (shared code executed twice), serialization overhead and added complexity.

If Compositors weren't happening, or if we wanted to render an entire page in WebGL, then we would wait for text measurement in Workers before moving into that model.

We could potentially use them to some effect as an intermediate step but it is going to be less than ideal. It would also require a lot of work to port our own code and move the community over to that model completely. Seems safer to wait for compositor workers. I suspect that its implementation will go quick once one browser has it and popular websites are utilizing it. Spec might take a while and it might be fatally underpowered in its first version. So we'll see how long it takes. In the meantime, we have React Native.

(Relay in a worker on the other hand seems very plausible.)

@josephsavona
Copy link
Contributor

Relay in a worker on the other hand seems very plausible.

Yup, and we're actively exploring this.

@slorber
Copy link
Contributor

slorber commented Feb 12, 2016

Hi,

Not sure to understand the full problem, but what I understand is that message passing between worker and main thread is expensive.

@sebmarkbage instead of focusing on using more powerful workers, shouldn't we focus on making message passing between worker and main thread more efficient? Like for example supporting native support for immutable values, and allowing to pass to worker an immutable object by reference without any serialization?

@fdecampredon
Copy link

Even if we implemented our own text measurement, we wouldn't have access to the browser font files for internationalization fallback.

@sebmarkbage Just a little question about that, would it be completely silly to think that we could be able to use an embedded font that would work on pretty much all language (From what I remember there is a version of arial that has been developed for that purpose), and to embed corresponding font metrics in js ?

@sebmarkbage
Copy link
Collaborator

@Slober It's not terrible expensive and there are ways around it. We should also be focusing on that, and I am, through TC39 but that's an orthogonal problem to this issue. (Edit: Also note that starting up modules and code in each worker isn't free neither. Increases start up time if you have a lot of overlap.)

@fdecampredon There are 120,000 unicode characters. When you turn them into glyphs a large number of them have ligature combinations which makes that number explode. Each CJK language have their own font/glyph design for the same character. That explodes that set but most of those are fixed size so maybe you could optimize/compress those. An embedded font, even if it only has all glyph sizes, would be quite large. For proper sizing and shaping you need more information than that. Even if you did that, you would still not have the same exact font combination that that particular browser has. I.e. that gets rasterized on the screen. So you would end up win cases of incorrect measurements.

@fdecampredon
Copy link

Ok it was silly :p thanks for the answer.
I developed a very simple word wrap algorithm for svg element in the past thanks to canvas measureText, perhaps the OffScreenCanvas could help if it gets adopted by all major browsers.

@sompylasar
Copy link
Contributor

@sebmarkbage Could you please clarify how is this related to the Fiber architecture? Thanks!

@gaearon
Copy link
Collaborator

gaearon commented Jan 24, 2017

This might be helpful: #7942 (comment)

@gaearon
Copy link
Collaborator

gaearon commented Oct 2, 2017

I’ll just close this as we’re not actively exploring web workers for the above reasons.
We are exploring async work on the main thread with cooperative scheduling though.

@patrick-mcdougle
Copy link

I understand that we're not exploring this for the above reasons, but I did just want to x-link this project here just in case we revisit: https://github.com/ampproject/worker-dom

@csr632
Copy link

csr632 commented Apr 17, 2020

Here is a proposal for react: #18652 .

@Menci
Copy link

Menci commented Sep 13, 2020

Just a question. Why React needs text measurement? AFAIK React only calculates virtual DOM difference and apply changes to browser DOM. Where does React need to care about the actul layouting?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests