Skip to content

Avoid monomorphization bloat in Visitor::visit_{i128,u128}()#3064

Open
max-heller wants to merge 1 commit into
serde-rs:masterfrom
max-heller:avoid-monomorphization-bloat
Open

Avoid monomorphization bloat in Visitor::visit_{i128,u128}()#3064
max-heller wants to merge 1 commit into
serde-rs:masterfrom
max-heller:avoid-monomorphization-bloat

Conversation

@max-heller
Copy link
Copy Markdown

Instantiate 128-bit integer error reporting code once per de::Error type instead of once per de::Visitor instantiation.

Context

I work on a (unfortunately closed source) project with a lot of large, deeply nested types deriving Deserialize for which compilation of the monomorphized Deserialize impls alone takes multiple minutes using erased-serde. I've been looking into which methods in particular are blowing up compile times and binary sizes, and Visitor::visit_{i128,u128}() popped up as an easy first step: the default implementations aren't huge (construct and return an error), but with ~7500 copies they add up.

Testing

Created a sample application deserializing a complex type (pandoc_ast::Pandoc, which has previously been used as a test case (serde-rs/json#313)). This example also uses erased_serde, which has a lot of other monomorphization bloat that I'm looking into as well (though it seems harder to avoid).

// src/main.rs
fn main() {
    _ = erased_serde::deserialize::<pandoc_ast::Pandoc>(
        &mut <dyn erased_serde::Deserializer>::erase(
            &mut serde_json::Deserializer::from_slice(&[]),
        ),
    );
}
# Cargo.toml
[package]
name = "playground"
version = "0.1.0"
edition = "2024"

[dependencies]
erased-serde = "0.4.10"
pandoc_ast = "0.8.6"
serde_json = "1.0.149"

--release before/after (7.5% decrease in llvm-lines)

cargo llvm-lines --bin playground --release | head -n20
   Compiling playground v0.1.0 (/Users/max/Documents/playground)
    Finished [`release` profile [optimized]](https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles) target(s) in 2.56s
  Lines                 Copies              Function name
  -----                 ------              -------------
  284824                7055                (TOTAL)
   12566 (4.4%,  4.4%)    80 (1.1%,  1.1%)  serde_core::de::Visitor::visit_i128
   12566 (4.4%,  8.8%)    80 (1.1%,  2.3%)  serde_core::de::Visitor::visit_u128
    5967 (2.1%, 10.9%)    81 (1.1%,  3.4%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_byte_buf
    5967 (2.1%, 13.0%)    81 (1.1%,  4.6%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_string
    5945 (2.1%, 15.1%)    52 (0.7%,  5.3%)  serde::private::de::content::visit_content_seq
    4682 (1.6%, 16.7%)    71 (1.0%,  6.3%)  erased_serde::any::Any::take
    4445 (1.6%, 18.3%)    71 (1.0%,  7.3%)  erased_serde::any::Any::new
    3843 (1.3%, 19.7%)    27 (0.4%,  7.7%)  <serde_core::de::impls::<impl serde_core::de::Deserialize for alloc::vec::Vec<T>>::deserialize::VecVisitor<T> as serde_core::de::Visitor>::visit_seq
    3764 (1.3%, 21.0%)    50 (0.7%,  8.4%)  <serde::private::de::content::SeqDeserializer<E> as serde_core::de::SeqAccess>::next_element_seed
    3607 (1.3%, 22.2%)    54 (0.8%,  9.2%)  <&mut dyn erased_serde::de::SeqAccess as serde_core::de::SeqAccess>::next_element_seed
    3537 (1.2%, 23.5%)    81 (1.1%, 10.3%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_bool
    3537 (1.2%, 24.7%)    81 (1.1%, 11.5%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_borrowed_bytes
    3537 (1.2%, 26.0%)    81 (1.1%, 12.6%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_borrowed_str
    3537 (1.2%, 27.2%)    81 (1.1%, 13.8%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_bytes
    3537 (1.2%, 28.5%)    81 (1.1%, 14.9%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_char
    3537 (1.2%, 29.7%)    81 (1.1%, 16.1%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_enum
    3537 (1.2%, 30.9%)    81 (1.1%, 17.2%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_f32
cargo llvm-lines --bin playground --release | head -n20
   Compiling playground v0.1.0 (/Users/max/Documents/playground)
    Finished [`release` profile [optimized]](https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles) target(s) in 2.75s
  Lines                 Copies              Function name
  -----                 ------              -------------
  263176                7057                (TOTAL)
    5967 (2.3%,  2.3%)    81 (1.1%,  1.1%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_byte_buf
    5967 (2.3%,  4.5%)    81 (1.1%,  2.3%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_string
    5945 (2.3%,  6.8%)    52 (0.7%,  3.0%)  serde::private::de::content::visit_content_seq
    4682 (1.8%,  8.6%)    71 (1.0%,  4.0%)  erased_serde::any::Any::take
    4445 (1.7%, 10.3%)    71 (1.0%,  5.0%)  erased_serde::any::Any::new
    3843 (1.5%, 11.7%)    27 (0.4%,  5.4%)  <serde_core::de::impls::<impl serde_core::de::Deserialize for alloc::vec::Vec<T>>::deserialize::VecVisitor<T> as serde_core::de::Visitor>::visit_seq
    3764 (1.4%, 13.2%)    50 (0.7%,  6.1%)  <serde::private::de::content::SeqDeserializer<E> as serde_core::de::SeqAccess>::next_element_seed
    3607 (1.4%, 14.5%)    54 (0.8%,  6.9%)  <&mut dyn erased_serde::de::SeqAccess as serde_core::de::SeqAccess>::next_element_seed
    3537 (1.3%, 15.9%)    81 (1.1%,  8.0%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_bool
    3537 (1.3%, 17.2%)    81 (1.1%,  9.2%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_borrowed_bytes
    3537 (1.3%, 18.6%)    81 (1.1%, 10.3%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_borrowed_str
    3537 (1.3%, 19.9%)    81 (1.1%, 11.5%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_bytes
    3537 (1.3%, 21.2%)    81 (1.1%, 12.6%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_char
    3537 (1.3%, 22.6%)    81 (1.1%, 13.8%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_enum
    3537 (1.3%, 23.9%)    81 (1.1%, 14.9%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_f32
    3537 (1.3%, 25.3%)    81 (1.1%, 16.1%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_f64
    3537 (1.3%, 26.6%)    81 (1.1%, 17.2%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_i128

Unoptimized build before/after (2% decrease in llvm-lines)

cargo llvm-lines --bin playground | head -n20
   Compiling serde_core v1.0.228
   Compiling serde_json v1.0.149
   Compiling serde v1.0.228
   Compiling erased-serde v0.4.10
   Compiling pandoc_ast v0.8.6
   Compiling playground v0.1.0 (/Users/max/Documents/playground)
    Finished [`dev` profile [unoptimized + debuginfo]](https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles) target(s) in 2.58s
  Lines                 Copies              Function name
  -----                 ------              -------------
  190379                9049                (TOTAL)
    6681 (3.5%,  3.5%)    71 (0.8%,  0.8%)  erased_serde::any::Any::take
    5416 (2.8%,  6.4%)    52 (0.6%,  1.4%)  serde::private::de::content::visit_content_seq
    5256 (2.8%,  9.1%)    71 (0.8%,  2.1%)  erased_serde::any::Any::new
    4809 (2.5%, 11.6%)    50 (0.6%,  2.7%)  <serde::private::de::content::SeqDeserializer<E> as serde_core::de::SeqAccess>::next_element_seed
    3762 (2.0%, 13.6%)    80 (0.9%,  3.6%)  serde_core::de::Visitor::visit_i128
    3762 (2.0%, 15.6%)    80 (0.9%,  4.5%)  serde_core::de::Visitor::visit_u128
    3445 (1.8%, 17.4%)   133 (1.5%,  5.9%)  <core::result::Result<T,E> as erased_serde::map::ResultExt<T,E>>::unsafe_map
    3398 (1.8%, 19.2%)   140 (1.5%,  7.5%)  core::result::Result<T,E>::map
    3180 (1.7%, 20.9%)   228 (2.5%, 10.0%)  core::option::Option<T>::unwrap
    2832 (1.5%, 22.3%)    12 (0.1%, 10.1%)  <serde::private::de::content::TaggedContentVisitor<T> as serde_core::de::Visitor>::visit_map
    2771 (1.5%, 23.8%)    81 (0.9%, 11.0%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_byte_buf
    2771 (1.5%, 25.3%)    81 (0.9%, 11.9%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_string
    2711 (1.4%, 26.7%)    79 (0.9%, 12.8%)  serde_core::de::Visitor::visit_byte_buf
    2711 (1.4%, 28.1%)    79 (0.9%, 13.7%)  serde_core::de::Visitor::visit_string
    2698 (1.4%, 29.5%)    38 (0.4%, 14.1%)  <serde::private::de::content::ContentDeserializer<E> as serde_core::de::Deserializer>::deserialize_seq
    2665 (1.4%, 30.9%)    80 (0.9%, 15.0%)  serde_core::de::Visitor::visit_char
    2590 (1.4%, 32.3%)    70 (0.8%, 15.7%)  <alloc::boxed::Box<T,A> as core::ops::drop::Drop>::drop
cargo llvm-lines --bin playground | head -n20
   Compiling playground v0.1.0 (/Users/max/Documents/playground)
    Finished [`dev` profile [unoptimized + debuginfo]](https://doc.rust-lang.org/cargo/reference/profiles.html#default-profiles) target(s) in 1.18s
  Lines                 Copies              Function name
  -----                 ------              -------------
  186302                9043                (TOTAL)
    6681 (3.6%,  3.6%)    71 (0.8%,  0.8%)  erased_serde::any::Any::take
    5416 (2.9%,  6.5%)    52 (0.6%,  1.4%)  serde::private::de::content::visit_content_seq
    5256 (2.8%,  9.3%)    71 (0.8%,  2.1%)  erased_serde::any::Any::new
    4809 (2.6%, 11.9%)    50 (0.6%,  2.7%)  <serde::private::de::content::SeqDeserializer<E> as serde_core::de::SeqAccess>::next_element_seed
    3445 (1.8%, 13.7%)   133 (1.5%,  4.2%)  <core::result::Result<T,E> as erased_serde::map::ResultExt<T,E>>::unsafe_map
    3398 (1.8%, 15.6%)   140 (1.5%,  5.7%)  core::result::Result<T,E>::map
    3180 (1.7%, 17.3%)   228 (2.5%,  8.2%)  core::option::Option<T>::unwrap
    2832 (1.5%, 18.8%)    12 (0.1%,  8.4%)  <serde::private::de::content::TaggedContentVisitor<T> as serde_core::de::Visitor>::visit_map
    2771 (1.5%, 20.3%)    81 (0.9%,  9.3%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_byte_buf
    2771 (1.5%, 21.8%)    81 (0.9%, 10.2%)  <erased_serde::de::erase::Visitor<T> as erased_serde::de::Visitor>::erased_visit_string
    2711 (1.5%, 23.2%)    79 (0.9%, 11.0%)  serde_core::de::Visitor::visit_byte_buf
    2711 (1.5%, 24.7%)    79 (0.9%, 11.9%)  serde_core::de::Visitor::visit_string
    2698 (1.4%, 26.1%)    38 (0.4%, 12.3%)  <serde::private::de::content::ContentDeserializer<E> as serde_core::de::Deserializer>::deserialize_seq
    2665 (1.4%, 27.6%)    80 (0.9%, 13.2%)  serde_core::de::Visitor::visit_char
    2590 (1.4%, 28.9%)    70 (0.8%, 14.0%)  <alloc::boxed::Box<T,A> as core::ops::drop::Drop>::drop
    2456 (1.3%, 30.3%)     9 (0.1%, 14.1%)  <serde::private::de::content::ContentDeserializer<E> as serde_core::de::Deserializer>::deserialize_any
    2413 (1.3%, 31.6%)    85 (0.9%, 15.0%)  serde_core::de::Visitor::visit_newtype_struct

Instantiate 128-bit integer error reporting code once per de::Error type
instead of once per de::Visitor instantiation.
@Mingun
Copy link
Copy Markdown
Contributor

Mingun commented Apr 29, 2026

The same thing, but better, implemented in #2781 in 8db73ad.

@max-heller
Copy link
Copy Markdown
Author

The same thing, but better, implemented in #2781 in 8db73ad.

Regardless of the exact shape (new Unexpected public functions or not), it'd be nice to get in a surgical change like this or 8db73ad without blocking on the rest of that PR or that PR's dependencies.

@Mingun
Copy link
Copy Markdown
Contributor

Mingun commented Apr 29, 2026

Yes, I just suggest to cherry-pick my commit if needed.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants