How do you do this?
Sorry, I just-
Hello, hello, everyone can hear me?
Good.
All right, listen up.
We've got session Ali.
I come who's going to tell us about how he's optimized Z-Bus 95%.
Hello.
Hello, everyone.
Thank you.
I think you should wait and you're not disappointed before you plough.
So I have a very catchy title, and I'm guessing that's how you came here.
But let's see if I can actually entertain you with this thought.
So first of all, about myself.
I'm generally just an open source Linux guy from a long time.
I mean, doing Linux related stuff,
systems stuff for 25 years now, almost.
Yeah, love open source, and that's why I'm here.
And I'm from different countries.
We're already from Pakistan.
I also have finished citizenship.
Yeah, I lived in many countries.
These days I'm in Berlin settled down quite a bit.
So probably not moving.
Anytime soon.
But you never know.
So, and for last many years, people who know me,
they know that I'm very much obsessed with this crap thing,
that you're probably also into.
And I work for a company called user, and it's a startup in Berlin,
and we do end-to-end charging solutions.
And the MIT works on the charging station itself, all the software,
and it's all written in Rust.
Cool.
So, I'm really cool stuff.
But let's get to the talk.
And the first question is, like, what does it have as zibas?
Well, it's a pure Rust Dibas library.
And I'm guessing a lot of people don't know what that means.
And whatever the other zibas thing.
And I don't blame you because it's a very specific technology
that is in a specific domain solely.
And so if you don't know, that means you're just never,
you're not blessed or cursed or whatever you can say.
But it's designed to be a very efficient binary IPC protocol.
And it's like all the systems stuff that currently is there,
like all the system D services, all the grom,
k-d services, and application, they use this protocol heavily till this day.
It started more than 20 years ago, and it's still being heavily used.
And I needed that, so I created something for that.
And that's zibas, it's a Dibas library, as I said.
And before zibas, there was another library already called zibas.
Well, if you go in pretty high notes, they have squatted the name dibas.
And so actually a lot of people end up using it just because of that,
because that's the first hit when you search for dibas.
So that was already there for years.
And it was like a wrapper around a binary notoriously horrible C library,
CAPI, that nobody uses in C-worm.
So when it translated to rust, it wasn't that great either.
And it had multiple issues, not only because it was a wrapper around C,
but also the API wasn't that great, and those many things.
So that's why I created zibas, and we also have a really cool illustration by
Genome designer, Yakub Steiner.
It is really cool illustrations, and he made one for us, that's cool.
Yeah, I'm supposed to be the driver.
I used to have beard cutted today this morning actually.
And the thing is that over the years, it has become the go-to dibas create.
As I mentioned, the only reason that I know people still end up using dibas,
because of the name squatting.
But otherwise, if they really pay attention, then they always choose zibas,
because their API is much more economic, it's pure rust.
So porting is also easy to different architectures and stuff.
So yeah, it has won, basically.
And also like there's a lot of praises from people who when they use it,
they come and say, okay, this is awesome.
They might find a bug or two here and there, but usually they're super happy about this great.
So what is the problem then?
Everything is done, right?
Like it's a work, so it's everyone loves it, what's the problem then?
But before we go there, I have to tell you a bit more detail about how zibas is working under hood.
So before I made zibas, I made the lower layer of it called zivarian,
which is the encoding and decoding, or as we in rust formed,
like to call serialization and serialization, of the protocol, of the wire protocol, the encoding.
And zivarian is responsible just for that.
And the first version wasn't survey based, but the first issue anyone of someone file was,
why do you not providing a certain interface, because as you probably know,
in the rust formed for serialization and decerelization,
zivarian is decaying, like everyone just uses that, is taken from granted.
It's maybe it will be at some point part of the standard library even.
But 2.0 was the main change was that I made it based on zivarian,
and it was much nicer API as well because of that.
But I also noticed while working on this serdeic API,
that there are some very fundamental incompatibilities between zibas
and and serdeic model.
And also Dr. Samxten generally in rust, like for example, option.
So option is a type that is used a lot in the rust world for very good reasons,
because sometimes you need nullability, and this is a very safe way of presenting that.
But also it's true for any serdeic API that you,
and some process are actually require things to be aligned properly,
the specific types in a specific way.
And the way you do alignment is that you add zero bytes, like in serialization,
and in decerelization you skip these zero bytes.
But these, this alignment, this padding that you're adding in parts skipping,
is dependent on the data type.
So for example, that it will require four bytes,
64 one would be requiring eight bytes and stuff like that.
So it depends a lot on the type, and that's where the problem comes up.
Because in dbus spec, if you have an empty array,
you are supposed to add padding.
You have supposed to align for the first element, even if there is no first element.
So if it's an empty array, you are supposed to align the first element.
And that's, in my opinion, quite stupid, but that's what the stack is,
and it hasn't changed for 20 years, and it can't be changed now.
It's like it will break the whole world.
So that does not work well with serdeic, because in serdeic,
in serdeic, you have, for example, this is my very simple serializer, right?
This is what you, this is the trade that you implement if you are implementing a serializer,
which is obviously called serializer.
And in this one, when you are serializing a sequence,
which is the serdeic data type for a name, for arrays, for example,
that's what it maps to, that you first get like the serialized sequence called on you.
And this one, as you can see, does not have any information about the type that will be encoded,
not even in a generic way.
So you only, only thing you know here is that something is about to be,
some array is about to be serialized, what type of array?
No idea.
So if you have an empty array, this is when you get this,
you don't know how much padding to add or how much to skip in case of serializer.
And that's what makes it completely incompatible.
So I was hitting my head around this for a long time, because all encoding API that you will see with serdeic,
they just take your data type that you have, that your data.
And it only needs to implement two traits, from serdeic, serialized and deserolized,
serialization, deserolized, deserolization.
And as long as your type implement those, it's going to work for any of those farmers.
So you can put the same type, you can encoded into tomel,
and you can encoded into the JSON and all of those.
But it doesn't work exactly like that for debas, because of this fundamental incompatibility.
So I needed something extra.
So as you can see, like this, and you when you implement this sequence serializer,
you implement it, you get a serialized element, but in empty sequence,
you will not, this will never, never be called.
So you never get any information.
So I had to, how did I solve it?
Enter debas type signatures.
So debas has the concept of signatures that define what is encoded in a specific message.
And that is always actually transferred, transmitted with the message itself.
So you can write generic APIs, sorry generic clients that can encode and decode any service
without knowing anything about it.
So it's very introspectable, and this is part of that introspection.
So I was like, okay, I can just make use of this.
It's not as nice as for other formats, but I just need to have another trade called type
that people have to implement in addition to serialize and deserize,
if they want to do debas communication.
And it looks a bit like that.
It's extremely oversimplified here.
It's like very different, actually, in reality, the signature.
And it encoded in this, like this, in the beginning.
And it's just like, it's a wrapper around string, basically.
And then you have a type trade that all types need to implement,
and they just provide me a signature.
And there are cases where you need a dynamic type,
where the signature is more at runtime.
So that for that you have dynamic type,
dynamic signature, basically.
It's a bit me because, set day is everywhere.
Everyone can implement it.
Actually, most of the crates you find where they provide some data type that you can use.
They implement, set day four, you set day serialize and deserize.
You just have to enable a cargo feature usually called set day.
But they all provide that, but nobody is going to provide for debas,
special features or implementation because it's very niche.
So it's a bit me in that sense.
But it's also me in the sense that if there's a location.
So usually it's represented as a string in the end.
So when someone gives you a complex type, usually it is a complex type,
if it's a custom type because it's a struct or an enum.
And that one, you will have to put multiple strings together.
And it has to be a runtime.
For reasons I won't get into.
I tried really hard actually.
I talked to many rust experts.
It's actually possible that I put these strings together.
But the way I need it, it's not possible with current rust.
So that's also a problem.
And that means a lot of allocations.
And it's not constant anything.
But to make it still nice enough, as nice as I could possibly make it.
I've provided this type derived macro.
So you can just like, in set there, you derive serialized and
serialized.
You can also derive type at the same time.
So you just have to add one more thing.
And that's it.
So it's not a lot of work then.
So yeah, it's just like this in addition to these two,
you add one more to your type.
And also implementation for all the common types, like all the standard library types,
all the basic types, of course.
And also like popular external creeks, like Krono or UID,
and all the types that provide.
Because there's a ubiquitous, it's just makes sense.
But it's still like not not the best.
Still too many allocations, as I said.
And it's not constant, which I would really want to have because then you can,
you know, it's just zero constant, right?
And you want zero things to be zero cost, not high cost.
Oh well, I was like, okay, what about the performance?
Like, should we actually look at the performance?
Because I was just talking to someone earlier that a lot of times when you are talking about performance
and things like that, most people don't look at actual numbers and data.
They just assume that, oh yeah, you are depending on a lot of things.
So the compiled I must be really slow, but if you actually look into it,
usually it's not that bad.
So I was like, okay, what about the performance?
And fortunately, this one person who decided to write yet another
divas lives, lived divas.
And most people use another library from GIO.
And that one has really, really nice API.
For C API, it's really awesome.
It's a lot more inefficient than the divas for many reasons.
So the bar is low for efficiency and there as well.
But in any case, I thought like, what is the biggest bottleneck?
I need to know anyway, right?
So that's where we get to something called flame graphs.
Like, the flame graphs are something that shows you a lot of information of which,
what is your bottleneck?
What are the functions that are taking most time at the runtime of your benchmarks?
For example.
And there's a tool for cargo flame graph.
And you can use that for benchmarking your applications as well as your benchmarks.
And I use that for those same benchmarks.
And I know it's like really depressing.
I don't know what happened there.
Like when people created flame graphs, if they didn't know that there are other colors in the spectrum,
or they thought that this is good enough.
But it's insanely unreadable.
But the good thing is that it creates an SVG.
And you can then lower it in your browser.
And then you click on specific parts.
And then it expands it.
And then it's a lot more readable.
So this might not have any colors.
And it would be my think more useful, actually.
But anyway, that's what you have.
And that's the tool you have.
But it works.
So from that I realized that the biggest bottleneck was signature parsing.
Because as I said, I represented the signature with a string.
And then when I'm decoding, decentralizing and serializing,
I have to look at that signature because otherwise I don't know what type I'm encoding.
And let's say I don't, then I wouldn't know, for example, how much to pad.
So the MPRA problem was solved with that because I had access to the signature.
And I knew exactly what type is encoded in the area.
So but I was parsing it all the time.
And especially this was a special bottleneck for larger arrays.
Because for larger arrays, for each and every element that was I was encoding or decoding.
I was parsing the signature again and again and again.
And that was just that.
So I had many sleepless nights because of this.
And I was like, what can I do?
And I recently found out that I'm a bit high on the autism spectrum.
So that was explained me having sleepless nights about things that don't matter to most people.
But yeah.
And yeah, I was thinking a lot about this thing.
This is also a bit shows about how my wife knows me.
So she knows what I'm always obsessed about and stuff.
So she knew that I was thinking was it was, but yeah, this is not our picture, but yeah.
It's just a representation.
Yeah, so I was thinking about like, how can I make it constant?
Like it would be so awesome.
It would be constant.
There would be no cause at all of this biggest bottleneck I have.
And then if I had to move the biggest bottleneck, then I have really good performance.
And years go by, I'm still thinking from time to time.
I'm still asking people meeting rust experts like, how can I make it better?
Nobody's telling me anything.
Until last year, when I joined my company, and we used something called postcard and postcard PC.
Postcard is also a binary efficient IPC, or actually more like RPC and coding.
And postcard PC is just on top of a way to do it through USB.
Like it's designed for microcontrollers.
So to communicate between microcontrollers and a host from James Mann.
And I realized that James was doing something very similar.
He had a trade also that he was requiring in postcard RPC from all the types if they wanted to be encoded.
So it's like, but his was a constant.
So I had this kind of moment.
This is what I need.
Why am I not doing this?
I need to rethink my signature representation.
Because why did I think that representation has to be in a non-part format?
Why can't it be in part format from the beginning?
And that was like a mind-blowing revelation.
And I immediately started coding it during the night or something I think it was late.
And I was like, no, I have to do this.
I have to find out if this actually can work.
I couldn't sleep.
So I created this genome representation and it's really simple.
And at the one of the problems I had was like, in some cases, as I said,
I needed the signature to be dynamic.
So how do I make it dynamic if it's static only, right?
So and then I figured out that I can have further enums inside it like for child types or
a few types of structs.
I can have it either or.
And in case of static, I can just create it in static way.
And in case of dynamic in more like a dynamic way.
And then you do allocation.
If you need allocation, then you do.
But if you don't, you don't need to do any allocation.
You can make it completely constant static.
And that's what enabled this.
So my type trait became so simple.
And all constant static exactly as I was dreaming about for years.
And that was, that you have no idea how good it felt.
And this, this serializer and serializer don't pass anymore.
And if they have a representation pass in presentation now always about the types.
And they can, yeah, they just do a match basically.
And that's all handed at compile time.
Awesome.
Well, not exactly.
Actually.
Because we have something called various in deep as which is a dynamic type.
So that type and codes that type, which could be anything.
But it also encodes the signature with it.
So anyone reading it first gets a signature.
And they know, OK, what is it?
What is it?
They actually value in there.
And then they can do encoding based on the signature.
But that you think you get the signature at runtime.
So you have to do parsing at runtime.
So what do I do about that?
I was like, OK, let's also make the parser more robust and efficient for the key.
It's very efficient for the cases where I have to parse at runtime.
And I used this query where this is also very ubiquitous.
No, I'm creating parsing any kind of parsing you do.
You use a norm.
But I later was, I shared a bus ride along bus ride with the ad.
Still with the old stuff.
It was like that the case where we were better than deep as RS.
We are now really, really good better than that.
And then, and some things from milliseconds to microseconds.
And there is the nan in there.
That's because we got a panic.
And we got a panic because it became so efficient.
It was because this particular test was so fast that the deepest broker,
because it's the dimension that deepest is based on a broker architecture.
So there is a broker that sits between everyone.
And that was disconnecting us because we were so fast.
We were doing the connections now so fast.
Previously, they were not too fast.
Like we were doing multiple connections.
But it was so slow that we were not going above the limit that security limits of the deepest broker.
But now we were, so it just stopped us.
A few times it worked.
It was fine.
So in those times, I noticed that it was very small lumber.
But I didn't record it unfortunately.
Awesome.
But if you're good at math and like me, you would have noticed that I lied.
It was not that those numbers that I showed were not a 95%.
But that's not because I lied.
It's because it's very much hardware dependent.
And if you are on a high-end machine where these benchmarks were done, it's fine.
It's not, I mean, it's, it's not that bigger difference.
Because the locations and everything, they are super easy on high-end machines.
And there's a lot of parallelism and all that.
So it's, you don't see that bigger difference.
But it was still quite big actually, you saw.
But if you run the benchmarks, I asked people who had low-end machines.
And they did the benchmarks.
The machines, many of the test cases, they were 94.95.96% about in that range.
So it's not a lie.
So if you have a low-end machine, it will, you will see a huge difference.
And also, it's type depending.
So that 95% I'm seeing that was like fixed size arrays.
Even if they were huge arrays, still if they were fixed size arrays.
Like for example, integers and those kind of things,
then it was, it was really, really efficient.
So how much time I have left?
12 minutes.
Okay, good.
So I'm going to talk about the future now.
Sorry, not to test it.
So there are still some, I would say, maybe bottleneck,
small button, etc. in all of Z-bus.
But and I'm sure people can make it more efficient.
I have some to do as well for it.
But in general, there's not a lot to do left.
So, but what is left is for us to go move away from
us now.
Because it has lifted time.
It's time for it to slowly move towards its demise and death.
Because we, we are going to live in a modern times now.
And the, the bus is from very old times.
And it doesn't, it just doesn't fit anymore things.
Let not has a talk.
I think later about system D, future and fast.
And I think he will also talk a bit about this.
Because system D is very much moving towards this new protocol
Warlick.
It's very simple actually.
It's based on Jason.
It's like the unique sockets.
And you have Jason on top.
And there's special and coding and stuff.
But it's essentially Jason.
And as you know, Jason, like, if you know anything about
Cersei, it was like the first target was Jason.
So it's like the Cersei and Jason.
They're really, really good match well.
And the person who is meant in her is the same person.
Like Cersei and developer and Cersei Jason as the same people.
So it's not a big surprise.
And also all the data types match well like you can use options.
You can, you can have no level types.
Twenty-first century stuff.
And yeah, it's in general it's just better.
But you might ask like, what about the parsing cost?
And you're right like, of course there's a bigger cost because it's not a binary protocol.
It's, it's text.
It's a human readable thing.
So there is that.
But you would be surprised.
Because I, Leonard was telling me like I sometimes meet him for lunches.
And he's, he was telling me like about whirling.
And I was like always like, you're real wrong.
There's a lot of performance problems there.
And you said, no.
There's a lot more problem with context switching.
And that's a, that's a bigger bottleneck.
And since Debus has designed more to be a broker based,
There's a lot more context switching involved in,
Warling, which is a P2P protocol.
And I didn't believe in for a long time.
And I was sure that he's wrong.
So I went on and wrote some benchmarks.
And those benchmarks proved him right and proved me wrong.
So I was very, it was very hard for me to send him a link about that.
But yeah, it, he was right.
Usually he's right in the end.
So yeah.
And, and the thing is that it's because of this P2P,
It's not just that there's less context switching.
It's also enabled.
Other things that you would normally not think about.
For example, in Debus, you have limited amount of connection.
As I said, like our test case was panicking,
Because it was launched into many, many connections.
But that's because Debus limits your connections.
And that's because there's like a broker.
And it has to have some limits for security reasons.
But if you have a peer to peer, you can have as many connections as you want.
The same sign, same service, you can have as many as you want.
And that means you can have exterior mutability.
Instead of interior mutability, I don't know how much people are here.
Familiar with most model of doing things.
But with exterior mutability, you can have really, really good performance
Because you don't need to have locks at runtime to guarding things.
And you can have it at more at compile time.
The compiler is checking if you have no mutable references to the same thing
And stuff.
So it's really good for performance.
And that being able to have as many connections as you want,
Really enables that.
And also in Debus, you have connection overhead.
Like when you establish it, there's a handshake,
protocol.
But in case of a violent, you don't have that.
So it also makes it super cheap to launch new connections between things,
between programs.
So it avoids a lot of allocation,
Cloning because of that as well.
In Zebus, we have quite a lot of cloning mainly because of that.
Because you can have multiple, because you need to share the same connection.
That's the way to do it in Debus work.
So when you share the same connection and you multiple parts of your code,
multiple threads, in task, in task,
are wanting to receive messages at the same time.
You have to do a broadcast channel.
And that means you have to clone.
I do it efficiently using Arc.
But Arc also has its, it has a smaller overhead.
It's the smaller your machine, the more the overhead.
And also it would be very difficult for me to do no STD, for example,
to not use STD and have really small size in Zebus.
Maybe I can do no STD, but no analog is out of the question.
I think for now, at least it would be really, really difficult.
Maybe I can do it, I think.
But in case of a warling, that would be a lot easier again
because of being able to have multiple connections.
That enables a lot of things.
If you're interested in more in depth, why that is the case,
I can talk to me afterwards, I can explain to you.
Anyway, there is a risk rate that exists for warling.
Guess the name?
It's called warling.
But it has a few issues.
First of all, main problem is that it's just blocking API.
It doesn't provide any sync API, which is me because it's an IPC.
And it's designed for code generation only,
which is sometimes it's good.
But it's not always what you want.
You want to use your existing type that
implement serialized and decentralized with it as well.
So it's not good for that.
It's not designed for it, at least.
And it does a lot of allocations.
It does allocation as much as it wants.
So it's not like I would want it to be.
And it's not really maintained.
I'm the new mainteriner, one of them.
So that tells you that how convenient it is
because I got the mainternership so that maybe
there would be a bit more maintenance than there is right now.
But I'm not familiar with the actual code that you're working all of it.
So it's very hard for me to maintain it.
Especially if I know that we can do better.
So if we can do better, why not do better and do it from scratch.
And I have a plan how to do it.
It's a bit weak, but it's based on experience.
I recently at work did SDK for a protocol.
That is very, very similar to Warlink.
And yeah, based on that experience, I realize how I can do a
no-estidiv compatible implementation that is anything.
And it uses very little allocation, if at all.
And hopefully it would be the same credit because I'm now the
maintenance or I have a say, but if not, then it's not
we're going to do our own.
And also that just final thing.
The diva is not going away any time soon, at least.
It will hopefully go away slowly, but it not any time soon,
because there's so much belt on top of it.
But it will remain around for a long time as legacy.
So you still need z-bus for a while.
So yeah.
Sorry.
Am I over time?
No.
Any questions?
Ooh, question.
So you introduce a signature.
But how does that signature get matched then?
Is that just a...
Sorry, I can't hear you.
Yeah, you need to speak up of it.
Okay.
The signature you're introducing.
How does that get matched?
Or does the compiler ready like ordered that?
Is it a one time or a lock-end sign?
I haven't measured in that way.
I'm how it makes it super efficient.
It somehow enables some compiler optimizations.
So it does, I think it does matching in parallel using
same-do or something.
So there is a lot of optimizations in Vinau itself that one can use.
And we are, I'm using them.
Some of them, it is.
Thank you.
You'll have to speak up, even with the mic.
Sorry.
Can you get this one closer?
When should we start using bar length?
Well, once we have good API for you.
I mean, if you're doing it in Rust, you would want a good Rust library, right?
The hope is that I will be able to work on it on my work time.
But we'll see.
I'm working at a start-up.
I don't know what the promises mean.
But that's at this desire because we will benefit from it too.
And my plan includes making it work for microcontrollers as well,
like to be communication between host and a microcontroller, for example.
Cool.
Awesome.
Thank you.
Thank you, everyone.