llvm / llvm/llvm-project

[Missed Optimization][X86] Clang generates ~3x slower code with -march=bdver2 compared to generic x86-64 target (with possible solution)

Open
#223,159 4 comments 0 reactions 0 assignees View on GitHub
backend:X86 backend:X86 Scheduler Models optimization:AMDCPU performance
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Description
When compiling a simple multiply-accumulate vector loop for the AMD Piledriver microarchitecture (`-march=bdver2`), Clang produces severe performance pessimization. Explicitly enabling `-march=bdver2` (or using `-march=native` on an Opteron 6386SE processor) results in code that is over 3 times slower than Clang's default generic x86-64 output.

In stark contrast, GCC successfully utilizes the architecture extensions to make the code slightly faster than its generic baseline.

## Reproducible Code Snippet
This isolated loop demonstrates the regression:

```
void test_loop(const float *restrict in, float *restrict out, unsigned n, float scale, float bias) {
for (unsigned i = 0; i < n; ++i)
out[i] = in[i] * scale + bias;
}
```

I have a complete project on [clang-bdver2-pessimization](https://github.com/pipatron/clang-bdver2-pessimization).

## Environment & Compiler Versions

* OS: Fedora 44
* Clang: version 22.1.8 (Fedora 22.1.8-4.fc44)
* GCC: version 16.2.1 20260819 (Red Hat 16.2.1-2)
* Target Hardware: AMD Opteron 6386SE ("Abu Dhabi" Piledriver, Family 15h, bdver2)

## Interactive Analysis

* Compiler Explorer (Godbolt): https://godbolt.org/z/6v5ne1M5o

## Observed Performance Matrix (Google Benchmark)
Built with -O3 -std=c23. Throughput metrics indicate a severe drop when target-specific vectors are enabled under Clang:

| Benchmark Configuration | Iteration Size | CPU Time | Processing Throughput |
|---|---|---|---|
| Clang (Generic Baseline) | 100,000 | 26,994 ns | 27.60 Gi/s |
| Clang (-march=bdver2) | 100,000 | 93,162 ns | 7.99 Gi/s (~3.4x slower) |
| GCC (Generic Baseline) | 100,000 | 30,125 ns | 24.73 Gi/s |
| GCC (-march=bdver2) | 100,000 | 26,501 ns | 28.11 Gi/s (Faster as expected) |

#### Assembly Analysis & Root Cause
Looking at the generated assembly from the pessimized Clang version, Clang aggressively unrolls and uses 256-bit YMM FMA3 instructions:

```assembly
.LBB0_6:
vfmaddps ymm4, ymm2, ymmword ptr [rdi + r8], ymm3
vfmaddps ymm5, ymm2, ymmword ptr [rdi + r8 + 32], ymm3
vfmaddps ymm6, ymm2, ymmword ptr [rdi + r8 + 64], ymm3
vfmaddps ymm7, ymm2, ymmword ptr [rdi + r8 + 96], ymm3
vmovups ymmword ptr [rsi + r8], ymm4
...
```

**The Cost-Model Flaw (featuring Gemini):**
On `bdver2` (AMD Piledriver), 256-bit YMM instructions are structurally split into dual 128-bit macro-ops, introducing severe execution pipeline stalls. Clang's loop vectorizer cost model is overestimating the throughput of 256-bit YMM paths on this sub-architecture, resulting in heavy unrolled loops that heavily thrash the Piledriver decoders.

Clang should either stick to 128-bit XMM code gen or favor native FMA4 scheduling pipelines when `-march=bdver2` is specifically requested, rather than defaulting to this highly damaging 256-bit unrolled FMA3 sequence.

#### Workaround / Definitive Validation
Adding the compiler flag **`-mprefer-vector-width=128`** to Clang completely resolves the performance regression.

When vector widths are explicitly constrained to 128 bits, Clang's performance recovers entirely and achieves execution parity with GCC. This strongly suggests that the root bug is a flaw in the target's loop vectorizer cost model—specifically, Clang fails to properly penalize the execution overhead of 256-bit YMM instructions on the dual-pumped 128-bit execution pipelines of `bdver2` hardware.

Contributor guide

Open the contributing guide

Research direction

Start with the provided loop and Compiler Explorer link, comparing Clang with generic x86-64, -march=bdver2, and -mprefer-vector-width=128. Trace the bdver2 vectorizer cost-model handling for 256-bit versus 128-bit operations. Done means a regression test or benchmark demonstrates that bdver2 no longer selects the severely slower path while preserving expected target-specific optimization.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.