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

Implement likely/unlikely intrinsic (tracking issue for RFC 1131) #26179

Open
1 of 4 tasks
nikomatsakis opened this issue Jun 10, 2015 · 87 comments
Open
1 of 4 tasks

Implement likely/unlikely intrinsic (tracking issue for RFC 1131) #26179

nikomatsakis opened this issue Jun 10, 2015 · 87 comments
Labels
A-intrinsics Area: Intrinsics B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC E-help-wanted Call for participation: Help is requested to fix this issue. metabug Issues about issues themselves ("bugs about bugs") S-tracking-perma-unstable Status: The feature will stay unstable indefinitely. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jun 10, 2015

Tracking issue for rust-lang/rfcs#1131:

  • Implement the likely/unlikely intrinsic
  • Re-export in std::hint
  • Document
  • Stabilize
@nikomatsakis nikomatsakis added metabug Issues about issues themselves ("bugs about bugs") T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jun 10, 2015
@Aatch Aatch self-assigned this Jun 15, 2015
@pythonesque
Copy link
Contributor

FWIW, looking through my own code, I actually think the fact that this can't apply to match variants is going to be fairly hard on idiomatic Rust (for example, it means you can't use it when matching on an Option, which is going to be extremely common since it amounts to if (likely(ptr != NULL)) in C).

@Aatch
Copy link
Contributor

Aatch commented Jun 17, 2015

@pythonesque once I get this done, I plan on writing a follow-up RFC for a #[likely] attribute you can put on match arms. Since it's already possible to put attributes on arms (to support conditional compilation of them), it should be fairly easy to implement.

@Craig-Macomber
Copy link
Contributor

It might be worth having an "unpredictable" option in addition to likely and unlikely. There is now something to lower it to in LLVM: http://reviews.llvm.org/D12341

@apasel422
Copy link
Contributor

@Aatch Are you still working on this?

@vi
Copy link
Contributor

vi commented Mar 28, 2016

Shall #[likely] be also on Result?

enum Result<T, E> {
   #[likely]
   Ok(T),
   Err(E)
}

I expect for most code Ok is likely and Err is unlikely. Of course, annotating if or match should override enum variant's annotation.

@Aatch
Copy link
Contributor

Aatch commented Mar 29, 2016

@vi I think hinting should be only be used rarely, so it being on the type doesn't seem like a good idea. Result is a bit more general purpose than just error-handling, sot that kind of hinting would impact code where it's inappropriate. Hinting should only really be done at the site itself.

@vi
Copy link
Contributor

vi commented Mar 29, 2016

@Aatch, Is it a good idea to have likely in every place where something is likely? Can it also serve for self-documentation: follow "likelies" and see happy path?

@Aatch
Copy link
Contributor

Aatch commented Mar 30, 2016

@vi is is not. As a general rule, people are pretty bad at knowing what cases are actually likely or not. Branch hinting is a fairly specific optimisation hint, used incorrectly it can actually make code slower. It should also only really be used when the unlikely case is known to be very unlikely.

Basically, you should only use it if it makes your code faster, otherwise it should be there.

@pczarn
Copy link
Contributor

pczarn commented Mar 30, 2016

@vi It might be good idea if the likely thing is cheap, and the unlikely thing is expensive. For example, Vec::push is expensive if it causes a reallocation, and very cheap otherwise.

In my opinion, frequent use of likely is either unnecessary or just manual profile-guided optimization.

@Stebalien
Copy link
Contributor

Instead of arguing about it, why not just test it? Annotate Result, and then see if rustc bootstraps significantly faster. This won't give a definitive answer but will help determine if it's worth considering.

@comex
Copy link
Contributor

comex commented Aug 20, 2016

Now that MIR is a thing, it would be nice if this were implemented.

@nikomatsakis
Copy link
Contributor Author

@comex I do agree =)

@Aatch Not sure what your time is like, but if you don't have time to implement this, it seems like a case where we might be able to mentor someone. The idea would be to leave a comment explaining roughly how you think it should work, and then tagging the issue with E-mentor and E-help-wanted. I of course can do this too and will try to remember to do so. =)

@nikomatsakis
Copy link
Contributor Author

Unassigning @Aatch since, empirically, he doesn't seem to be actively hacking on this right now. =) (Feel free to correct me.)

@seanmonstar
Copy link
Contributor

@nikomatsakis PR up, though I could use some help adding tests.

@Aatch
Copy link
Contributor

Aatch commented Sep 2, 2016

Copying @nagisa's comment from #36181:

#llvm told me expect intrinsic is more harmful than it is helpful and that the branch weight metadata should be emitted instead. Might be worth to go back and reconsider the RFC, since the branch weight metadata is more powerful and expressive and would be better expressed as attributes in source too.

