-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
new lint to detect inefficient iter().any()
#13817
base: master
Are you sure you want to change the base?
Conversation
r? @Manishearth rustbot has assigned @Manishearth. Use |
acb27cb
to
8eb9d35
Compare
On second thought, I thought it would be more appropriate to place the lint under the |
8eb9d35
to
14694b1
Compare
14694b1
to
c52ebf2
Compare
149e701
to
c1d5108
Compare
clippy_lints/src/slice_iter_any.rs
Outdated
/// Checks for usage of `iter().any()` on slices of `u8` or `i8` and suggests using `contains()` instead. | ||
/// | ||
/// ### Why is this bad? | ||
/// `iter().any()` on slices of `u8` or `i8` is optimized to use `memchr`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you mean .contains()
? Anyway, I think this is too specific, on my machine it seems to use compiler intrinsics and make no calls to memchr
.
You're right about performances though: I benchmarked both .iter().any(==)
and .contains()
and the latter runs more than 10 times faster on large areas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I had assumed from the implementation that memchr
would be used in any environment (including my local environment). Also, this godbolt does so as far as the assembly is concerned: https://rust.godbolt.org/z/Kxrzr81Me
However, if it may differ depending on the environment, the description should be indeed modified, so I'll make a change. Could you please tell me for reference, what's the environment you have checked that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the memchr
itself has been replaced by an intrinsic. I'm using the latest nightly compiler on x86_64.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! In any case, it looks like this lint description should be modified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the memchr()
exists for larger integer types too, yes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, nope, it doesn't. It could be extended, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that contains()
is now faster for certain types (u16, u32, u64, i16, i32, i64, f32, f64, usize, isize
) compared to before (see: rust-lang/rust#130991). Therefore, this performance lint should be extended to cover these types starting from Rust 1.84.0 and I'll make a change for this.
tests/ui/slice_iter_any.rs
Outdated
let vec: Vec<u32> = vec![1, 2, 3, 4, 5, 6]; | ||
let values = &vec[..]; | ||
let _ = values.iter().any(|&v| v == 4); | ||
// no error, because it's not a slice of u8/i8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be more readable to use .contains()
here too? I thought that was what @Manishearth was suggesting.
tests/ui/slice_iter_any.rs
Outdated
|
||
let values: [u8; 6] = [3, 14, 15, 92, 6, 5]; | ||
let _ = values.iter().any(|&v| v == 10); | ||
// no error, because it's an array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be more efficient to lint there, even though this is an array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The optimization doesn't seem to work for arrays. Because of the implementation I mentioned in this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But isn't it clearer anyway to use .contains()
rather than .any(==)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are suggesting that clippy should modify this code style, you may be right. I'll try to make changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, after thinking about it, what this lint should do is to suggest performance improvements for u8
and i8
slices, and it seems appropriate to make this as a separate lint. What do you think? If this is a good idea, I'll implement this as a new lint in another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't a lint which is more efficient for some types (u8/i8) and not less efficient for some others deserve to be in the performance category? And by the way, I see the same 10+ performance boost in u32
as well.
It looks like a second lint would also cover this one, I'm not sure two lints are needed. I'll let others weigh in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the same 10+ performance boost in u32 as well
I didn't check about it. Thank you very much. If so, it seems more reasonable to combine them into one as a single lint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case you don't have one, here is the one I used, for testing func1
and func2
, the two versions I wanted to compare.
fe5f2c4
to
041f76b
Compare
2b9d742
to
b3837dc
Compare
contains()
instead of iter().any()
for u8
and i8
slicesiter().any()
clippy_lints/src/methods/iter_any.rs
Outdated
ty::Ref(_, inner_type, _) if inner_type.is_slice() => { | ||
// check if the receiver is a u8/i8 slice | ||
if let ty::Slice(slice_type) = inner_type.kind() | ||
&& (slice_type.to_string() == "u8" || slice_type.to_string() == "i8") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my environment, I could only see the speedup for the u8
and i8
slices (about 5~7x) while @samueltardieu says he has been able to confirm this with other types of slices, so the changes in this PR are only for these types.
At least the speedups for the u8
and i8
slices are correct for reasons that come from the Rust implementation, I think.
b3837dc
to
a956f34
Compare
a956f34
to
19357ca
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A thing I'm unsure about is the mixing of two lints like this. I'm going to ask on Zulip if we should be doing two lints here.
clippy_lints/src/methods/iter_any.rs
Outdated
&& let Some((name, recv, _, _, _)) = method_call(recv) | ||
&& name == "iter" | ||
{ | ||
let ref_type = cx.typeck_results().expr_ty(recv); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: Probably should be expr_ty_adjusted
to handle autoderef. Add a test that ensures this works on a vector vec.iter().any(...)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I changed to use expr_ty_adjusted
and added a test for this in e964cbc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Manishearth TBH, I added style lint (unnecessary_iter_any
) through the review process, which I consider unnecessary at least for this change.
Certainly there will be some cases where replacing iter().any()
with contains()
will be more readable, but I think it could also lead to false positives. Not only that, I found that adding the unnecessary_iter_any
lint required a lot of modifications in the existing code base of Clippy.
Therefore, I think it is appropriate to limit this change to adding slice_iter_any
lint for slices of numeric slices. What do you think?
edit: What I meant is e964cbc. If the changes that follow this cource are acceptable, I'll squash the previous commits as appropriate.
f67d408
to
d54475b
Compare
d54475b
to
e964cbc
Compare
iter().any()
iter().any()
fn can_replace_with_contains(op: Spanned<BinOpKind>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool { | ||
matches!( | ||
(op.node, &lhs.kind, &rhs.kind), | ||
( | ||
BinOpKind::Eq, | ||
ExprKind::Path(_) | ExprKind::Unary(_, _), | ||
ExprKind::Lit(_) | ExprKind::Path(_) | ||
) | ( | ||
BinOpKind::Eq, | ||
ExprKind::Lit(_), | ||
ExprKind::Path(_) | ExprKind::Unary(_, _) | ||
) | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, the following code uses ==
inside closures, but cannot simply be replaced by contains()
:
let _ = values.iter().any(|&v| v % 2 == 0);
Therefore, this function exclude such cases.
I think that we can add a single numeric-only lint to the nursery but we should have the clippy team figure out whether we wish to
before the numeric lint is moved out of the nursery. Currently 2 and 3 are options that would be made harder by adding a numeric-only lint outside of the nursery. Clippy is allowed to expand lints after release but when it does so it can be annoying to people so we try to limit that. |
fix #13353
Using
contains()
for numeric slices are more efficient than usingiter().any()
.changelog: [
slice_iter_any
]: new lint