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.