-
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
Add an expect intrinsic #1131
Add an expect intrinsic #1131
Conversation
`if expect(foo == bar, false) { .. }`. | ||
|
||
The expected value is required to be a constant value. | ||
|
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 assume you mean to use something like llvm.expect.i8
and converting the booleans to i8s, but this could be included in the Detailed Design.
Likewise, when I tried this myself by creating extern fn expect_i8
etc functions, linking against llvm, I couldn't find the optimizations in the asm. It'd be nice to see the improvements that such a function could possibly bring in this RFC.
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.
@seanmonstar actually, you can use llvm.expect.i1
. As for improvements, that's hard to show since benchmark code tends to warm up the prediction table pretty quickly. It also isn't always visible in the asm if the code was already structured such that the blocks were ordered appropriately.
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.
Right, but we don't have an i1
, the smallest in rust is i8
.
True about the benchmarks. I meant the compiled asm having the jump altered, before and after using expect
.
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.
@seanmonstar actually, bool
s are now treated as i1
s when generating LLVM IR.
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.
Ah, very cool. That confusion may be even more reason to include the implementation details.
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, and I left it out of the detailed design because I don't think the precise implementation details for a specific backend are important. Just the semantics of the intrinsic.
However, 👍 assuming there is a gain to found in there. This is one of the bigger pieces missing so that httparse can be as fast picohttpparser. |
This is something I'd like to see in Rust. However, the suggested 'syntax' seems weird (though some what traditional and I'm guessing close to llvm) from both from a use and implementation point of view: why is the intrinsic on the condition when the hint is about which branch is taken? If we start doing our own optimisations, I assume we'd have to be careful about any kind of code motion here in order to not break hinting to llvm. Could we instead have an attribute on the |
IIRC, this is because I'm fully in favor of this RFC... this is very important for getting optimal codegen in many cases and I've personally been wishing for it. Right now I've been resorting to #[cold] to get the desired behavior in a few cases, but it's too blunt of an instrument to use everywhere. |
|
To me, the fact that a constant expression is necessary seems to argue for @nagisa The enum variant case could be done with dependent types, sort of: fn expect<const C: T, T=bool>(expr: T) {
/* ... */
expr
}
if expect::<true>( /* some expr */ ) { /* ... */ }
match expect::<Variant1, MyEnum>( /* some expr */ ) {
Variant1 => { /* ... */ }
_ => { /* ... */ }
} But that's a bit clunky and doesn't deal with patterns (e.g. if you want to state that a branch for Edit: My original example didn't make any sense at all. Hopefully it's better now. |
@quantheory yeah, I'd be happy with @nrc Ok, where would the attribute go? On the @nagisa most of the time use of branch hinting doesn't help at all. The fact that you can't hint about match expressions isn't really a concern I have. This is quite simply to help tune code where the penalty of a misprediction can end up quite large. Most of the time, the only difference this will make to codegen is block reordering (for example, we hint the overflow checks as being unlikely, which pushes the panics down to the bottom of the function). |
@quantheory 👍 for |
@Aatch Seems reasonable to have the annotation on the condition. What about the idea of always assuming the |
I'd rather it not be an attribute, as that would make it harder when an if statement includes different conditions with different likelihoods. Take this example: https://github.com/h2o/picohttpparser/blob/master/picohttpparser.c#L173 |
That would lead to bad codegen when checking error conditions: if some_unexpected_error_condition {
return Err(...);
} Aside: It would also be nice to get likely/unlikely attributes for enum variants: enum Result<V, E> {
#[likely]
Ok(V),
#[unlikely]
Err(E)
} These wouldn't override an explicit likely/unlikely call on a branch but would provide sane defaults. Unfortunately, this would likely take quite a bit more work to implement. |
@nrc having the attribute on the condition is ultimately identical to having a function call wrapping the condition, no? I'd argue that an attribute on the condition itself would be really unclear, especially since you'd probably need to wrap the condition in parens anyway to be sure that it's applying to the correct expression. What this means is that instead of |
I really like the attribute on variants idea by @Stebalien |
@Aatch sorry, I was asking about the idea of always assuming the |
@nrc, I don't think we should have any guarantees for this feature beyond "doesn't change the observable behaviour of the function", meaning we could just ignore it if we wanted without technically doing anything wrong. In fact, there's no guarantee that it will do anything anyway. LLVM might have ordered the blocks the same way with or without the hint. I guess what I mean is that there isn't any behaviour to preserve for branch hinting. The point is that stuff like ordering blocks is pretty much arbitrary (especially in LLVM IR where you have to have a terminator, no fallthrough) so compilers make a best guess at how to do it. This just gives them another piece of information they can use to do so. If using |
@Stebalien These hints shouldn't be used in a blanket way like this. The default should always be to let the branch prediction do its job. likely and unlikely seems like the most readable API. I would like if the eventual docs recommend profiling and not using these indiscriminately. I'm skeptical of this. Are we giving programmers the illusion of control? Isn't profile guided optimization the correct approach? It also appears that branch prediction hints are being obsoleted with newer microarchitectures. |
I tested the basic benchmark in picohttpparser with and without the likely/unlikely hints. Just to show that it makes a
clang-3.5 with likely/unlikely
clang-3.5 without likely/unlikely
the difference is small but it's there. |
First, this seems like an important area to have support for in general. When I was at the NY meetup, I was approached by a few people working in the "HFT" area who had all sorts of questions about low-level codegen details like this. In most cases, the answer was basically "well, doing that optimization would be LLVM's job, but we'd probably have to expose some primitive". Anyway, I am currently in favor of likely/unlikely (in preference to the current text) as well, but @Aatch I think it would be great to see some discussion of the approaches taken by other compilers, or projects. We've seen already what picohttpparser uses, but I know that many codebases (the linux kernel, the firefox code base, etc) incorporate some sort of hints, and it'd be nice to know what they use.
|
Oh, the other question I was wondering: how fragile are LLVM's optimizations with regard to the expected value? (i.e., with regard to @nrc's point about us having to be careful when we reorder code etc). I would sort of expect that the expectation is associated with an SSA value produced by the |
@nikomatsakis well C/C++ exposes As for how LLVM actually does it, it alters the weights of branches where the condition is the result of the intrinsic. It processes all the branches in the function first before replacing |
@Aatch I think you mean gcc exposes |
It seems to me that |
|
Yeah, I understand the desugaring, I'm more interested in the semantics. (If you're talking about semantics, what's the difference between expectation on the conditional vs. expectation on the values?) |
On the one hand, LLVM does have the i8 expect intrinsic. In that sense, @huonw may be correct. On the other hand, that will in many cases get expanded right back out again for the underlying machine, to some variety of jump-if-equal. (or a branch prediction hint of some sort taking similar arguments) |
@huonw consider |
@bluss while true, I'm actually focusing on the I'm only second guessing myself because the C compilers expose it as |
Michael Kerrisk's post regarding the performance impact of He maintains the Linux manpages project, and one particularly useful point in his post is where the breakeven point is on the benchmark he provides: 1/10^5 (EDIT: typo, actually 1/10^4) incorrect predictions is about where |
@eternaleye that is interesting. The fact that 1/10^5 is the "break-even" point is surprising. At any rate, there's a reason my motivating example is a branch not taken in about 1/(2*10^19) cases. I'll keep stuff like this in mind when documenting the intrinsics though. |
Since the end of the final comment period is approaching and it's likely that this will be accepted when it does, I started on implementation. Turns out that the way we translated function calls meant that the intrinsics didn't actually work, but I improved that, so it all works now. The branch is here: https://github.com/Aatch/rust/tree/branch-hinting |
👍 after having read the diff. |
I'm ok with the unstable intrinsics but still share @nrc's concern about how these are exposed. In Rust they will just look like functions, which I think is pretty damn misleading. At least in gcc they look like magic functions. |
@brson sure, the lower-level In gcc, nobody uses I only think them being functions is misleading if you don't actually know what they do. In which case I ask: why the hell are you using them then?! The actual use of them is pretty clear: |
Agreed with @Aatch. Let's not forget why gcc uses a Let's not try to infer some sort of grand design for the weird-looking names of intrinsic functions in C; there's no such thing, the names are weird because of technical restrictions that Rust thankfully doesn't have. |
It's official... the language subteam has decided to accept this RFC. Thanks @Aatch! |
Tracking issue: rust-lang/rust#26179 |
What is the state of this? |
@ticki, implementation turned out to be rather hard, if you actually wanted the intrinsics to do anything at least. It's turned out to simply be easier to wait for work on MIR to progress and build them on top of that. |
@Aatch, |
@ticki the macro you added? These intrinsics aren't implemented in the compiler at all yet, so I'm not sure what your macro is doing. The likely/unlikely intrinsics work differently to assume, and LLVM is very finicky about the relationship between the generated |
Oh! My bad. I was speaking about the assume intrinsics. I wonder why the intrinsics cannot just use LLVM's branch weight metadata? |
The feature name of the RFC still says |
@Aatch Does this do anything now that we have MIR? |
Are there thoughts on how to get this to work with |
that would probably fall under whatever mechanism ends up being used for
hinting which pattern matching clauses are more likely
…On Wed, Sep 6, 2017, at 16:05, Joshua Liebow-Feeser wrote:
Are there thoughts on how to get this to work with `if let`? I haven't
seen any mention of `if let` in the doc or this discussion.>
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#1131 (comment)
--
cmr
http://octayn.net/
+16038524272
|
Any news on that? |
Merged PRs are generally considered done and their comment thread not a great place to have further discussion. After accepting a Rust RFC (merging the PR) we open a “tracking issue” to discuss its implementation. This one is at rust-lang/rust#26179 |
Rendered View