llvm / llvm/llvm-project

[MachineBlockPlacement][X86] -O2 slower than -O1 on CSV parse loop (Zen 5): branch misses ~2.6×

Open
#218,248 2 comments 0 reactions 1 assignee Claimed by @compilersutra View on GitHub
backend:X86 llvm:optimizations performance
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

On a small CSV parse loop, **`-O2` is slower than `-O1`** on Zen 5 even though instruction count is similar.

**What we see (perf, N=1048576):**

| Build | Time | Branch misses |
|-------|-----:|--------------:|
| `-O1` | 64.9 ms | 0.657M |
| `-O2` | 71.9 ms | 1.730M |
| `-O2 -disable-block-placement` | 66.3 ms | 0.660M |

**Repro:**
```bash
clang -O1 machine_block_placement_csv_parse.c -o parse.O1
clang -O2 machine_block_placement_csv_parse.c -o parse.O2
clang -O2 -mllvm -disable-block-placement machine_block_placement_csv_parse.c -o parse.O2np
./parse.O1 1048576 # PASS sum=15723844160
```

**Key finding:** Turning off block placement fixes the slowdown and branch misses.

**Pass:** `opt-bisect-limit=309` → `MachineBlockPlacement`

**Assembly:** `-O2` uses backward jumps to a shared increment; `-O1` / `-O2np` use forward fall-through.

Not a correctness bug — looks like a **layout / branch-miss issue** on this workload.

**Environment:** Ryzen 7 9700X,(Zen 5) Clang 18.1.3 and clang-24
**Source:**
```cpp
#include
#include
#include
#include

static uint32_t lcg(uint32_t *s) {
*s = *s * 1664525u + 1013904223u;
return *s;
}

/* Known sums for seed=29 and snprintf "%d,%d,%d\n" with (lcg%10000) fields. */
static long expected_sum(size_t lines) {
switch (lines) {
case 1: return 12313;
case 10: return 140157;
case 100: return 1465822;
case 1000: return 15009852;
case 1048576: return 15723844160L;
default: return -1; /* unknown — skip check */
}
}

int main(int argc, char **argv) {
size_t lines = argc > 1 ? (size_t)atoll(argv[1]) : (size_t)(1u << 20);
size_t cap = lines * 48 + 64;
char *buf = (char *)malloc(cap);
if (!buf) {
fprintf(stderr, "oom\n");
return 2;
}

size_t len = 0;
uint32_t s = 29;
for (size_t i = 0; i < lines; i++) {
int a = (int)(lcg(&s) % 10000);
int b = (int)(lcg(&s) % 10000);
int c = (int)(lcg(&s) % 10000);
int n = snprintf(buf + len, cap - len, "%d,%d,%d\n", a, b, c);
if (n < 0 || (size_t)n >= cap - len) {
fprintf(stderr, "buffer too small\n");
free(buf);
return 2;
}
len += (size_t)n;
}

/* Hot kernel under study. */
long sum = 0;
const char *p = buf;
const char *end = buf + len;
while (p < end) {
char *q;
long x = strtol(p, &q, 10);
if (q == p)
break;
sum += x;
p = q;
if (p < end && (*p == ',' || *p == '\n'))
p++;
}

long want = expected_sum(lines);
if (want >= 0 && sum != want) {
fprintf(stderr, "FAIL lines=%zu sum=%ld expected=%ld\n", lines, sum, want);
free(buf);
return 1;
}

printf("PASS lines=%zu sum=%ld\n", lines, sum);
free(buf);
return 0;
}

```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.