Conf42 JavaScript 2020 - Online

- premiere 5PM GMT

Let the main thread breathe!

Video size:

Abstract

The main thread, on the web, has a lot of responsibilities. At the same time, web apps are getting more sophisticated every day. Therefore, the main thread gets too busy that will disappoint our user by showing janky frames! The off-main-thread architecture ensures apps run smoothly on every device for everyone.

In this talk, we will go through the possibilities in browsers such as WebWorker, Worklet, and WebAssembly by introducing practical tools that allow us to boost our user experiences.

Summary

  • Majid Hajian: Today I'm going to talk about performance. How we can to reduce the pressure on the main thread and make it more reliable to work with our application. We're going to explore three different possibilities. Web workers, webassembly and worklets.
  • We are adding a lot of complex tasks to main thread. Budget is shrinking down because we want to deliver more frames. One of the options is stable on many browsers, like almost all browsers, is web workers. And the way that we work with that is via post messaging.
  • Webassembly is a compiler that has a strict set of subset of typescript. It compiles to web ASM or webassembly. The measurement is quite difficult, but it might be even faster. However, we want that run in a different thread, not because it's faster. It makes my application more usable.
  • We have new APIs called worklet. In worklet it's similar to like web workers, you have a different thread. It gives us a low level access to the rendering pipeline, audio or video, graphic. By doing that we can actually let our main thread breathe.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hello everyone, welcome to this session. So today I'm going to talk about performance. I'm going to talk about how we can to reduce the pressure on the main thread and make it more reliable to work with our application. So let's move on and see what we can do today. The idea of this talk actually came to my mind when I was actually researching about number of users who are connected to Internet. And I realized that by maybe 2018 or 19 something, the number of connected people around the world was 50% of the population of the world. But however, this year it's actually increasing to around 60%, like 10% more. And these numbers is getting increased every year, right? So more people are joining to Internet and in fact many of them join to Internet or they have already joined the Internet and use Internet via their mobile phone. And when it says mobile phone, it immediately come to my mind that, okay, then it's not only one type of phone, it's probably a lot of different type of phones. And some of them are quite good, quite advanced and working very fast and some of them doesn't work very well, right? So they are smartphone nice looking, but in terms of hardware, they don't have a good hardware. So in fact we can actually have an example here. So Nokia Two or Nokia 2.1 in this case and iPhone are two good smartphones, right? So one of them are quite powerful and you can run many things without any problem on that. But another one, Nokia two, although it's very good phone, cheap and it's running Android, probably the latest version, it's quite reasonable. However, it has not has powerful as iPhone. So you're building in a web application for both of these phones. So it is important to test on both of them. So we don't want to actually give our user this page, the famous unresponsive page, while we are doing some task. So this is not only for mobile, this is also for desktop users. And that's why actually we are building these days progressive web app, right? So we want to actually make sure what we are delivering, we are delivering to our customer as best as possible, regardless of what type of browser or what type of hardware they are using, right? But what we can do, what can we do in this case? So it is very important for us to care about our clients and that's the most important things we have to do as a developer. Our clients could be an end user client, could be our product manager or whoever, right? So we need to care about them. So we need to think about how they are running our application. So in this talk, what we're going to do, we're going to actually explore three different possibilities. Web workers, an old way of handling multithreading in browsers, but differently in this talk, and we're going to talk about webassembly and worklets. So we're going to see what they are, and I'm going to actually give you practical tips so how you can use these tools easier, and I will actually remove this question in your mind. No, I'm not able to do that. You can do that, you can use that, we'll see that. But let me introduce myself. My name is Majid Hajian, I'm based in Oslo. I'm a software engineer, passionate developer, and in my spare time I'm doing a lot of conference and meetup organizing. I'm an instructor, speaking quite a lot, especially about flutter and PWA and performance. I'm also author of progressive web app with angular book, published by apps, and progressive web apps development, published by Pacpub. So let's move on. So in fact, when we are talking about main thread, we know that in the main thread there are a lot of stuff is happening, right? So for instance, in the main thread we have events, so even loops. For instance we have Javascript execution, we have styling, we have like layouting in the browser, we have paint and compositing. All of these happening in the main thread. And in fact a few years ago, maybe when the websites were not that complex, running all of these tasks in the main thread while it was okay, but these days we are adding a lot of more APIs to our application, we are adding a lot of complex tasks to main thread. So that's why our main thread is overwhelmed. So it's a lot of things that we are putting on its shoulder and sometimes it's getting tired, right. We are expecting main thread doing all of those tasks and delivering 60 FPS. So that means scroll must be very smooth, the website must be interactive, and no janky frame. Okay? Of course. But let me do a calculation here. Do the math. Imagine that you want to actually run 60 frames into 1 second, and that means what? The budget that you have is almost 16.6 milliseconds, right? So what does that mean? That means with this budget, if you run one task, similar task on three different phones with three different hardware spec. So in one phone you may not reach the budget, but in another phone you may reach to the budget or maybe overpass it. So this is very important then to test what we are going to deliver. And in fact this is not just about when actually we are going to add screen Hertz, like 60, that's even worse. So that means like in 90 FPS, for instance, we have around eleven milliseconds, or in 120 we have maybe around eight milliseconds. Like the budget is shrinking down because we want to actually deliver more frames and make it more smooth, right? And that brings the unpredictability on our application. It runs well in one browser or one desktop or a screen or device, and it doesn't work properly in other one. So we want to actually get rid of this unpredictability and make our application more predictable, right? We as a developer are responsible for doing such a things. So we need to let our main thread have enough space and error to just run the task. And if it's not enough space, we need to use other tools or other APIs or other threats, let's say here, to relieve these tasks from main thread to that other threat, right? But one of the options, which is with us since maybe 2012, is stable on many browsers, like almost all browsers, they support that very well. It's web workers, right? Web workers are quite famous, we know them. So web workers works like almost like a headless version of your browser. It seems like you're opening a tab, but it's headless, right? It has some characteristic for sure. It's isolated. There is no variable that we can share, it's because it's isolated. And of course we don't have access to Dom, they can also run in parallel. So if you have several cpus core, then browser may decide to run it in different core at the same time. So then we get parallelism, right? So let's quickly look at the web workers. The web worker right? Now, how it works is that when you instantiate the web worker, you actually spawning a new instance of like Javascript engine for instance. So it has its own event loop or message queue. It works similar to main thread. And the way that we work with that is via post messaging, right? And post messaging could be a string and could be like something that is actually stopping us to use this tools, for instance, for us, right? And why? Because, well, working with post messaging is not as interesting as it should be, right? But it will be very interesting if we can actually see like a promisified version of this post messaging system. Let's say for instance, we just call a function from main thread directly to worker thread and then it's promised and then we are familiar with that, right? So this is the way that we work with Javascript application these days. Quite familiar pattern, right? So in fact there is a library that can help to do that. So comlink is a library from Google developer team, so that they are making this post messaging system to use the proxy and promisify it for us. So how it works is that it's quite easy. So imagine that you have your main thread, you just want to instantiate your worker. You need to wrap actually your worker with comlink. And once you wrap you can immediately call the function that you defined in your worker thread. So in this case, so I have two function here which I exposed from my worker thread via come link and now I can directly call that in my main thread and awaits for that. And it's because it's just promise and then I can do whatever I want. Fairly simple, right? It's amazing. So that makes our life as a developer easier. So we can just now use web workers as easy as we are expecting. So in fact, in this example you'll see that I have a react application for instance, and I'm actually spanning a new worker and wrap it with for instance link. And then I can actually set state or do something with my worker. For instance, if I have a sorting function which takes time because it's just synchronous and I want to just sort some list and it takes time so I can just expound this worker and put this business task to the worker. And once it's done, I'm just awaiting for that, receiving the respond or the data sorted data, and then I can set my state. Of course, remember that once you're done with your worker, it's nice to terminate them in order to clean up your stuff. But you may ask, okay, what kind of things we can do with worker? You're talking about worker, you made our life easier with this library, but what can we do? Can I put everything in worker? Well, the main question for you when it comes to using web worker is that does this task, this business logic needs UI, or if it doesn't need that, then does this task actually can be run in another thread and I can wait for that and then do something with UI. In fact, my logic of application, my business logic, my application could go to the other thread and once the data back I can just change the UI, right? It's interesting because we can say in this case, main thread is like a UI thread, you just do your UI changes manipulation and then you'd run in a different thread, business logic, right? So one example for you, probably you're familiar already with redux pattern, especially if you are for instance, react developer. So you have redux. And if you don't know, let me quickly explain. So you have one global and universal store, and then that has in an, in your UI you dispatch some action, and then action goes to reducer, and reducer synchronously does some logic, and at the end you get a new state and then you can actually again update your UI based on the state, right? This is how it works and that's synchronous and that's sometimes time consuming and blocking. So what we can do in this case, like for instance, we can actually hold off this reducer and store to our worker thread, and then we can have a view and action in our main thread. So simply we are actually holding off this heavy logic which is blocking our main thread to do our application to somewhere else. And then we do this. And then once the reducer does like a new object of your store, you can get it and update or manipulate your UI. This is one of the examples, I bet that you can now think of many other examples in your application that could do similar things, right? But the question comes to your mind maybe is that does it even faster? Like is it faster if I run this task on a worker thread? Well, the question is not about is faster or is it slower? The question is that how more reliable is your application? Let me give you an example. Let's say this is your main thread, right? When you actually run a task on your worklet thread, you need to take into account you have a little bit of overhead. The worker thread needs to be spammed and run and then close and then get back to the main thread. So you have a little bit of overhead. So in fact it might not be faster. But what we can say is that it could be more reliable. So you know that this heavy logic, which I could run in that third, it will not block my UI thread. And in fact, in some of those cases, in the examples about the phones, imagine that you have a very limited budget. So then you cannot actually block your UI to do even a very small task. Then you can spound main thread and do your stuff without blocking your application and gives your user better user experience, right? This is something that we really want to do. But about parallelism, then it might be even faster. So the measurement is quite difficult, but it might be even faster. Let's say if your hardware has, or your cpu, or your customer device cpu has several cores, actually these workers can run in each core in parallel, and this parallelism makes it even faster. However, that's very difficult to measure, and that's something out of our hand as a web developer. And also in some cases might be that in some phones, third or fourth cores are even slower. So we're not going to talk about details of these cores. But the point here is that it could even be faster. However, we want that run in a different thread, not because it's faster, because it's more reliable. It makes my application more usable. So let me give you an example here. So in this example you see that I'm actually doing a heavy task sorting my list. It's a real example from an application that I made. So just copy it into this example for you. So you see that when I'm actually sorting something, and that's a little bit heavy, a lot of calculation here, this animation actually is blocked by that task, so I'm not able to actually do anything. The UI is blocked, the main thread is blocked, so it's overwhelmed. There is no space to breathe right now. So that's why it's just blocking and animation doesn't work. Let's have a look at that once more. But once I do aspounding its worklet and do that business logic in worker thread and I get the result and refresh animation interaction click, everything works as smooth as it should work, right? This is exactly what we like to do in our application. But is that the only option we have? Web workers? Of course not. Webassembly is another amazing tools that we can run into it. And when we are talking about webassembly, immediately it comes to our mind. C plus plus C rust. Of course webassembly purposes that to run different application languages in the browser. Well, we know that, but we as a web developer, we like to do stuff in a way that we are doing. Javascript, typescript perhaps, right? It might be easier for us to do that. So in fact, if we have a tools that we don't need to go through all of these languages, c, c plus plus rust, et cetera, and just run our regular typescript javascript file and then get out webassembly, that might be quite interesting to us. And some smarter people have done that. Assembly script actually is a compiler that has a strict set of subset of actually typescript, and it compiles to web ASM or webassembly. So that is amazing. But what does that mean then when you say strict subset of typescript? So first of all, let me tell you that it's quite easy to get started with Adson Liftsgrip. It's amazing. So you can just simply download it from NPM and then you can simply just initialize your application and then once it's ready you have your typescript boost entry file and you can write your typescript. You just simply type a script export do function. But the only difference is that you cannot use all the available types in typescript. You can just use webassembly types and assembly types are shown in the presentation right here. Once you're familiar with these types, then the rest is just typescript. So you can just go ahead and write your functions and do whatever stuff a lot of other tools or maybe libraries you can find for assembly script to run other different things. For instance, I created an example at the end of the talk hopefully, is that it just encodes everything from your camera right in the browser and you capture, record and encode to mp4. Write everything in the browser without blocking your UI. No, nothing. So your application is quite responsibilities. But once you write your assembly script and it's done, then what you can do is you can just simply run a command and build your wasm file. And more interesting thing here is that when you're going to use this wASM file, why I'm saying this is more interesting because if you have worked with Webassembly and if you are going to use that without assembly script, you know that using these VASM files initialization and execution running and get access to those modules that you export, it's a bit of work in browser. Even in browser you need to do some stuff, some steps, but with assembly loader that's quite actually easy. So you just import instantiate a streaming function from loader of assembly swim and then you pass your vasm file and that's it. Then you have access to the function that you exposed and then you can easily use that here. So you see that is an example here for this function that I wrote and I just get access to that and I can change my state in my react application for instance. So let's actually go ahead and see an example with that. So imagine that now you are running an application again with an animation and once you're doing that without webassembly, the animation will actually or UI thread is blocking while when you are doing that in assembly's webassembly thread then it doesn't block. So you can simply get a very responsive application. But one thing that I need to emphasize here is that it's not like okay, we have these tools and we have to use it in any way. No, you need to actually measure what you are doing. Measurement is very important because what I can say about Webassembly is that although webassembly, when we say webassembly, maybe we say oh, it's quite fast. It might be true it's fast, but that's not, again, it's not the case. The most important thing about Webassembly is that it starts in a reliable path and stay in that pass. So if it start fast it will probably remain in that pass. But that's different from Javascript, right? It may be slow in the beginning, but then Ngin does some trick and magic, caching, et cetera, and then it makes it faster, right? So this unpredictability, it's not good for our application to make it reliable. When we have main thread, we make our application more reliable. But that's not the only thing we have these days. We have even more tools, new tools. We have new APIs called worklet. And worklet are amazing. The reason for that is in worklet it's similar to like web workers, you have a different thread and what it gives us is it's giving us a low level access to the rendering pipeline, audio or video, graphic. And that means, well, I can actually paint or I can render something or I process some audio without blocking my main thread. Just by calling this API quite performance, you have a very low level access to the engine. In the browsers there are a couple of different types of worklets. So for paint, animation, layout, audio, these are the types of worklets. But the one that I want to actually give you an example how it works and also it's more stable in different browsers because workers are quite new and it's not implemented in many browser yet. But Houdini or CSS paint API, Houdini is another name, maybe you're familiar with that. It has a better actually support in different browsers. So you may use that right now. And let me give you an example. Let's say you want to actually paint a checkboard, right? Okay, do you have different options? But one option here is that you can actually create how you want to paint that with this JavaScript file register or register your paint. This is what you want to paint, you want to render with graphic engine. And once you do that, first you need to check if you have access to this API or browser has support for this API, right? That is very important. This is feature detection and it's quite important, especially when you are building progressive web apps. Okay, so once you do that, so you can simply add this JavaScript file, this registered paint class into this module. Paints worklet has a module and then once you do that, then you can paint it. You can just simply in CSS say paint and then it will paint it. And how does it look? Then it will look like this. So simply you can run it and then you get what you are doing here. So imagine that you have very complex background. For instance, if you want to do it with JavaScript without blocking your UI, right? That is the way you can actually use this right now. And I can give you another example. Let's say you want to create like a barcode generator on the fly without blocking UI. You see the animation here is working smoothly while I'm actually generating these codes by changing level or writing my input. This is quite fast and high performance, no blocking at all. So that's one of the examples we probably use in our application in our daily work. Fantastic. So we have several tools and APIs which makes our coding style for using threads easier, right? And by doing that we can actually let our main thread breathe. Do not put everything on main thread shoulder because it may be tired. And once the main thread is tired then it cannot handle all of the things and it makes you and your client disappointed. So you can just simply hold off some of these tasks to the other threads with the different APIs. For instance, let's embarrass actually the power of these workers. And by doing that we are not only making our application more reliable but also usable. Our user will going to love it, right? So with that said, thank you very much for listening to me. And if you want to get access to the source code and examples, just go to this link and download. Or you can watch it online. And if you have any questions feel free to tweet me. My message on Twitter is open. You can send me any question. I'm happy to answer all of that. So good luck, see you next time.
...

Majid Hajian

Software Developer

Majid Hajian's LinkedIn account Majid Hajian's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways