How I optimized zbus by 95%

Zeeshan Ali Khan

About myself

Open Source & Linux Systems Engineer

🇵🇰 🇫🇮 🇬🇧 🇸🇪 🇩🇪

🦀

JUCR

🚙

What's zbus?

Pure Rust D-Bus library

Ok and WTH is D-Bus? 🤔

Effecient binary IPC protocol

Before zbus..

There was only dbus-rs

libdbus wrapper

Multiple issues

zbus

Goto D-Bus crate

What's the problem then?

Before we go there..

zvariant

serde-based since 2.0

Fundamental incompatibilites

Option<T>

No Nullable types in D-Bus 😥

Empty Array Alignment

wikipedia.org/wiki/Data_structure_alignment

impl serde::Serializer for MySerializer {
    type SerializeSeq = MySeqSerializer;

    ...

    fn serialize_seq(
        self,
        len: Option<usize>,
    ) -> Result<Self::SerializeSeq, Self::Error> {
        unimplemented!()
    }
}
impl serde::SerializeSeq for MySeqSerializer {
    ...

    // Never called for empty sequences.
    fn serialize_element<T: ?Sized + Serialize>(
        &mut self,
        value: &T,
    ) -> Result<(), Self::Error>
    {
        unimplemented!()
    }

    fn end(self) -> Result<(), Self::Error> {
        unimplemented!()
    }
}

How did I solve it?

Enter D-Bus type signatures

(iba{sv})

Type trait

// over-simplified.
struct Signature<'a>(Cow<'a, str>);

trait Type {
    fn signature() -> Signature<'static>;
}

trait DynamicType {
    fn dynamic_signature(&self) -> Signature<'_>;
}

Meh!

Type derive

#[derive(Serialize, Deserialize, Type)]
//                               ^^^^
struct MyStruct {
    a: u32,
    b: String,
    c: Vec<u8>,
}

impls for commonly used types

Including external crates

e.g chrono, uuid, etc

Too many allocations

Not const 😥

Oh well. 🤷

What about performance?

https://github.com/KillingSpark/rust-dbus-comparisons

cargo bench + criterion

Library Mixed StrArray BigArray Enc + Send
dbus-rs 168 µs 1.33 ms 377 µs 262 µs
zvariant 54 µs 1.05 ms 538 µs 246 µs

Not too bad?

Bar is low

Biggest bottleneck?

Introducing cargo flamegraph

Signature parsing

Especially large arrays

Many sleepless nights

Years go by..

postcard & postcard-rpc

trait Schema {
    const SCHEMA: &'static NamedType;
}

Rethink Signature Representation

enum Signature {
    Unit,
    Bool,
    Byte,
    Int16,
    ...
    Array(Child),
    Struct(Fields),
    Dict { key: Child, value: Child },
}
enum Child {
    /// A static child signature.
    Static { child: &'static Signature },
    /// A dynamic child signature.
    Dynamic { child: Box<Signature> },
}

enum Fields {
    Static { fields: &'static [&'static Signature] },
    Dynamic { fields: Box<[Signature]> },
}
trait Type {
    const SIGNATURE: &'static Signature;
}

trait DynamicType {
    fn signature(&self) -> Signature;
}

(De)serializer don't parse anymore 👍

Well, not exactly..

Variants

More robust & efficient parser

nom

winnow

Parse once

It works!!

Performs better?

Library Mixed StrArray BigArray Enc + Send
dbus-rs 168 µs 1.33 ms 377 µs 282 µs
zvariant 3 54 µs 1.05 ms 538 µs 246 µs
zvariant 5 9 µs 202 µs 47 µs NaN

🙌

You promised 95%! 😡

Hardware-dependent

Type-dependent

The Future?

Varlink

JSON

Better serde-compatiblity

Parsing costs?

Context-switching is expensive

p2p

Other optimization opportunities

Many connections -> exterior mutability

Avoid allocations/cloning

no-std

no-alloc

A Rust crate exists

Guess the name?

Yes, it's Varlink

A few issues

  • Blocking API
  • Designed for code-generation
  • Lots of allocations
  • Unmaintained

We can do better

I have a plan

A bit vague but...

...based on experience

The same crate (hopefully!)

D-Bus not going away

Legacy

Questions?

https://github.com/dbus2/zbus