why is clang > 3.6 not emiting expect intrinsic for __builtin_expect?
nagisa: it turns out that the expect intrinsic overall harmed the optimizer by inhibiting certain optimizations
so we now directly lower __builtin_expect to branch probabilities in the frontend when generating IR

I think that keeping the intrinsics is a good idea. They should be fairly obvious for people coming from C/C++. Attributes aren't mutually exclusive with the intrinsics, so we're not exactly cutting ourselves off with intrinsics.

Supporting branch weights at the MIR level would probably be the easiest implementation for lowering to the metadata ourselves.

bors added a commit that referenced this issue Sep 13, 2016
core: add likely and unlikely intrinsics

I'm no good at reading assembly, but I have tried a stage1 compiler with this patch, and it does cause different asm output. Additionally, testing this compiler on my httparse crate with some `likely` usage added in to the branches does affect benchmarks. However, I'm sure a codegen test should be included, if anyone knows what it should look like.

There isn't an entry in `librustc_trans/context.rs` in this diff, because it already exists (`llvm.expect.i1` is used for array indices).

----

Even though this does affect httparse benchmarks, it doesn't seem to affect it the same way GCC's `__builtin_expect` affects picohttpparser. I was confused that the deviation on the benchmarks grew hugely when testing this, especially since I'm absolutely certain that the branchs where I added `likely` were always `true`. I chalk that up to GCC and LLVM handle branch prediction differently.

cc #26179
@chriskrycho
Copy link
Contributor

Just adding a note here as I'm processing everything in #38643: it will need docs before stabilization. @nikomatsakis would you mind adding a note to that effect in the root of the issue?

@nikomatsakis
Copy link
Contributor Author

@chriskrycho done

@joshtriplett
Copy link
Member

joshtriplett commented Nov 10, 2021

@Amanieu I'd personally like to have both, since likely and unlikely intrinsics work in more contexts (e.g. sub-expressions).

EDIT: I now agree that an attribute makes sense.

@scottmcm
Copy link
Member

scottmcm commented Nov 10, 2021

@Amanieu I would also like to have that. One thing that got mentioned in the meeting was that we have #[cold] for functions and closures, so allowing that on blocks might be an interesting way to phrase it.

