jiebi/CodeConvo huggingface dataset:
What's happening here is that we have one huge CGU, but when we split it up we still have one huge CGU. This means that the CGU which may take ~1% less time in LLVM only gives us a tiny sliver of a window to run ThinLTO passes. In the case of the crate this means that adding ThinLTO passes is an overall build time regression. So generalizing even further, I think that enabling ThinLTO and multiple CGUs by default is going to regress compile time performance in any crate where our paritioning implementation doesn't actually partition very well. In the case of we've got one huge function which hogs almost all the optimization time (I think) and the entire crate is basically dominated by that one function. I would be willing to conclude, however, that such a situation is likely quite rare. Almost all other crates in the ecosystem will benefit from the partitioning which should basically evenly split up a crate. maybe you have thoughts on this though?
Thank you so much for collecting and analyzing such a large amount of data, Your conclusions make sense to me. Unevenly distributed CGU sizes are also a problem for incremental compilation; e.g. the test sees 90% CGU re-use, yet compile time is almost the same as from-scratch. In the non-incremental case it might be an option to detect the problematic case right after partitioning and then switch to non-LTO mode? I'm not sure it's worth the extra complexity though. For cases where there is one big CGU but that CGU contains multiple functions, we should be able to rather easily redistribute functions to other CGUs. We'd only need a metric for the size of a . The number of MIR instructions might be a sufficient heuristic here. That would not help for but it might help for other crates. In conclusion, judging from the table above, I think we should make the default sooner rather than later. Open questions I see: What to do about ? I would have thought that it would always be a clear win to disable this when ThinLTO is enabled but that doesn't seem to be the case. Are there hardware configurations where this never is a win? E.g. should we default to traditional compilation if ?
FWIW, as the author of , which is a made-for-fun Piston game, I am more than happy to take a small compile regression when it seems like vast majority of crates get huge real-time improvements!
This seems like a good thing to have in our back pocket, but I'm also wary of trying to do this by default. For example the case is one where we may wish to disable ThinLTO but the one massive function could also critically rely on a function in a different CGU being inlined? That sort of case would be difficult for us to determine... In any case though I agree that we probably don't need the complexity just yet so I think we can defer this for later. Agreed! So far all the crate's I've seen the O(instructions) has been quite a good metric for "how long this takes in all LLVM-related passes", so counting MIR sounds reasonable to me as well. Again though I also feel like this is ok to have in our back pocket. I'd want dig more into the minor change example, I'm curious how awry the CGU distribution is there and whether a heuristic like this would help. Quite surprisingly I've yet to see any data that it's beneficial to compile time in release mode or doesn't hurt runtime. (all data is that it hurts both compile time and runtime performance!) That being said we're seeing such big wins from ThinLTO on big projects today that we can probably just save this as a possible optimization for the future. On the topic of inline functions I recently dug up again (later closed in favor of , but the latter may no longer be true today). I suspect that may "fix" quite a bit of the compile time issue here without coming at a loss of performance? In any case possibly lots of interesting things we could do there. Also a good question! I find this to be a difficult one, however, because the CGU number will affect the output artifact, which means that if we do this sort of probing it'll be beneficial for performance but come at the cost of more difficult deterministic builds. You'd have to specify the CGUs manually or build on the same-ish hardware to get a deterministic build I think? I think though that if you have 1 CPU then this is universaly a huge regression.
With one CPU we'd split the preexisting one huge CGU into N different ones, probably take roughly the same amount of time to opimize those, and then tack on ThinLTO and more optimization passes. My guess is that with one CPU we'd easily see 50% regressions. With 2+ CPUs however I'd probably expect to see benefits to compile time. Anything giving us twice the resources to churn the cgus more quickly should quickly start seeing wins in theory I think. For now though the deterministic builds wins me over in terms of leaving this as-is for all hardware configurations. That and I doubt anyone's compiling Rust on single-core machines nowadays!
One thing I think that's also worth pointing out is that up to this point we've mostly been measuring the runtime of an entire . That's actually, I believe, the absolute worst case scenario for where ThinLTO will provide benefit. Despite this, it's showing huge improvements for lots of projects! The benefit of ThinLTO and multiple CGUs is leveraging otherwise idle parallelism on the build machine. It's overall increasing the amount of work the compiler does. For a from scratch, though, you typically already have tons of crates compiling for the first half of the build in parallel. In that sense there's not actually any idle parallelism. Put another way, ThinLTO and multiple CGUs should only be beneficial for builds which dont have many crates compiling in parallel for long parts of the build. If a build is 100% parallel for the entire time then ThinLTO will likely regress compile time performance. Now you might realize, however, that one very common case where you're only building one crate is in an incremental build! Typically if you do an incremental build you're only building a handful of crates, often serially. In that sense I think that there's some massive wins of ThinLTO + multiple CGUs in incremental builds rather than entire crate builds. Although improving both is of course great as well :)
For deterministic builds you have to do some extra configuration anyway (e.g. path remapping) so I would not consider that a blocker. And I guess there are single core VMs around somewhere.
But all of this is such a niche case that I don't really care MIR-only RLIBs should put us into a pretty good spot regarding this, so I'm quite confident that we're on the right path overall.
That's really interesting. For incremental compilation enabled the situation might be different (because pre-trans inlining hurts re-use) but for the non-incremental case it sounds like it's pretty clear what to do.
Yeah, maybe we should revisit this at some point. Although I have to say the current solution of only and symbols and nothing in between is really nice and simple.
Hm yeah that's a good point about needing configuration anyway for deterministic builds. It now seems like a more plausible route to take! Also that's a very interesting apoint about incremental and inlining on our own end... Maybe we should dig more into those ThinLTO runtime regressions at some point! Also yeah I don't really want to change how we trans inline functions just yet, I do like the simplicity too :)
out of curiosity how did you figure out this and which function it was. I've wanted to look into why the webrender build is so slow and would welcome tips.
oh sure I'd love to explain! So I originally found as a problematic crate when compiling rust-belt as it just took awhile and I decided to dig deeper. I checked out the crate and ran: That drops in the current directory, and opening that up I see: ! The graph here isn't always the easiest to read, but we've clearly got two huge bars, both of which correspond to taking a huge amount of time for that one CGU (first is optimization, second is ThinLTO + codegen). Next I ran: and that command dumps a bunch of IR files into . Our interesting CGU is so I opened up (we sure do love our long filenames). Inside that file it was 70k lines and some poking around showed that one function was 66k lines of IR. I sort of forget now how at this point I went from that IR to determining there was a huge function in there though...
I don't know if this is the right place, but I think it should be considered whether the symbol issues reported in the Nightly section here: should be considered a blocker or not.
The tl;dr is that certain versions of llvm, (i've seen this on 3.8, and whatever llvm rustc nightly uses) appends seemingly random garbage to the end of some names, e.g. we get: instead of: This knocks the debuginfo out of sync (it doesn't have the garbage appended). I've been able to repro this with clang3.8 for c++ files as well (haven't tested on other llvm), and switching to 5.0 seems to have fixed the issue. I don't know if its within reach, but perhaps we should attempt upgrading to llvm 5.0 before releasing this on stable? Note I understand this is for release mode, but i see this in debug mode on rustc nightly right now as well...
If you try to compile the crate with , it fails with the following error: This seems to be a regression from to , and my best guess for the change that caused it is this pull request: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
Filed to fix this. FYI, a short-term workaround is to enable the target feature (). claim
Possibly related: This is seen cross-compiling from Linux to Windows. Giant 1MB linker error message related to atomicload, only in release mode, for target x8664-pc-windows-gnu. Failing in rustc 1.67.1 stable. Currently asking for help comments; will file an issue in a day or two if nobody reports a known fix.
Since native atomic ops exist in x86_64, I don't believe this and are related to your problem. Given that rend3 has enabled , your problem may be related to
While checking whether the can be ported from typenum to nightly with , I hit this warning. I am not sure whether it's a false positive or whether the behavior I want is meant to be disallowed, but I think it should be allowed when enabling . I've reduced the code though I left some stuff to show why it would be useful. Originally posted by in
is emitted if we successfully evaluate an anonymous constant with generic const arguments. Anonymous constants currently inherit all of their parents generics, so for the constant has both and in scope, even though it only uses . This would mean that pretty much all anonymous constants would cause that lint, so we add a flag to their MIR to detect whether they actually use any generic parameter: uses so that flag is true. The problem is now that is concrete, so evaluating the constant is successful, but the constant refers to which is still generic. We should just disable the lint if is enabled :shrug: cc
Another similar. Short example failing on Nightly (thanks to ilyvion for narrowing it down): My use case: I know it's a semantic/voluntary constraint only, and it's quite recursive, but I believe it deserves its place. (Alternatives I tried are at )
this has been fixed, needs test to close
I was sent here from the . Please advise if I am posting in the wrong area. format! macro documentation could be improved by changing one of the examples to be more obvious to new users. Change the example to Additionally, assert! could be used to demonstrate final string values: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
Something like ? claim
I think the use of "val" certainly makes it more obvious than my suggestion to use "z". Your suggestion would also do it in one line... I guess the question is whether you would prefer to make it more obvious (readable) by splitting it into two lines. This is my first contribution to Rust in any form, so I'm happy to go with your suggestion.
Thanks all!
adds methods to and . Unresolved questions:
Can I nominate this for stabilization? It's a straightforward API with no real alternatives possible, and I had a real-world use case for it recently.
CC --^?
fcp merge
Team member has proposed to merge this. The next step is review by the rest of the tagged team members: [x] [ ] [x] [x] [ ] No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See for info about what commands tagged team members can give me.
:bell: This is now entering its final comment period, as per the . :bell:
The final comment period, with a disposition to merge, as per the , is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC will be merged soon.
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --Defining traits that contain methods that return such a type: The compiler should probably have warned me about not using the of the , consistent with the behaviour of async functions in Rust 1.67.0. This will also work without the need for traits but async traits is one of the most common use cases for such a type. <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summaryBacktrace</summary<p</p</details<!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
We looked at this as the wg-async triage meeting today. We agree this is a bug and that the behavior should be consistent with on and/or . This is also likely a good first issue for someone wanting to contribute to async.
Would you say this is also a good issue for a first-time contributor to Rust? I'm not super familiar with the language, but I want to contribute to open source compilers, so I'm hoping to potentially take on this task.
Can I take this ?
Either one of you ( or are welcome to work on this! If it's your first time contributing to Rust, it might take a while to ramp up on it, but I find the best way to do that is to just do it. If you decided to have a go at it, feel free to ask any questions you have either here or in . I tend to watch #wg-async closer than GitHub issues, so you'll probably get a more prompt reply there.
this issue wasn't claimed so you are free to claim and work on it if you still are interested. Kindly comment with to claim the issue.
claim
This is also a very special case of
Filing since it's in pFCP over at , and I don't think we can reasonably change it after stabilization (especially since it's documented). Currently, in release mode evaluates to . (And same for the other bases.) It's not obvious to me that that's necessarily the best option. For example, the implementation is slightly nicer on CPUs from the past or so (and I think would be just as nice on older ones ), but gives <subas u32</subin the wrapping case. It also might be nice to have the wrapping case give something clearly different from any of the successful cases, rather than having and give the same thing. For example returns in release mode, which isn't a power of two (according to ), so it's distinguishable from all the non-wrapping cases. This only affects // on . (The checked versions don't have an issue, nor do the ones on .)
I couldn't find any previous discussion of what the exact wrapping value ought to be, just that there ought to be one, as opposed to a . There was this note: For which I think the implementation I mention above is a counter-example, albeit only for the base-2 case. Also, currently in release mode is essentially , so we can pick any value we want to return for those cases. FWIW, the implementation in gives as the result for .
In Rust we try to do the right thing. Not even Python gives -1. Let's avoid in-band error values in Rust.
What's your proposal for what the "right" thing is, then? That panic in release too? Arguably the most-correct value is −∞ (like ), but that doesn't fit in any of our integer types. So would make sense, but release mode for the other un-prefixed things is wrapping, not saturating.
Python has some functions that return in-band values: "xy".find("y") 1 "xy".find("z") -1 From Rust I expect something better. In Rust if a function isn't naturally total and you can't restrict its input, then I expect it to return Option (or Result), or when that's isn't ergonomic enough, a panic (in release too). Returning -1 is dangerous as that Python find function, returning 0 is not math.
I think returning (wrapped if necessary) is a clear distinguish between normal and error result. Returning an is not ideal currently because it will makes the function practically unusable in const context (before const unwrap is stabilized) If getting closest to the "correct" math answer, how about choosing ? However it seems weird and still seems better to me
Perhaps in other languages. Rust is known for being better than that. I have chosen to use Rust because it's a bit better than Python (see above). Let's return Option and and wait for const unwrap to be stabilized before making ilog const, then.
That's what does already. Is your proposal, then, that the version of just shouldn't exist on ? How do you square that with precedent of other things -- like or -- that have the version return , but the un-prefixed one wrap in release mode?
what about treating it like division by zero and just panicking?
I'd be fine with -1. The point of these methods is to get fast code (otherwise many won't use them and will prefer to do to keep the code branchless and avoid the ugly ) and a defined value outside of the usual input range. (from above) is a good example. If you want stricter guarantees than that, you can use .
A minor point: being 0 is actually a reasonable choice - even though 0 is not a power of two, it is congruent to the correct power of two modulo 2^32 (which is 2^32 itself), so exactly what is expected in wrapping arithmetic. But unfortunately there's no "−∞ mod 2^32" that could be used for .
Modular arithmetic is confusing in the context of logarithm, if you want to return the discrete logarithm of 0 in the release mode, then there are a lot of candidates, because (in case of ) therefore in the sense of modular arithmetic, could be any multiple of . If you really want to achieve something close to , then I guess the best value is as I mentioned above. (Interestingly though, = , which is a multiple of 8, 16, 32, 64, 128)
That's certainly a possibility. suggested not doing that back in the original PR, though:
Doesn't implicitly include a check for zero in the code generated?
]
Also, since the methods are implemented for signed integers as well, another issue would be the log of negative values. For example for floating-point, would return &minus;&infin;, but would return NaN. So if would return some value for &minus;&infin;, would panic anyway, or return some value?
I don't think it makes sense to do anything other than panic in the case of negative values, which in turn is probably an argument for panicking on zero as well. I guess one could argue that "wrapping" could be interpreted as wrapping the preimage to fit in the function's domain (rather than just wrapping the image to fit in the range) but IMO that would be entirely unintuitive and unhelpful as well. After thinking about it a bit, it seems to me that simply cannot have a meaningful variant, because semantics (as reasonably interpreted) cannot answer the question "what should the result be for values for which the original function is undefined?" There are no elements in 's domain for which the function underflows or overflows.
That's only the case on old chips. As I linked in the OP (), that happens in the instruction generation very-last-phase of LLVM on old chips, and on newer chips it knows to emit a different instruction instead of the branching code: The good news is that it looks like newer LLVM has gotten smarter here. It used to be that, because of how the intrinsic gets translated, it would emit obviously-redundant assembly on old chips in some code patterns, as you could see in Rust 1.16:
is supposed to find the largest so that . Since computers use wrapping arithmetic, the equation is better put as . The largest satisfying this equation in the case is (aka ). So if we consider a wrapping to be the inverse of , makes perfect sense (and is very efficient on modern CPUs, as others have pointed out). I would also advocate for to be as a method which always has this behaviour. [Edit: it already behaves that way]
Note that's already how works . It's just that it wraps to in release right now -- this issue doesn't propose removing the panic in debug behaviour, just to figure out what the best value for release would be.
Since, from the notes, it seems like the libs-api meeting today said "how about we panic instead of wrap", I'll try to answer my own question from above: I would propose the following rule: Conceptually, the result is first calculated in true ℤ (aka to infinite precision). Then for the non-prefixed versions If that result is undefined (, , etc), then it panics (regardless of compilation mode) If that result is defined and fits in the result type, it's returned. If that result is defined but not in-range for the result type, it panics if overflow checking is enabled, and wraps (mod 2<supN</supfor uN) if overflow checking is disabled. I think that's consistent with all the current precedent. ( also is good reading about the distinction I'm drawing here.)
I would redefine that to include cases where the result is defined but you can't wrap that to the result type, e.g. or . that would cover and
I agree, but was considering that implied by "in ℤ", as that set includes neither −∞ nor ⅈ. (Like how is , not a complex number.)
I'm not necessarily against panicking in that case. I just pointed out how it's a bit inconsistent with the other operations we have on integers. (Whether it makes sense for the other operations to have this difference in behavior between debug and release mode is an interesting discussion too, but I'm afraid that ship has sailed.) The reason we don't always panic on overflow for and etc. is performance. It's simply too costly to do. (We always panic on division by zero because that's both mathematically undefined (even modulo 2<supBITS</sup) and because on some platforms, the native division instruction will signal/abort/trap the program when the right hand side is zero.) We applied the same reasoning to things like and , although one might reasonably argue that for those it might be fine to always panic, since these are not as common as and . (You could then still use if you really want to make sure there's no check, if you're sure it's unnecessary.) That seems reasonable. It'd be nice if it'd still be easy to get the efficient calculation through some usage of or though.
Example: Results in the following error: of course, is by default unless you say , so the error and the suggestion are clearly wrong. And if you do say explicitly, you get Note being suggested, which is ~not even valid syntax~ apparently valid syntax, but not something you would write. I expected to see this happen: Either this compiles cleanly Or the compiler emits a different error that makes sense <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: (Seems to happen on nightly too.) </details
Duplicate of
Reopening to track the error message, which is not tracked by . is just wrong.