Skip to content
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

Add lint to improve floating-point expressions #4897

Merged

Conversation

krishna-veerareddy
Copy link
Contributor

@krishna-veerareddy krishna-veerareddy commented Dec 12, 2019

Looks for floating-point expressions that can be expressed using built-in methods to improve accuracy, performance and/or succinctness.

changelog: Add lint floating_point_improvements.

Fixes #4726
Partly addresses #2040

Currently linted expressions:

Expression Suggestion
x.log(2.0) x.log2()
x.log(10.0) x.log10()
x.log(std::f32::consts::E) x.ln()
(1 + x).ln() x.ln_1p()
(2.0).powf(x) x.exp2()
(std::f32::consts::E).powf(x) x.exp()
x.powf(1/2) x.sqrt()
x.powf(1/3) x.cbrt()
x.powf(y), where y is whole x.powi(y)
x.exp() - 1 x.exp_m1()
x * y + z x.mul_add(y, z)

@krishna-veerareddy
Copy link
Contributor Author

@clarfon Hey I wanted to get the ball rolling on this issue but I have a few questions regarding how we would separate the lints. For example, should I create separate lints for log(2|10|e) and log_1p functions or should I group all the log functions under a single lint or should I group all the suggestions under a single lint?

The direction I was going in was creating separate lints for each of your suggestions but I am not sure if that's the right approach.

@clarfonthey
Copy link
Contributor

clarfonthey commented Dec 12, 2019

Wouldn't this be the same as one of the known constant lints? Don't see why this would be lumped under a different lint.

We could maybe even lint common trig function calls which return values of already provided constants, e.g. sin(PI) == 0. But maybe calling it a "computed known constant" rather than a literal without a name is better, and splitting that from the approx literal lint.

Ignore me. I can't read.

@krishna-veerareddy
Copy link
Contributor Author

krishna-veerareddy commented Dec 12, 2019

No, this would lint calls such as (1f32).log(2.0) and suggest usage of (1f32).log2() instead. It does the same for bases 10 and e. known_log_bases was probably a poor choice for the lint name. Specifically this PR fixes these three items in your issue under a single lint:

x.logbase(E) => x.log()
x.logbase(10) => x.log10()
x.logbase(2) => x.log2()

@clarfonthey
Copy link
Contributor

clarfonthey commented Dec 12, 2019

Oh! I did that thing I absolutely shouldn't do which is to assume something without reading the whole thing. -___-

Honestly, I feel like one lint should cover all cases for things like this. Things that have explicit specialised versions should go in one lint. (With the exception of things like powf(1/3) -> cbrt, which aren't the same but the latter is probably intended.)

Things which don't have explicit functions but are more accurate should go in a separate lint, e.g. Herbie's home page example of sqrt(x+1) - sqrt(x) being better off as 1/(sqrt(x+1) + sqrt(x)).

@clarfonthey
Copy link
Contributor

Another potential thing to consider is whether you're optimising for speed or accuracy. I'd maybe separate lints based upon that, e.g. hypot is always more accurate but may be slower.

@krishna-veerareddy krishna-veerareddy changed the title [WIP] Add known_log_bases lint Add lints to detect inaccurate and inefficient FP operations Dec 14, 2019
@krishna-veerareddy
Copy link
Contributor Author

@clarfon Hey I created two lints: one for better accuracy and another for performance. The expression currently linted are:

x.log(2) => x.log2() // for better accuracy
x.log(10) => x.log10() // for better accuracy
x.log(E) => x.ln() // for better accuracy
(1 + x).ln() => x.ln_1p() // for better accuracy
(2.0).powf(x) => x.exp2() // for better performance
E.powf(x) => x.exp() // for better performance
x.powf(1 / 2) => x.sqrt() // for better performance
x.powf(1 / 3) => x.cbrt() // for better performance
x.exp() - 1 => x.exp_m1() // for better accuracy

I will add lints for some of the other suggestions in your issue in a separate PR. Would love to know your thoughts/suggestions.

@clarfonthey
Copy link
Contributor

Honestly, it may be worth a "always better" lint for some. For example, the log lints are pretty much the same or better for performance and better for accuracy. Ditto for the log plus one and exp minus one lints. However, changing to hypot or fused multiply-add might reduce performance, and thus is better for accuracy only.

@krishna-veerareddy
Copy link
Contributor Author

Yeah makes sense. Any suggestion for the lint name?

@llogiq
Copy link
Contributor

llogiq commented Dec 14, 2019

How about floating_point_perf and floating_point_accuracy?

Thinking about it more, we likely want the best perf and accuracy, but some people will accept perf loss for more accuracy while others will accept suboptimal accuracy for better perf.

So we can either make both lints and for suggestions that don't exacerbate perf or accuracy for the respective other we lint the more likely lint unless it's disabled. Or we make a floating_point_improvements lint that will improve both perf without accuracy loss and accuracy without perf loss.

@krishna-veerareddy
Copy link
Contributor Author

@llogiq Any suggestion for lints that are better for both performance and accuracy? Should I use floating_point_perf_and_accuracy or is that too verbose.

@krishna-veerareddy
Copy link
Contributor Author

Having a floating_point_improvements lint for better accuracy and performance sounds good to me. Should this lint also cover cases where there is no clear accuracy/performance improvement but makes the code more idiomatic or should that be a separate lint? Here are a couple examples:

x * PI / 180 => x.to_radians()
x * 180 / PI => x.to_degrees()
x.logN() / y.logN() => x.logbase(y)

@llogiq
Copy link
Contributor

llogiq commented Dec 15, 2019

I'd put everything that sacrifices accuracy under floating_point_performance, everything that sacrifices performance under floating_point_accuracy and everything else under floating_point_improvements.

@krishna-veerareddy krishna-veerareddy changed the title Add lints to detect inaccurate and inefficient FP operations Add lint to improve floating-point expressions Dec 15, 2019
@krishna-veerareddy
Copy link
Contributor Author

I didn't have to add the floating_point_accuracy and floating_point_performance lints yet but please let me know if any of the methods in this PR would sacrifice either performance or accuracy and Ill add that method under the relevant lint.

@krishna-veerareddy krishna-veerareddy force-pushed the issue-2040-accurate-float-functions branch 5 times, most recently from dca37f5 to 53ee2fb Compare December 21, 2019 02:15
@bors
Copy link
Contributor

bors commented Dec 22, 2019

☔ The latest upstream changes (presumably #4930) made this pull request unmergeable. Please resolve the merge conflicts.

@flip1995
Copy link
Member

I agree with the grouping of the lints, but I don't like the names. E.g., #[allow(floating_point_improvements)] is kind of misleading and against our lint naming convention (cc #2845).

My suggestions:

  • accuracy: inaccurate_floating_points
  • perf: bad_performing_floating_points
  • general: bad_floating_points

I'm not happy with the word bad though. But writing allow(lint_name) should describe (in an English "sentence") what is allowed there:

#[allow(clippy::inaccurate_floating_point)]
let y = x.log(2);

So it is obvious, that x.log(2) is inaccurate, but acceptable there, whereas allow(floating_point_accuracy) kind of suggests the opposite.

@krishna-veerareddy
Copy link
Contributor Author

Makes sense. How about:
general: {inferior|substandard|ill_advised|misguided}_floating_points
accuracy: inaccurate_floating_points
performance: {slow|inefficient}_floating_points

Also having a floating_point_{operation|computation|arithmetic|expression} suffix makes more sense to me than floating_points although it does make the lint name long.

@flip1995
Copy link
Member

I like inefficient_floating_points as a lint name for the perf group. For the general case I guess inferior is the best word here.

As for the suffix, I personally like operations best.

@clarfonthey
Copy link
Contributor

Could always just do flops for floating point operations

@krishna-veerareddy
Copy link
Contributor Author

krishna-veerareddy commented Dec 23, 2019

That is more concise but isn't flops supposed to be floating point operations per second? So maybe just flop? I am happy with flops too but don't want to cause any confusion.

The similar term FLOP is often used for floating-point operation, for example as a unit of counting floating-point operations carried out by an algorithm or computer hardware. - Wikipedia

@llogiq
Copy link
Contributor

llogiq commented Dec 25, 2019

Yes, {inefficient|inaccurate|substandard}_flop looks acceptable to me.

@flip1995 flip1995 added the S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) label Dec 28, 2019
@krishna-veerareddy
Copy link
Contributor Author

krishna-veerareddy commented Feb 23, 2020

@flip1995 Hey I changed the name of the lint to suboptimal_flops as we discussed above. The reason you don't see the other lints yet(inefficient_flops and imprecise_flops) were that all the checks above improve either accuracy or performance or both without loss of either.

But, I looked at the implementation of these library functions and some of these are internally calling the cmath library which could go under the imprecise_flops category as the cmath implementation sacrifices performance for accuracy. Here are the methods that use cmath internally: cbrt, ln_1p, exp_m1 and hypot(this one isn't linted yet). I can start making this change but would love to know your thoughts.

Also, I temporarily removed the lints to improve style as there were some bugs in implementation. Ill create a separate PR for those.

@flip1995
Copy link
Member

The reason you don't see the other lints yet [...] were that all the checks above improve either accuracy or performance or both without loss of either.

Ah ok, that's fair. As long as it is in nursery, it doesn't matter too much anyway.

I can start making this change but would love to know your thoughts.

I think this is part of the split up, we should do, when we "stabilize" the lint(s)

Also, I temporarily removed the lints to improve style as there were some bugs in implementation.

👍


I'd say with the NITs fixed, I noted above, we can merge this, and everything else can be done in a later PR(s) 👍

Add lint to detect floating point operations that can be computed more
accurately at the cost of performance. `cbrt`, `ln_1p` and `exp_m1`
library functions call their equivalent cmath implementations which is
slower but more accurate so moving checks for these under this new lint.
@krishna-veerareddy
Copy link
Contributor Author

@flip1995 Hey I created the imprecise_flops lint and moved some suggestions under that lint since I already had the changes. Also, fixed all the above comments. Please review when you have some time. Thanks!

@flip1995
Copy link
Member

@bors r+

Thanks!

@bors
Copy link
Contributor

bors commented Feb 24, 2020

📌 Commit ff0d44e has been approved by flip1995

@bors
Copy link
Contributor

bors commented Feb 24, 2020

⌛ Testing commit ff0d44e with merge 2734e4e...

@bors
Copy link
Contributor

bors commented Feb 24, 2020

☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test
Approved by: flip1995
Pushing 2734e4e to master...

@bors bors merged commit 2734e4e into rust-lang:master Feb 24, 2020
bors added a commit to rust-lang/rust that referenced this pull request Mar 2, 2020
Update cargo, clippy

Closes #69601

## cargo

16 commits in e57bd02999c9f40d52116e0beca7d1dccb0643de..bda50510d1daf6e9c53ad6ccf603da6e0fa8103f
2020-02-21 20:20:10 +0000 to 2020-03-02 18:05:34 +0000
- Fix rare failure in collision_export test. (rust-lang/cargo#7956)
- Ignore broken Cargo.toml in git sources (rust-lang/cargo#7947)
- Add more fingerprint mtime debug logging. (rust-lang/cargo#7952)
- Fix plugin tests for latest nightly. (rust-lang/cargo#7955)
- Simplified usage code of SipHasher (rust-lang/cargo#7945)
- Add a special case for git config discovery inside tests (rust-lang/cargo#7944)
- Fixes issue rust-lang/cargo#7543 (rust-lang/cargo#7946)
- Filter out cfgs which should not be used during build (rust-lang/cargo#7943)
- Provide extra context on a query failure. (rust-lang/cargo#7934)
- Try to clarify `cargo metadata`'s relationship with the workspace. (rust-lang/cargo#7927)
- Update libgit2 dependency (rust-lang/cargo#7939)
- Fix link in comment (rust-lang/cargo#7936)
- Enable `cargo doc --open` tests on macos. (rust-lang/cargo#7932)
- build-std: remove sysroot probe (rust-lang/cargo#7931)
- Try to clarify how feature flags work on the "current" package. (rust-lang/cargo#7928)
- Add extra details in the new feature resolver doc comment. (rust-lang/cargo#7918)

## clippy

6 commits in fc5d0cc..8b7f7e6
2020-02-24 05:58:17 +0000 to 2020-03-02 20:00:31 +0000
- Rustup to #69469 (rust-lang/rust-clippy#5254)
- Some rustups (rust-lang/rust-clippy#5247)
- Update git2 to 0.12 (rust-lang/rust-clippy#5232)
- Rustup to #61812 (rust-lang/rust-clippy#5231)
- Add lint to improve floating-point expressions (rust-lang/rust-clippy#4897)
- Do not run deploy action on other repos (rust-lang/rust-clippy#5222)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

manual_mul_add suggests broken code
5 participants