(Albeit one can mostly write #[cold] { ... } as (#[cold]||{ ... })().)

@Amanieu
Copy link
Member

Amanieu commented Nov 10, 2021

Clang's CodeGenFunction::EmitBranchOnBoolExpr has special handling for __builtin_expect (which is what likely/unlikely use under the hood) which is not present in rustc.

Specifically, it performs the following transformations with boolean expressions:

likely(a && b) => likely(a) && likely(b)
unlikely(a && b) => a && unlikely(b)
likely(a || b) => a || likely(b)
unlikely(a || b) => unlikely(a) || unlikely(b)

In rustc this would need to be done during THIR to MIR lowering, which would require the compiler to treat these intrinsics specially.

Additionally, the llvm.expect intrinsic is designed to work when its result is directly passed to a br LLVM IR instruction. It does not work if used outside of this context (example), which is not very obvious.

@joshtriplett
Copy link
Member

@Amanieu Those transformations seem potentially useful. I personally wouldn't consider them necessary to make these intrinsics useful, though. Would you consider those transformations to be blockers?

@iago-lito
Copy link
Contributor

I have a naive question here: besides likely and unlikely hints, sometimes I have a guarantee that the branch taken will actually be (uniformly) random. Would such a hint be useful at all for compilation? Would it be equivalent to every branch being unlikely?

@the8472
Copy link
Member

the8472 commented Nov 11, 2021

sometimes I have a guarantee that the branch taken will actually be (uniformly) random.

LLVM already has unpreditable for that. This came up in #53823

@MSxDOS
Copy link

MSxDOS commented Nov 21, 2021

These intrinsics no longer have any effect for my code (starting with #87570 ?).
Easily compared by importing them as use core::intrinsics::{likely as unlikely, unlikely as likely}; or replacing with blank functions, in all cases produced binaries are identical.

@the8472
Copy link
Member

the8472 commented Nov 25, 2021

@MSxDOS that's probably worth a regression issue.

@joshtriplett joshtriplett removed I-libs-api-nominated Nominated for discussion during a libs-api team meeting. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 1, 2021
@Amanieu
Copy link
Member

Amanieu commented Dec 1, 2021

We discussed this during the libs-api meeting and don't think that a function is the correct interface for exposing probabilities on LLVM IR branch instruction (or equivalents in other backends). We would instead prefer an attribute-based solution which would work on both if and match arms.

Passing this back to T-lang.

@Amanieu Amanieu added T-lang Relevant to the language team, which will review and decide on the PR/issue. and removed T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Dec 1, 2021
@joshtriplett joshtriplett added the S-tracking-perma-unstable Status: The feature will stay unstable indefinitely. label Dec 8, 2021
@joshtriplett
Copy link
Member

For this tracking issue, for the intrinsics, we're marking these as "perma-unstable", like most LLVM intrinsics.

We'd love to see a lang MCP for #[likely] and #[unlikely] attributes.

@joshtriplett joshtriplett added the E-help-wanted Call for participation: Help is requested to fix this issue. label Dec 8, 2021
bors added a commit to rust-lang-ci/rust that referenced this issue Jan 22, 2024
#[cold] on match arms

### Edit

This should be in T-lang. I'm not sure how I can change it.

There is discussion: https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Allow.20.23.5Bcold.5D.20on.20match.20and.20if.20arms

### Summary

Adds the possibility to use `#[cold]` attribute on match arms to hint the optimizer that the arm is unlikely to be taken.

### Motivation

These hints are sometimes thought to help branch prediction, but the effect is probably marginal. Modern CPUs don't support hints on conditional branch instructions. They either have the current branch in the BTB (branch prediction buffer), or not, in which case the branch is predicted not to be taken.

These hints are, however, helpful in letting the compiler know what is the fast path, so it can be optimized at the expense of the slow path.

`grep`-ing the LLVM code for BlockFrequencyInfo and BranchProbabilityInfo shows that these hints are used at many places in the optimizer. Such as:
- block placement - improve locality by making the fast path compact and move everything else out of the way
- inlining, loop unrolling - these optimizations can be less aggressive on the cold path therefore reducing code size
- register allocation - preferably keep in registers the data needed on the fast path

### History

RFC 1131 ( rust-lang#26179 ) added `likely` and `unlikely` intrinsics, which get converted to `llvm.expect.i8`. However this LLVM instruction is fragile and may get removed by some optimization passes. The problems with the intrinsics have been reported several times: rust-lang#96276 , rust-lang#96275 , rust-lang#88767

### Other languages

Clang and GCC C++ compilers provide `__builtin_expect`. Since C++20, it is also possible to use `[[likely]]` and `[[unlikely]]` attributes.

Use:
```
if (__builtin_expect(condition, false)) { ... this branch is UNlikely ... }

if (condition) [[likely]] { ... this branch is likely... }
```

Note that while clang provides `__builtin_expect`, it does not convert it to `llvm.expect.i8`. Instead, it looks at the surrounding code and if there is a condition, emits branch weight metadata for conditional branches.

### Design

Implementing `likely`/`unlikely` type of functions properly to emit branch weights would add significant complexity to the compiler. Additionally, these functions are not easy to use with `match` arms.

Replacing the functions with attributes is easier to implement and will also work with `match`.

A question remains whether these attributes should be named `likely`/`unlikely` as in C++, or if we could reuse the already existing `#[cold]` attribute. `#[cold]` has the same meaning as `unlikely`, i.e., marking the slow path, but it can currently only be used on entire functions.

I personally prefer `#[cold]` because it already exists in Rust and is a short word that looks better in code. It has one disadvantage though.
This code:
```
if cond #[likely] { ... }
```
becomes:
```
if cond { ... } #[cold] { ... empty cold branch ... }
```

In this PR, I implemented the possibility to add `#[cold]` attribute on match arms. Use is as follows:
```
match x {
    #[cold] true => { ... } // the true arm is UNlikely
    _ => { ... } // the false arm is likely
}
```

### Limitations
The implementation only works on bool, or integers with single value arm and an otherwise arm. Extending it to other types and to `if` statements should not be too difficult.
bors added a commit to rust-lang-ci/rust that referenced this issue Nov 17, 2024
Likely unlikely fix

RFC 1131 ( rust-lang#26179 ) added likely/unlikely intrinsics, but they have been broken for a while: rust-lang#96276 , rust-lang#96275 , rust-lang#88767 . This PR tries to fix them.

Changes:
- added a new `cold_path()` intrinsic
- `likely()` and `unlikely()` changed to regular functions implemented using `cold_path()`
github-actions bot pushed a commit to rust-lang/miri that referenced this issue Nov 18, 2024
Likely unlikely fix

RFC 1131 ( rust-lang/rust#26179 ) added likely/unlikely intrinsics, but they have been broken for a while: rust-lang/rust#96276 , rust-lang/rust#96275 , rust-lang/rust#88767 . This PR tries to fix them.

Changes:
- added a new `cold_path()` intrinsic
- `likely()` and `unlikely()` changed to regular functions implemented using `cold_path()`
@saethlin
Copy link
Member

#120370 should now work, at least sometimes. I don't know if #88767 is fixed, but since reviewing the fix PR I now have the opinion that a #[cold] on arms or the intrinsics::cold_path() strategy is the right way to provide this branch hinting information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-intrinsics Area: Intrinsics B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC E-help-wanted Call for participation: Help is requested to fix this issue. metabug Issues about issues themselves ("bugs about bugs") S-tracking-perma-unstable Status: The feature will stay unstable indefinitely. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests