How I optimized zbus by 95%

Zeeshan Ali Khan

About myself

Open Source & Linux Systems Engineer

🇵🇰 🇫🇮 🇬🇧 🇸🇪 🇩🇪

🦀

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;
}

pub struct NamedType {
    pub name: &'static str,
    pub ty: &'static SdmTy,
}

pub enum SdmTy {
    Bool,
    I8,
    Seq(&'static NamedType),
    Struct(&'static [&'static NamedValue]),
    ...
}

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?

Micro-optimizations

A few already in

  • Tait Hoyem
  • Emilio Cobos Álvarez

The future of IPC

Varlink

My other talk

D-Bus not going away

Legacy

Questions?

https://github.com/dbus2/zbus

Explain NaN

Only 91% in the table