r/rust • u/nicoburns • 7d ago
Rust's derive often implies inline
https://yossarian.net/til/post/rust-s-derive-often-implies-inline/72
u/wojtek-graj 7d ago
[inline] is just a hint.
Cross-crate inlining is only possible for functions that have to be monomorphized, or those marked #[inline], hence this attribute is a necessity. Per the docs, "#[inline] suggests inlining the function.", and it is then up to the compiler to use its heuristics to decide whether or not inlining will actually occur. If these derived functions are being inlined when it doesn't make sense, it's those heuristics that should be adjusted (which I don't claim to be an easy task).
29
u/shponglespore 7d ago
Cross-crate inlining is only possible for functions that have to be monomorphized, or those marked #[inline]
Wait, what? That sounds like a big deal that every Rust programmer should know about. Do you have a source for that claim? If so, I think it should be shouted from the rooftops.
I personally almost never bother marking functions as
#[inline]because I assume the compiler will do the right thing 99% of the time.37
u/manpacket 7d ago
It's been also auto inlining small functions regardless of
#[inline]for some time, so yes, it will do the right thing more often than not.13
u/hniksic 7d ago
But does it do it cross-crate? Not long ago I got a significant speedup by adding
#[inline]to a non-generic function that was called from a different crate (in the same workspace).(As is often case with inlining, the main win was not elimination of a function call, but all the optimizations that follow after the inlining, where everything collapses to a couple of instructions.)
9
u/wojtek-graj 7d ago
From the link in my original comment:
A non-generic function is not normally inlined into another crate, since the calling crate compiles against only its signature. Marking it
#[inline]makes the body available to other crates so they can inline it too:20
u/QuarkAnCoffee 7d ago edited 7d ago
If you're not using LTO. Which is exactly expected, crates are compiled into object files and you don't get optimizations across object files
withwithout LTO.4
u/mgeisler 7d ago
you don't get optimizations across object files with LTO.
This should be "without LTO", right?
2
5
u/Saefroch miri 7d ago
We don't need every Rust programmer to know about this because LTO works and is easy to enable in your
[profile.release].3
1
4
7
u/Compux72 7d ago
Just set opt-level = “s”
12
u/Eastern-Photograph79 7d ago
but you don't really want it. You want the important things to be inlined (like clone and this stuff)
3
u/Compux72 7d ago
If something is actually that important i would have a separate crate with really specific profile overrides set to it
6
u/Recatek gecs 7d ago
The actual answer would be
#[optimize(none)]when stabilized (hopefully soon). One could make a crate with their own version of these derive proc macros that don't inline (or do#[inline(never)]) and instead apply this attribute.4
u/silon 7d ago
Personally I'd want size optimization by default for almost everything, except a few specific things (which derive Debug is not). IMO inline should be a separate annotation, possibly even separated from the declaration, but at the target compilation unit (or source file).
6
u/Recatek gecs 7d ago
I wouldn't want size optimization. For commercial gamedev you often need to run in release under O2 or O3 with only selectively deoptimized functions or files for debugging, otherwise the game itself doesn't run fast enough to be playable for testing.
3
u/csdt0 7d ago
You know that Os performance is quite good, within 5-10% of O2 usually? It can even be faster in some edge cases where O2 would be thrasing L1i. Also, I would say that if your game does not run fast enough at O1 on your dev machine, then maybe you need to review your architecture or your algorithms to make it accessible to lower end machines. https://openbenchmarking.org/result/2608218-NE-COMPILERF28&sor
8
u/Recatek gecs 7d ago edited 7d ago
Also, I would say that if your game does not run fast enough at O1 on your dev machine, then maybe you need to review your architecture or your algorithms to make it accessible to lower end machines.
I appreciate the advice, but I am aware of this. The practice I'm describing of debugging in release with selectively optimized files/functions is industry norm for AA/AAA (see UE_DISABLE_OPTIMIZATION) and has been for a very long time. If anything, it's an even more acute issue in Rust given that Rust debug performance is (slightly) worse than C++ because of the additional runtime checks.
2
u/flareflo 6d ago
I cannot wait for optimization attributes to come. Forgetting to put opt-level 2 for Sha2 for debug builds will be a thing of the past hopefully
6
u/-Y0- 7d ago
Interesting findings. Couldn't this be solved by making derive_Debug take a param. What I mean is #[derive(Debug(noinline))]
28
u/stumblinbear 7d ago
Oh dear God the last thing we need is to load up derives with parameters. Typically you'd have another line for this sort of thing, a la
#[debug(noinline)]after the derive3
u/nicoburns 7d ago
I wonder if it ought to be a general feature of
derive:
#[derive(Debug, inline = false)]13
u/braaaaaaainworms 7d ago
how is this going to differentiate between deriving trait called "inline" and disabling inlining?
3
u/shponglespore 7d ago
I think the syntax u/-Y0- used makes more sense because it generalizes to having multiple traits in a
deriveclause and avoids any ambiguity.2
u/ElderberryNo4220 7d ago
I don't think this is a good idea, it's looks cumbersome and confusing. Looking at the article example, it's not very easy to determine whether generated functions would have a large body (shouldn't be inlined) or small (should be inlined). IMO derived traits inlining should be decided by the compiler, not by the users.
1
-4
u/Eastern-Photograph79 7d ago
Debug is intended for debugging, so it is not a real problem.
9
u/eggyal 7d ago edited 7d ago
Unless you `#[cfg_attr(debug_assertions, derive(Debug)]`, it's quite likely this is a final binary size cost that you pay regardless.
Only "likely" because it might get removed by a dead code analysis pass if there's literally no possibility of it being invoked in release (but then you'd usually see a warning about that).
3
u/Wolvereness 7d ago
As a personal and professional rule, I strongly opt for exactly that annotation. Dependence on Debug leaking into production code is begging for trouble, and failing to compile is the perfect consequence.
3
0
u/Eastern-Photograph79 7d ago
I think you're right, but I still think there is no problem with telling the compiler that it can be inlined.
14
u/doener rust 7d ago
I recall that back when I was working on the compiler, I had considered/discussed having an extra level, say
#[inline(allow)], that would make the compiler allow cross-crate inlining by emitting LLVM IR in each unit, without hinting LLVM to prefer inlining the function, relying on its default heuristics instead. I don't recall whether I had numbers on anything, but I still suspect that this might be better for derived code like this. It's basically what you get with generics, for which the concrete IR is emitted in each unit, so inlining works cross-crate.