Uploaded image for project: 'Rust Driver'
  1. Rust Driver
  2. RUST-1558

Reduce serde-induced compile time

    • Type: Icon: Improvement Improvement
    • Resolution: Unresolved
    • Priority: Icon: Unknown Unknown
    • None
    • Affects Version/s: None
    • Component/s: None

      Compiling the tests with either cargo build or cargo test takes a long time. In a simple experiment, an incremental change to a string literal in the integration.rs file takes roughly 17s on my machine. From some initial investigation, this appears to be due to the code generated from serde Deserialize and Serialize derived implementation.

      To arrive at this, I first used -Zself-profile on a cargo build that made the aforementioned incremental change. The results are attached in the "summary" file. Of note, the LLVM_module_codegen_emit_obj portion took up the vast majority of the time.

      As a next step, I used cargo-llvm-lines to see what was causing most of the LLVM lines to be emitted. Unfortunately, cargo-llvm-lines doesn't currently support being run against unit tests, so I created an integration test that is similar to the one found in integration.rs (https://github.com/patrickfreed/mongo-rust-driver/blob/compile-time-investigation/tests/compile_time.rs). After running cargo-llvm-lines on that, I was able to observe that most of the generated code lived in the raw BSON serializer and deserializer. Note that this test alone builds pretty fast, so this is possibly not representative of the issue at hand.

      Here are the top results:

        Lines           Copies        Function name
        -----           ------        -------------
        1408384 (100%)  29997 (100%)  (TOTAL)
          96864 (6.9%)     52 (0.2%)  bson::de::raw::Deserializer::deserialize_next
          56325 (4.0%)     24 (0.1%)  <bson::de::serde::BsonVisitor as serde::de::Visitor>::visit_map
          34128 (2.4%)     21 (0.1%)  <&mut bson::ser::raw::value_serializer::ValueSerializer as serde::ser::SerializeStruct>::serialize_field
          26746 (1.9%)     50 (0.2%)  bson::de::serde::Deserializer::deserialize_next
          25957 (1.8%)     12 (0.0%)  <bson::raw::serde::OwnedOrBorrowedRawBsonVisitor as serde::de::Visitor>::visit_map
          22430 (1.6%)     24 (0.1%)  <mongodb::operation::_::<impl serde::de::Deserialize for mongodb::operation::WriteResponseBody<T>>::deserialize::__Visitor<T> as serde::de::Visitor>::visit_map
          20646 (1.5%)    118 (0.4%)  bson::de::raw::Deserializer::access_document
          16402 (1.2%)     59 (0.2%)  bson::de::raw::Deserializer::deserialize_document
          15542 (1.1%)     24 (0.1%)  <mongodb::operation::_::<impl serde::de::Deserialize for mongodb::operation::CommandResponse<T>>::deserialize::__Visitor<T> as serde::de::Visitor>::visit_map
          12931 (0.9%)    327 (1.1%)  serde::de::Visitor::visit_map
          12699 (0.9%)    121 (0.4%)  bson::de::raw::DocumentAccess::read
          10951 (0.8%)     12 (0.0%)  <mongodb::error::_::<impl serde::de::Deserialize for mongodb::error::WriteConcernError>::deserialize::__Visitor as serde::de::Visitor>::visit_map
          10159 (0.7%)     12 (0.0%)  <mongodb::error::_::<impl serde::de::Deserialize for mongodb::error::BulkWriteError>::deserialize::__Visitor as serde::de::Visitor>::visit_map
           9607 (0.7%)     12 (0.0%)  <mongodb::operation::_::<impl serde::de::Deserialize for mongodb::operation::CursorInfo>::deserialize::__Visitor as serde::de::Visitor>::visit_map
           8609 (0.6%)    150 (0.5%)  core::result::Result<T,E>::map_err
           8198 (0.6%)     82 (0.3%)  bson::de::raw::CodeWithScopeDeserializer::read
           7936 (0.6%)     42 (0.1%)  <&mut bson::de::raw::BinaryDeserializer as serde::de::Deserializer>::deserialize_any
      

      Given that we have a ton of Deserialize/Serialize derived types and use them in basically every code path, it seems likely that serde is the culprit here. We're not alone in this, serde causing compile time bloat is a well known problem (e.g. rust_analyzer has the same issue: https://github.com/serde-rs/serde/issues/1146#issuecomment-907622649).

      There are some possible mitigations that we could explore here:

      • Manually implement Serialize/Deserialize for our custom types
        • It seems like the derived implementations generate a ton of code, so making sparse manual implementations could help here.
      • Incorporate changes into the serializer / deserializer themselves to cut back on the amount of monomorphization that is happening
      • Stop using serde for internal types
        • We would continue to rely on serde to deserialize user provided Ts, but we could skip on using it for known command shapes and error responses, for example.

      There may be other mitigations as well, or different causes that this investigation didn't reveal. I think this is probably a good starting point for trying something though.

      One thing that's important to note is that these compile time issues are not exclusive to our tests, they just so happen to be the one that us maintainers run up against the most. I think any app that uses the driver heavily will probably incur similar costs as well.

        1. llvm-lines-output
          1007 kB
        2. summary
          122 kB

            Assignee:
            Unassigned Unassigned
            Reporter:
            patrick.freed@mongodb.com Patrick Freed
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: