rust-lang / rust-lang/rust-clippy
Ling suggestion: use a range to cycle between values, instead arithmetic remainder.
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
What it does
To cycle between 0 and a limit, we usually use an expression like this:
let next_index = (current_index + 1) % ring.len();
The line is easy to understand and works fine. However, the generated code (as shown below) is pretty bad.
An alternative is to use a range:
let next_index = (current_index + 1..ring.len()).next().unwrap_or(0);
The generated code is much smaller and faster, but the source a bit larger and less obvious.
Generated Code
To see why this optimization is important I wrote two functions using both approaches:
pub fn fast_next(current: usize, max: usize) -> usize {
(current + 1..max).next().unwrap_or(0)
}
pub fn slow_next(current: usize, max: usize) -> usize {
(current + 1) % max
}
Then:
$ rustc --crate-type lib -g -O nums.rs
$ objdump -d libnums.rlib | c++filt
The code for fast_next:
add $0x1,%rdi
xor %eax,%eax
cmp %rsi,%rdi
cmovb %rdi,%rax
retq
The code for slow_next:
test %rsi,%rsi
je 2a <nums::slow_next+0x2a>
add $0x1,%rdi
mov %rdi,%rax
or %rsi,%rax
shr $0x20,%rax
je 21 <nums::slow_next+0x21>
mov %rdi,%rax
xor %edx,%edx
div %rsi
mov %rdx,%rax
retq
mov %edi,%eax
xor %edx,%edx
div %esi
mov %edx,%eax
retq
push %rax
lea 0x0(%rip),%rdi # 32 <nums::slow_next+0x32>
lea 0x0(%rip),%rdx # 39 <nums::slow_next+0x39>
mov $0x39,%esi
callq *0x0(%rip) # 44 <nums::slow_next+0x44>
ud2
Also in https://rust.godbolt.org/z/jb1EPW4Gn.
Categories
- Kind: I think Pedantic, but maybe Perf.
What is the advantage of the recommended code over the original code
- Faster and smaller machine code.
Drawbacks
This alternative is invalid in some cases:
- The divisor is zero or negative.
- The value is incremented with a step different to
1(like(a + 2) % b).
The generated code is worse is the divisor is constant and a power of 2 (like (a + 1) % 16).
Example
(a + 1) % b
Could be written as:
(a + 1..b).next().unwrap_or(0)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No implementation files or tests are named. Start by reviewing how rust-clippy defines and tests lints, then evaluate the proposed transformation against the listed invalid cases and confirm that the generated-code benefit justifies a new lint.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100