This short entry is a longer answer to stackoverflow.
The questions is:
Is there a (negative) performance impact due to unused function arguments (in Rust)?
Claim: There is no runtime performance impact. The compiler removes all unused function arguments (in release mode).
Free Functions
To this end, we will consider the following three functions:
Lets have a look at the generate assembly (playground_1):
Since this is a bit much to understand, release mode:
As promised, no difference between single_argument & too_many_arguments.
I guess (I’m not an assembly expert), that in debug mode too_many_arguments writes its argument y into a register.
Last check: We want to check there is some overhead at the call site.
Note that the following example forbids inlining and uses each function twice to ensure that the compiler does not replace function calls by their return values.
And the generated assembly (in release mode):
We observe that calls to enough_arguments are preceed by two movl (with the expected numbers), whereas both too_many_arguments & single_argument are only preceeded by a single movl.
Hence: In release mode, there is no runtime overhead associated with unused function arguments.
Sideremark: In debug mode we see the expected overhead:
Traits
The questions which motivated this post is slightly different.
Let’s say we have some trait:
and we have an implementor who is not using all arguments:
Question: Does this lead to run-time costs?
The expected answer is: Since LLVM does not know about traits, it basically sees only free function, hence there is no overhead.
But lets look at some more assembly.
Here is the example rust: (Again, no inlining and double function calls to avoid optimizations.)
and the relevant assembly:
Once again, no $111 or $113, so no overhead.
Can we see some overhead? Yes, like this:
and the relevant assembly:
But here we call into an unknown trait implementation, using dynamic dispatch.
So: What else should the compiler do? 1
Once again, we do not see any overhead. This is expected, since we still do static dispatch, so have a free function in LLVM (only the location of the function’s source code changed).
Summary
Let’s recall the definition of zero-cost abstraction due to Bjarne Stroustrup:
What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.
So we have checked:
Unused function arguments are a zero-cost abstraction in Rust.
Note that there is some overhead in debug mode.
Moreover, there is a linter warning about those parameters, which is really helpful.
Also, the tooling (i.e., rust playground or godbolt) is really nice to have. It easily allows to look at the generated assembly.
Additium
On reddit it was remarked that drop-types behave differently.
If a function argument is a drop-type (and not a reference to one), then the function must clean up the instance.
So, even if the argument seems to be unused, it actually is used (in order to call drop on it).
I guess it is fair to say that the compiler leaves out some optimization potential, but I’m unsure if this leads to real-world performance digressions.
Well, it could reason that there is only a unique implementation of DummyTrait. But this optimization seems unnecessary, since the whole point of dynamic dispatch is to support multiple implementations. ↩