Skip to content

What might a slimmer serde look like? #1412

@KodrAus

Description

@KodrAus

Hi! 👋

I've been experimenting recently with whether it's possible to get a slim dependency on serde that still lets you work generically with its core traits. Basically enough to know that T: Serialize or T: Deserialize<'de>. This usecase is probably pretty niche, so please go ahead and close it if it's not something you're interested in supporting. I've mostly been looking at compile times rather than artifact size, since we'll drop any impls that aren't actually used anyway when compiling serde in a binary.

It looks like about 50% of the compile time is spent on the code generated by these macros:

mod de {
    mod impls {
        array_impls! {
            1 => (0 a)
            2 => (0 a 1 b)
            3 => (0 a 1 b 2 c)
            4 => (0 a 1 b 2 c 3 d)
            5 => (0 a 1 b 2 c 3 d 4 e)
            6 => (0 a 1 b 2 c 3 d 4 e 5 f)
            7 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g)
            8 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h)
            9 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i)
            10 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j)
            11 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k)
            12 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l)
            13 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m)
            14 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n)
            15 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o)
            16 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p)
            17 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q)
            18 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r)
            19 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s)
            20 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t)
            21 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u)
            22 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v)
            23 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w)
            24 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x)
            25 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x 24 y)
            26 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x 24 y 25 z)
            27 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x 24 y 25 z 26 aa)
            28 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x 24 y 25 z 26 aa 27 ab)
            29 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x 24 y 25 z 26 aa 27 ab 28 ac)
            30 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x 24 y 25 z 26 aa 27 ab 28 ac 29 ad)
            31 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x 24 y 25 z 26 aa 27 ab 28 ac 29 ad 30 ae)
            32 => (0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j 10 k 11 l 12 m 13 n 14 o 15 p 16 q 17 r 18 s 19 t 20 u 21 v 22 w 23 x 24 y 25 z 26 aa 27 ab 28 ac 29 ad 30 ae 31 af)
        }

        tuple_impls! {
            1  => (0 T0)
            2  => (0 T0 1 T1)
            3  => (0 T0 1 T1 2 T2)
            4  => (0 T0 1 T1 2 T2 3 T3)
            5  => (0 T0 1 T1 2 T2 3 T3 4 T4)
            6  => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5)
            7  => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6)
            8  => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7)
            9  => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8)
            10 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9)
            11 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10)
            12 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11)
            13 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12)
            14 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13)
            15 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14)
            16 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14 15 T15)
        }
    }
}

mod ser {
    mod impls {
        array_impls!(01 02 03 04 05 06 07 08 09 10
                     11 12 13 14 15 16 17 18 19 20
                     21 22 23 24 25 26 27 28 29 30
                     31 32);
        
        tuple_impls! {
            1 => (0 T0)
            2 => (0 T0 1 T1)
            3 => (0 T0 1 T1 2 T2)
            4 => (0 T0 1 T1 2 T2 3 T3)
            5 => (0 T0 1 T1 2 T2 3 T3 4 T4)
            6 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5)
            7 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6)
            8 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7)
            9 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8)
            10 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9)
            11 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10)
            12 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11)
            13 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12)
            14 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13)
            15 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14)
            16 => (0 T0 1 T1 2 T2 3 T3 4 T4 5 T5 6 T6 7 T7 8 T8 9 T9 10 T10 11 T11 12 T12 13 T13 14 T14 15 T15)
        }
    }
}

Which seems reasonable since those macros generate a lot of code.

To see how much overhead the impls have over the trait definitions, I've made a few tweaks to the core serde library:

  • Move just about all the code from serde into a new crate, serde_core
  • Make serde just re-export the serde_core API and forward its existing crate features
  • Feature gate the ser and de modules, and the ser::impls and de::impls modules in serde_core, but enable them by default.

The split between serde and serde_core was just so that libraries depending on serde with default-features = false keep the same behaviour.

If a library depends on serde_core instead of serde it can disable crate features to slim down compile times. If serde proper finds its way into the dependency graph then those compile times will return to normal.

The results could look something like this:

features compile time rlib size what's built
- 7.53s 3.7M exactly what's in serde today
no-default-features + ser 1.88s 168K just the serialization traits without any impls
no-default-features + de 1.44s 276K just the deserialization traits without any impls
no-default-features + ser + impls 3.34s 528K just the serialization traits and their impls
no-default-features + de + impls 5.69s 3.2M just the deserialization traits and their impls

I'm not necessarily putting this approach forward as a good idea, I haven't thought too hard about it, but was an interesting thought experiment I thought I'd share.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions