Change:
I tried this code, sorting 30 million random : On this machine: Using the vendored version of from mid 2022 see here I expected to see this happen: Runtime ~1.45s Instead, this happened: Runtime ~2s I bisected the issue down to with . The same happens if you do instead of . So this also affects the current implementation which is very similar to my vendored version.
Dinging into it, I think the relevant code that has this impact is in the function. Which has two blocks of logic one for if the left side is shorter and one if the right side is shorter. Previously both sides generated code that is branchless in terms of jumping based on the comparison result. Now the block handling left side is shorter generates a jump based on the comparison result. LLVM 15: ! LLVM 16: ! This can be fixed in code by using 'proper' branchless code: -This would fix the stdlib, but it would still mean a regression for any other code out there that relies on this.
I feel like I should attach the usual disclaimer that this kind of optimization can never be considered fully stable, specifically LLVM has various heuristics that affect whether it compiles a segment of code to use a "branchless" CMOV-alike or use a jump, however that doesn't mean there's nothing we can do here. label: +A-LLVM +A-codegen +regression-from-stable-to-beta +I-slow
WG-prioritization assigning priority (). label -I-prioritize +P-low +T-compiler
out of curiosity. What is the future for a perf regression marked as P-low? Is there any work planned on this? Personally I was surprised that it even generated branchless code in the first place.
A P-high or P-critical compiler issue means that it will be reviewed regularly by T-compiler until it is resolved, where "regularly" is a much higher frequency for P-critical. So a P-low implicitly means "this isn't so important that we need to keep putting it on the agenda". Though I should note that periodically, all T-compiler issues do get reviewed and triaged, just on a more irregular basis. Performance work is one of those "never done" things so it's rare for it to be high priority in general, which isn't the same as "no one will work on it". It just means it would be addressed in a somewhat irregular fashion.
I'm not sure I understand the difference between "never done" and "no one will work on it".
"Never done" as in "never finished" not "never worked on"
Ohh, thanks for clarifying. Anyway from my perspective me and other people I've talked to were surprised this even generated branchless code in the first place, I'd be fine with closing this issue as won't fix.
since you have opened a PR (thanks a lot for that!), it makes totally sense to try fixing it, no matter the priority we assign to issues.
I think the issue can be split into two parts: A) A significant regression in . B) LLVM no longer generating branchless code for a certain pattern. I think A should definitely be fixed, and that's what my PR is for. B however I'd argue doesn't really need fixing, because the code in question was not obviously branchless.

Discussion:
// SAFETY: left and right must be valid and part of v same for out. unsafe { <del> let to_copy = if is_less(&*right, &**left) { get_and_increment(&mut right) } else { get_and_increment(left) }; ptr::copy_nonoverlapping(to_copy, get_and_increment(out), 1); </del> <ins> let is_l = is_less(&*right, &**left); let to_copy = if is_l { right } else { *left }; ptr::copy_nonoverlapping(to_copy, *out, 1); *out = out.add(1); right = right.add(is_l as usize); *left = left.add(!is_l as usize); </ins> } } } else {

// SAFETY: left and right must be valid and part of v same for out. unsafe { <del> let to_copy = if is_less(&*right.sub(1), &*left.sub(1)) { decrement_and_get(left) } else { decrement_and_get(right) }; ptr::copy_nonoverlapping(to_copy, decrement_and_get(&mut out), 1); </del> <ins> let is_l = is_less(&*right.sub(1), &*left.sub(1)); *left = left.sub(is_l as usize); *right = right.sub(!is_l as usize); let to_copy = if is_l { *left } else { *right }; out = out.sub(1); ptr::copy_nonoverlapping(to_copy, out, 1); </ins> } } } // Finally, `hole` gets dropped. If the shorter run was not fully consumed, whatever remains of // it will now be copied into the hole in `v`. <del> unsafe fn get_and_increment<T>(ptr: &mut *mut T) -> *mut T { let old = *ptr; // SAFETY: ptr.add(1) must still be a valid pointer and part of `v`. *ptr = unsafe { ptr.add(1) }; old } unsafe fn decrement_and_get<T>(ptr: &mut *mut T) -> *mut T { // SAFETY: ptr.sub(1) must still be a valid pointer and part of `v`. *ptr = unsafe { ptr.sub(1) }; *ptr } </del> // When dropped, copies the range `start..end` into `dest..`. struct MergeHole<T> { start: *mut T,