Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What might a slimmer serde look like? #1412

Closed
KodrAus opened this issue Oct 15, 2018 · 5 comments
Closed

What might a slimmer serde look like? #1412

KodrAus opened this issue Oct 15, 2018 · 5 comments

Comments

@KodrAus
Copy link

KodrAus commented Oct 15, 2018

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.

@dtolnay
Copy link
Member

dtolnay commented Oct 15, 2018

In what scenarios would it be useful to depend on a Serde trait that has no impls?

@KodrAus
Copy link
Author

KodrAus commented Oct 15, 2018

The general case would be something like I only need T: Serialize or T: Deserialize and care a lot about compile times. I imagine that I only need T: Serialize or T: Deserialize actually covers a fair number of use-cases by pure coincidence, but the number of cases that also care enough about compile times to jump through hoops is probably negligible.

The specific case I'm looking at is structured logging in log, but we don't have consensus that serde is the most appropriate tool for the job there so I wouldn't put too much weight in that.

@dtolnay
Copy link
Member

dtolnay commented Oct 15, 2018

But bounds like that are only useful if some other library in the dependency graph enables all the impls.

If you want a trait with no impls, it should be easy to shim in your own rather than removing impls from Serde's traits:

#[cfg(...)]
use serde::Serialize;

#[cfg(not(...))]
use self::private::Serialize;
#[cfg(not(...))]
mod private {
    pub trait Serialize {}
}

@KodrAus
Copy link
Author

KodrAus commented Oct 15, 2018

Yeh, there's still a fair improvement to compile-times when excluding one of the ser or de modules but retaining its impls but thinking about it a bit more it just doesn't seem genuinely useful at all. The path I followed to get here was pretty convoluted.

@KodrAus KodrAus closed this as completed Oct 15, 2018
@dtolnay
Copy link
Member

dtolnay commented Oct 15, 2018

Sounds good. On the plus side, when we stabilize const generics and variadic generics I look forward to the 50% compile time improvement from doing the array impls and tuple impls that way!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants