We're going to get started, but how to lose weight?
I'm going to put your mic on.
It is on.
Yep.
You can hear me?
Yes.
Hello, everyone.
I'm up all as well, Terry.
I work at Vullment Automation, which is a Finnish national or global company.
I'm working on a software architect and a programmer for a browser-based UI platform
for running factories, basically.
So if I make a mistake, then like millions burn in a day.
It's fun.
So I'm a TypeScript developer by day.
I'm acquiring an open source enthusiast and albatross.
I'm a data-oriented design-seller, which you might see very soon.
If you've not heard of data-oriented design, I'm not going to explain,
but maybe you'll kind of get it anyway.
And I'm developing the Nova JavaScript engine in my free time.
Written in Rust with data-oriented design principles in mind,
which you will maybe also see a bit about here.
So losing weight.
What do I mean by weight?
First of all, whatever I mean, this is supposed to be like fun and like whimsical.
But mostly, I mean, the amount of memory your program uses at runtime.
That's what we usually think about.
Though, remember, your code is also weight.
Get sent to the client or even if you're in no JS or something,
it actually gets saved on the disk.
Also, if you write horrible, horrible code,
and you make your program so complex that nobody can understand.
That is also weight.
The complexity of your program and the context knowledge required to work with it is also weight.
And I posit that for any program there exists an ideal weight.
And this is kind of like a mix between the three kinds of weights there.
You can take your own pick, like which one do you prefer.
I'm more of the, like, take out memory, increase the others,
but not by too much.
So I'm going to talk about losing weight as in approaching the ideal memory weight,
without ballooning the rest over much.
Luckily, we can calculate the ideal memory weight.
How many of you have heard of Shannon entropy?
How many of you have heard of succinct data structures?
I heard about them yesterday, and I was like, I've never heard of that.
Oh, oh, oh, I know what those are, okay.
Basically, Shannon entropy, which I will not explain,
but it is the amount of surprisingness in your data.
How surprising is your data? That is Shannon entropy.
Or another way to say it is how many yes or no questions must be answered before the data is understood.
For, for example, you have a stream of bits where every 500 bits,
there is one non-zero value.
So it's always zero except like one in 500 on average.
I asked one question, are the next 500 bits all zero?
If the answer is yes, I have understood the data,
and now we can also say that the amount of Shannon entropy in that data is roughly,
maybe one bit.
So Shannon entropy gives you the amount of bits of information that your data holds.
Not the amount of memory that you use to hold it,
just the amount of data there is, the amount of information there is in that.
All right. Here are some JavaScript values.
Always the same size regardless of what kind of data it holds,
kind of on the stack anyway.
So it's four bytes in Chrome and eight bytes everywhere else,
unless you are in a 32-bit machine, hopefully not anymore.
And Boolean and small integers, or in some engines, all numbers,
are already held in that four or eight bytes directly.
But then any object, any array, any symbol, so-and-so forth,
those also have data on the heap.
And you can see those values taken from node and Chrome there.
Basically, it's always one header reference,
and that header reference is now the size of a JavaScript value,
because the JavaScript value is the size of a reference in the engine.
Plus than some data, like a number is header reference,
that's four or eight bytes, plus eight bytes of double data,
so 16 bytes or 12 bytes.
And as we go down here, you can see that an object is 24 or 12 bytes,
node or Chrome, but when you go to array buffer,
or uintake, array, they're 96 or 52 bytes, and 104 or 60 bytes.
Huge objects, and those are empty.
You have no data, no information in those.
It's just the object itself.
Okay, so how to actually lose weight?
Well first, the previous talk mentioned oversharing.
Don't overshare with your client.
Remove the things you don't need.
The things you don't need in the client that are never used,
so it should be removed.
This is like, obviously, kind of done in simple,
but yes, this does happen.
I removed a lot of stuff recently.
Second of all, get rid of Booleans.
You want mate?
Booleans are really nice, really cool, in many ways.
Very convenient, but it's four or eight bytes.
This is a store one bit, one bit.
That is, like, if you're in Chrome,
where the size of a value is small, smaller than elsewhere,
that's 3% of memory used at all.
3%.
Like, you have 64 gigabytes of RAM in your computer,
and now I'm going to say, hey, you're allowed to use four gigabytes of it.
How about?
No, please no.
So we want to get rid of Booleans, where we can.
We want to use context knowledge to get rid of stuff
or optimize stuff down to smaller sizes, where we can.
And we want to be able to choose appropriate data sizes or
storages for data.
Whenever we can, based on context knowledge,
based on the system that you're working with.
You've been working on that program for, like, ten years at this spot, right?
How many of you have been working for ten years in the same thing?
Yeah, I know you're there.
You know exactly what kind of data there is in the system.
You can use that context knowledge.
And you want to get rid of the heap headers,
and actually objects, mostly, or entirely,
if you can at all.
Now, this is starting to sound a bit weird, like,
wait, JavaScript without objects, like,
what are you talking about?
Struct of arrays is what I'm talking about.
Yeah.
This is the data oriented design side here.
If you go structure of arrays,
IE, you take your group of objects,
and you turn it into one object which has multiple arrays,
or type arrays, or maps sets.
You can use context knowledge to store
only the amount of data that you absolutely need.
And you can get rid of booleans.
Okay.
Let's look at code.
Now, this will be fun.
I will have to try and find how to get my code edited back here.
Where did you go, man?
Where did I put you?
Okay.
I have lost my code editor.
That's good.
Now, I need to go to losing weight.
Only come on.
There we go.
Now, that's much better.
I have way too many code examples here.
And unfortunately, I'm going to pick the simplest one,
which is not real code.
I have an actual real example,
but I wouldn't have the time to go through it.
So I'm going to give you a done simple example of what
array of struts, the structure of arrays transforms.
I mean, what kind of things you can do here to optimize.
So I have here an object, component object.
Some kind of retained mode UI stuff.
There's a type, so this component,
no, obviously there are many types of objects.
This component apparently knows what type of object is.
There's an enabled Boolean, which somehow, I guess,
takes the component out of the whole UI maybe.
Hovered, selected, is route with height, parent, children.
Seems fairly standard, right?
We have a component pretty much like this.
Oh, yeah, and then there's this scope name here.
This is going to be fun kind of two-part identifier.
So path hierarchical path to a container,
and then a unique name within that container.
Remember that.
And as we see, there's parent and children.
So this is a hierarchy of components.
IE, it's a group.
Somehow a group of components, which form a larger set.
This is not an array of objects right now,
but we can still actually think of it as an array of objects.
It's just structured in this tree structure or a hierarchy structure.
And we can turn that into an array of objects.
First, I'm going to get rid of some Boolean's.
Or actually even before that, I'm going to get rid of something I don't need.
Is route Boolean there?
You didn't need it, did you?
Your route, if your component is a route or not,
is decided by it having a parent.
Why did you have that Boolean there?
You probably added it because it was convenient, right?
Yes.
I can check it like this, right?
Nice.
Yeah.
Let's get rid of that.
But then, the Enabled Boolean.
That's one bit of information in each object,
costing you four or eight bytes.
It adds up.
When you have thousands of these, it really adds up.
So what we can do instead,
is that we split the Enabled and disabled components from one another already at a higher level.
We don't have any disabled components in the hierarchy at all.
Obviously, maybe in your case, the components actually,
they need to retain their position in the hierarchy,
and they need to kind of know if they're enabled or disabled,
while they change that state.
So maybe this split isn't possible for your case.
But sometimes this is a possible split.
You can move the disabled components over here.
And now, actually, if you move them over there,
you can probably get rid of a lot of their data,
because like a disabled component,
which isn't in the UI, probably is never hovered or selected, right?
So you can get rid of them in those cases.
And that just means that when you move your component over from being disabled to becoming enabled,
hoping data around, but I assume your components aren't constantly enabling and disabling.
So maybe it makes sense.
All right, and I've moved the components over to this component table,
which is here.
And it looks pretty much like the component looked,
except now, every single thing here is kind of an array of some sort.
So this is not an object that exists for each component.
This exists for each hierarchy of components.
Each array there holds data for all the components.
So that type there, it is now a flexible index array.
IE just a uint8 or uint16 or uint32 array.
And it is as long as you have components.
It's length is the number of components you have.
And now your component has an index.
I am component index 4.
Okay, I will look into the type list or type array at index 4.
And I will find there, what's a uint8 or uint16 or uint32 array.
So I find another number there.
That points to our data storage somewhere here, there, data parts.
I have a list of unique strings, unique type strings.
So all our components now, they don't actually know their type.
They know their type index.
And you can just go look up the actual type string here.
And that list holds no extra data.
It's a unique list of unique strings.
Now, because probably your list of types is fairly small.
Maybe it's like 200.
You are only using one byte of data, or maybe two bytes of data,
to store your type per component.
All right.
Harvard, I'm making an assumption.
Maybe your system is such that you don't have like,
hierarchical hovering type of thing.
There is only ever one component that is hovered at a time.
So why do we have that Boolean in every single component?
We don't need it.
We need one index, which component is currently hovered,
or if none, the none.
Okay.
Save the bit of data there, or memory.
Selected components.
Again, why do we have that Boolean there?
You probably don't have all of the components selected at the same time.
It's probably changing.
Maybe you have these three, these four components selected right now,
and then some at a later time.
So just put that in a set, or even better.
If you can, if you don't mind it being a little slower,
maybe to look up, make that an array that you just push and pop from.
Get to your little bit better,
otherwise, but it's negligible most.
All right.
Parent.
Now, this is just what we had before.
So we have an index list.
And now, this is the flexible index array again.
How many components?
This is pointing back into the component table.
How many components have?
You have that is the length of this array,
and each item here is an index into the component.
It's into the component itself, component table itself.
And if this component doesn't have a parent,
then we instead use the maximum value for that array.
So in a unit eight array, it would be 255.
If you see that this is pointing to an index 255,
then you know, okay, that just means that there is no parent.
Note that that does mean that if your array length,
if your number of components is 255,
you actually need to use a unit 16 array,
because otherwise it would get like it wouldn't work.
Okay.
Children, again, I've made an assumption here.
This is where context knowledge would come in.
You know what kind of components are common in your system.
I've made the assumption that in this system,
it's actually uncommon that there are children.
Maybe this is like a very wide kind of system,
where your root component has a lot of children,
and those may be sometimes have children,
but mostly it's just like one very, very wide children list.
So in that case, it doesn't make sense to have a list of components,
or an array of components,
component indexes for each component that you have.
It may be makes sense to have a hash map,
where you can look up,
hey, my index is four,
do I have children,
and the hash maps is no you don't.
Okay.
If instead,
it's common that your components do have a small amount of components,
or children, sorry.
Then it would make sense to actually turn this into two types of arrays,
one for the number of components,
a number of children you have,
and another that points into a type array of component indexes.
And that with that,
you would get,
well, the number of children then,
basically becomes usually a unit eight,
so you're using one by for that,
and then you point to this type array list of children indexes.
You point to the beginning,
and your length determines how many items you have there.
It becomes a bit more complicated,
and that then requires that you need to do it
or own garbage collection with that,
so you have fun.
But it does give you the optimal memory usage.
It becomes a basically succent data structure,
where it uses only the amount of memory that you absolutely need.
The width and height values that we saw before,
those were string or number.
I'm guessing this is probably a pixel based system,
but there is an optional possibility
that you can give like 100%
or some calc value,
like basically,
yeah, you're usually using pixels,
but if you want to do CSS,
you can.
But usually you use pixels, right?
And usually you use pixels that are less than 10,000,
because your screen isn't very wide.
So we can just store the width in,
and height in you in 16 arrays by default.
And if you need the more advanced CSS features,
we will store the maximum value,
essentially we assign minus 1 to these arrays for your index.
And then we have these hash maps on the site,
where we then store the rest of your data.
Like if you have,
for some reason your width is 3 million pixels.
Okay, sure,
we will put it in the hash map for you.
Or if your width is calc,
100 minus 20 pixels,
okay,
we can store it there.
But we don't want to be keeping,
or this basically,
it saves us 2 or 8 by 2 or 6 bytes on every single,
every single component for their width height values on average.
Then the scope name that I had there before.
This is something I really like.
So the scope name was 2 part string.
First part is the hierarchical path.
Essentially you can think like file path, for instance.
Path to a folder where this component is defined in.
And then the name part,
which is a unique string within that folder.
I'm going to make a guess here.
Or actually this is based on our system,
but let's say I'm making guess.
The names in a particular,
in a particular container,
a particular path,
a particular folder,
are all to generate it.
Nobody usually,
like you can change the file,
file name, or the name part,
but most people don't.
You just create like 1000 components,
save,
and they are component 1 component 2 component 3.
So, so on and so forth.
Now, within if you take the path and the name together
and take it into a string,
you will never ever find any duplicates anywhere.
It is a completely unique identifier.
But if you split them apart,
path and name,
and the name is also generated,
usually not changed.
Path is a folder where you have 1000 components.
These two things repeat all over the system.
Everywhere, everybody has like,
you have everybody in the same folder
and then unique names
that are repeated in the next folder over.
So, we can just put those unique names
and unique scopes or paths,
folders into different string arrays here.
And then again, we refer to those with a flexible index array.
IE,
if my unique name, name 1,
or component 1,
is the first unique string that we have.
Name string that we have,
then this will hold the index 0,
and I can just find my name here,
in the index 0,
and I get component 1 from there.
I do the same for my scope.
I combine them,
and that's my actual unique identifier.
Now, why would you do this?
Well, you say memory,
the browser will not,
or your engine will not actually remove
duplicate strings very aggressively,
but you can do it yourself this way.
And now,
if you need to compare two unique identifiers,
normally it would be a string comparison.
Now, it's a comparison of two numbers,
or four numbers, two pairs of numbers,
between each other.
If both values match,
then this is the same unique identifier.
Otherwise, it's not.
All right.
So, that's pretty cool,
I think,
I think anyway,
yeah.
Where is my...
Yes, good, good, good.
Don't worry about the weirdness here.
So, in conclusion,
remember,
losing weight is about approaching the ideal memory weight,
without ballooning the rest.
Don't go whole wild.
Don't do this for every single thing you have.
If you have like 10 components,
not worth it.
But if you have 100,
if you have 10,000,
then starts making sense.
Let's see if I have...
Yeah.
Okay.
Yeah.
Maybe it comes up later.
But, yeah,
using structure of arrays,
enables you to use the tools
that JavaScript has for optimizing memory,
for using,
or kind of,
using the actual ideal memory weight.
Yeah, here is that some really examples.
So, I,
at a costumers place,
I saw 80 megabytes being used in a single thing.
And that was horrible,
and I took it through this filter,
and got out nine megabytes out of it.
And then I generated some very repetitive test data,
which ballooned the original structure,
sized to 350 megabytes,
15 megabytes after optimization.
Okay, that was heavily repetitive,
so it makes sense that it optimizes the weight better,
compresses the weight better,
but still.
And then a different example
where I did this,
I got from 10 megabytes to 3 megabytes,
or 2, 3 megabytes with the same stuff.
Okay.
But you're probably thinking about this.
I showed you that type arrays are horrible.
Like 100 bytes for a type array,
and an array buffer in no Chrome and 200 in node,
like,
isn't that horrible?
Like, why would I use a type array if it's so big?
And obviously the point is that the item sizes,
then small, but yes,
this is a real kind of fundamental problem with,
not like object oriented programming per se,
but structural inheritance.
Structural inheritance basically says,
when you inherit a class,
and you become more focused,
you have better focus.
This is better use.
It only ever grows,
which is kind of the wrong way around you would think.
But yeah, array buffer still makes sense.
They break even at 20 or 50,
depending on kind of your use case.
But the engine could still lose weight.
And if it did,
it would be a Nova JavaScript engine.
Basically what we do is use these kinds of tricks
to approach ideal memory weight,
but become a bit more complex,
and have more code, way more code.
So our sub-class objects,
IE, when we do an un8 array or type,
any type array or array buffer,
they no longer grow in size.
They actually shrink in size,
because they drop out all the objects stuff,
and we have a hash map on the side.
Like, if you really want to use that un8 array,
or that array buffer as an object,
we will make it possible,
but it's not going to be cheap.
And like,
we actually have our current array buffer,
is 32 bytes,
and our type array is 20 bytes,
and I can push them down to 12 or 16 bytes each.
So, I have more examples.
I have more slides that cover these in kind of more detail,
but I'm going to leave those for you to discover.
There's the GitHub link for the code.
There are more code examples,
the real code that I have done at work,
which I didn't have time for,
and then links for no large JavaScript engine.
You can read my blog posts on some of this stuff,
and so on.
Questions?
Thank you.
Cool.
Is there a getting started page?
No, not at the moment.
There's a fairly large amount of contributing information
for the engine.
The engine is still very rough.
We pass only a bit more than 50% of the test 2,
2, 6, 2, test 3, sweet.
So, it's not like, don't take this into production,
but if you want to program in Rust and work on a JavaScript engine,
you can see how the sausages make so to say.
We are there for you.
Yeah.
So, it seems to take away a lot of the dynamicness of the transcripts.
And if you look at that, you can also think you're very,
actually, you are reading it immediately.
So, why don't you send the script,
which is that it's a little hard to send in
that those orders for you, which is not the easy task.
That would probably be a bit,
but the question was, why not use assembly script,
and then turn into resin and, like,
have an actual proper engine do it for you?
That would probably be the smarter idea, yes.
Okay, thank you.