-
Notifications
You must be signed in to change notification settings - Fork 39
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
Build Speed proposal: Cut out the serde dependency when communicating with steno? #4484
Comments
Interesting. What would we lose by doing this? |
I think possibly the steno library's visibility into the underlying types. If steno consumed a byte-array interface, it might be more difficult for Steno itself to be responsible for logging "what data was passed", but that would give callers the ability to use a lighter-weight serialization framework. This is admittedly all predicated on the theory that:
I do think both of those points should be validated before investing a ton of effort here, but this does align with the recommendations from https://matklad.github.io/2021/09/04/fast-rust-builds.html#Keeping-Instantiations-In-Check :
|
I think Steno is already agnostic to the underlying types, right? I think you could replace |
In short: yes, either by used fixed types, or by allowing clients to define their own mechanism of serialization / deserialization. For example, if we used My suspicion is that this incurs a cost in the long-tail of building the awslabs/aws-lambda-rust-runtime#481 (comment) I also strongly agree with the follow-up opinion posted there:
I agree that it's not as simple as "just using Serde" but I think we could define something pretty similar. For the most part, I don't think steno cares much about the values being passed as "inputs" and "outputs", beyond the following:
But this doesn't actually mean that the caller needs to provide a type that can necessarily be serialized to / from JSON! It could just be a type that can be serialized / deserialized from a "string", or a sequence of bytes, or whatever. I do think that Steno is already performing correct behavior -- I'm just trying to keep an eye out for spots where monomorphization (specifically in Nexus) could be biting us in terms of compile-time cost, this seems like it could be one of them. |
One more (similar) data-point that heavy usage of serde_derive can bite consumers from a compile-time point-of-view: serde-rs/serde#1146 (comment) |
My recollection is that the reason Steno looks this way is not because it's going to peek inside the structures you gave it, but so that Steno can take responsibility for the serialization and deserialization steps. That's in turn to eliminate boilerplate copied and pasted into every action / undo-action function, especially if that boilerplate might be easy to get wrong. It also ensures that when these steps fail, we get a uniform error, no matter which action/undo-action failed, and we can include as much context as makes sense with it. The hope was that that would make it easy to add metrics, scrape logs, etc. for these failures. So the thing I'm worried about here is just that by eliminating these type parameters, we move responsibility from one common hunk of code inside Steno to each individual action/undo action, and that people start writing this by hand and it winds up being (1) incorrect sometimes where it couldn't be today and (2) less debuggable, if people don't bother to annotate these errors with the same context that Steno does (or could). It could be we can achieve the best of both worlds -- e.g., maybe a macro similar to Dropshot's I guess it boils down to how big an effect this is. Do we have any idea? Or is the thrust of this issue "collect data to see if this is worth pursuing"? |
This is the proxy I've evaluated so far: https://gist.github.com/smklein/8fd491c4f80609473b664051c2e46c11 Based on https://github.com/dtolnay/cargo-llvm-lines#multicrate-projects From that data, I run the following: cat llvm-lines-lib-fat-lto.txt | awk '{$1=$1};1' | rg -v TOTAL | rg "$PATTERN" | cut -d' ' -f 1 | awk '{sum += $1} END {print "Sum: " sum}` To get a really rough proxy for "how much is a crate contributing to total lines of LLVM". This is still not an accurate assessment of "total compile time", but I don't really know of existing tools that perform that time-based analysis, rather than this space-based analysis. From that script, I saw the following (and be aware, these probably overlap -- they're not necessarily distinct buckets):
To be clear: Other than this communication with steno, the "db model" crates should not depend on serde -- it's not really trivial to separate "how many of the generated Serde-related lines of LLVM IR are related to the API, vs related to serializing DB structures en route to steno". So: I think the top priority to improve compile-times should be optimizing our usage of Diesel, but I think we're generating a similar total amount of LLVM IR from our usage of serde + dropshot. It does seem like the total amount of code generated from steno is relatively small, but it imposes the So while that's not nothing, it's also admittedly not huge. I suppose we'd be better off focusing more on Diesel and Dropshot for the moment. |
Totally, and I think that these are reasonable goals.
I don't think removing the dependency on https://blog.logrocket.com/rust-serialization-whats-ready-for-production-today/ has an overview of a bunch -- I think they aptly categorize serde as "elegant, flexible, fast to run, and slow to compile". But in our usage, we don't actually need flexible, because we're only really serializing to one format within steno. |
I think I've misunderstood then. I thought the problem here was all the type parameters causing extra monomorphization. If that were the case, I think you'd have to remove them, and that would mean clients doing the serialization from their custom types to some non-parametrized type (like I also can't tell to what degree llvm lines corresponds with compile time. I wonder if a cheap next step would be to fork steno as needed and write two little steno consumers: one against the current steno and one against the new one. We could load them up with different sagas and actions (maybe a comparable number to Nexus) that just do nothing. It seems like the difference in compile times there ought to be an upper bound on what we could see from doing this in Nexus. |
Here's my idea -- I'd like to validate this with additional data, and I'll see if I can sketch out some numbers for potential improvement.
A lot of the structs in
nexus/db-model
deriveserde
'sSerialize
andDeserialize
traits, in addition to their family of Diesel traits.These traits are needed in two spots:
serde_json::Value
Serialize
andDeserialize
to be implemented.This "makes sense", in the sense that Steno needs to serialize and deserialize this data. There is a minor quirk here, however -- Steno doesn't really need to parse this information, and forcing these Serialization/Deserialization derives onto all structs that could be stored / retrieved.
For example: if Steno acted on a "byte array" as input / output, we could cut down on a fair bit of monomorphized code (and therefore compile-time), and might be able to use a lighter serialization framework, like https://github.com/dtolnay/miniserde
The text was updated successfully, but these errors were encountered: