Performance regression (~20%) due to LLVM optimization removal in versions after 18.1
- Dominant language
- C++
- Stars
- 3k
- Forks
- 352
- PR merge metrics
- No merged PRs in 30d
Description
A significant performance regression (approximately 20%) has been identified in certain benchmarks when using ISPC with LLVM versions newer than 18.1. The root cause has been traced to the removal of a specific LLVM optimization that was initially added but later disabled due to correctness concerns with `NaN` values.
The performance regression stems from changes in LLVM's handling of `select`/`fadd` instruction patterns. In LLVM 18.1, the following optimization was performed:
Before optimization:
```
%add_e_load41_calltmp44 = fadd <16 x float> %e.1, %calltmp44
%1 = select <16 x i1> %cmp36, <16 x float> %add_e_load41_calltmp44, <16 x float> %e.1
```
After optimization:
```
%add_e_load41_calltmp44 = select <16 x i1> %cmp36, <16 x float> %calltmp44, <16 x float>
%1 = fadd <16 x float> %e.1, %add_e_load41_calltmp44
```
The optimization was introduced in https://github.com/llvm/llvm-project/commit/9cd7c534e27c2558ef16e14d4440bd838320334b but later disabled in https://github.com/llvm/llvm-project/pull/83200 due to incorrect handling of `NaN` values.
While the generated assembly instructions appear similar, the optimization significantly affects register allocation. Without this optimization (which triggers approximately 34 times in the benchmark), increased register pressure leads to suboptimal register allocation with 5 additional 64-byte spills/reloads within the inner loop, resulting in the observed ~20% performance degradation.
An attempt was made to re-enable the optimization by explicitly setting the `nnan` fastmath flag on select instructions:
```
diff --git a/src/opt/LowerISPCIntrinsics.cpp b/src/opt/LowerISPCIntrinsics.cpp
index 48c4a05e..2f896485 100644
--- a/src/opt/LowerISPCIntrinsics.cpp
+++ b/src/opt/LowerISPCIntrinsics.cpp
@@ -142,6 +142,13 @@ static llvm::Value *lLowerBlendStore(llvm::CallInst *CI) {
llvm::Value *selected = lCreateSelect(builder, M, V, LI);
llvm::StoreInst *SI = builder.CreateStore(selected, P);
+ if (T->isFPOrFPVectorTy()) {
+ // Enable select instruction folding into floating point binary operators.
+ llvm::FastMathFlags FMF;
+ FMF.setNoNaNs();
+ llvm::cast(selected)->setFastMathFlags(FMF);
+ }
+
return SI;
}
```
However, this approach is not viable as a general solution because there's no guarantee that `select` operands will never contain `NaN` values, leading to incorrect code generation in other benchmarks.
This appears to be a challenging regression to resolve because the previous performance gain was achieved through a transformation that was technically invalid. It was fortunate that it didn't cause correctness issues in the affected use cases, but it's unclear whether similar performance gains can be achieved through valid transformations.
This issue serves to document the regression and its root cause for future reference, preserve the investigation findings, and track potential future solutions as LLVM evolves.
Contributor guide
Assessment
This issue has not been assessed yet.