Files
263-0007-00L-Advanced-Syste…/docs/optimization_branchless.md
T
2026-05-20 22:23:07 +02:00

130 lines
5.2 KiB
Markdown

# Optimization: Branchless w() (negative result)
- **Author**: Linus
- **Predecessor**: variant_baseline.cpp
- **File(s)**: variants/variant_branchless.cpp, variants/variant_branchless_sqrtfree.cpp
- **Status**: probably: negative (slower)
## What this changes
Two attempts at making the field-of-view weight function `w()` branchless.
**Attempt 1** (`variant_branchless.cpp`): replace the if/else in `w()` with an
arithmetic blend `c_neg_times_dt + cond * (dt_neg - c_neg_times_dt)`, where
`cond` is a 0.0/1.0 mask cast from a bool comparison. The `sqrt` inside
`f.norm()` is kept.
**Attempt 2** (`variant_branchless_sqrtfree.cpp`): on top of attempt 1, also
eliminate the `sqrt` by squaring the comparison. Uses the fact that
`cos_phi = cos(100 deg)` is negative, so `f.norm() * cos_phi <= 0`, meaning
the condition is automatically true whenever `e.dot(f) >= 0`. For
`e.dot(f) < 0`, both sides are non-positive so we can square them safely:
`dot^2 <= f.dot(f) * cos_phi^2`. Needs the constant
`cos_phi_sq = cos_phi * cos_phi` in `headers/constants.hpp`.
Additionaly i split the mainloop into two loops, so we where able to remove the if-statement from the loop. Once again that didn't made a difference in the runtime analysis
## Code (relevant snippet)
Attempt 1 - arithmetic blend (still has sqrt):
```cpp
double w(Vector2d e, Vector2d f) {
double dot = e.dot(f);
double f_norm = f.norm();
double cond = (double)(dot >= f_norm * cos_phi); // 1.0 or 0.0
return c_neg_times_dt + cond * (dt_neg - c_neg_times_dt);
}
```
Attempt 2 - blend + sqrt-free (squared comparison):
```cpp
double w(Vector2d e, Vector2d f) {
double dot = e.x * f.x + e.y * f.y;
double f_dot_f = f.x * f.x + f.y * f.y;
// Bitwise | (not ||) keeps both sides evaluated -> no branch.
double cond = (dot >= 0.0) | (dot * dot <= f_dot_f * cos_phi_sq);
return c_neg_times_dt + cond * (dt_neg - c_neg_times_dt);
}
```
## Benchmark
Conditions: min of <N> runs, N_ITER=1000, -O3 -march=native -flto.
A preliminary run on my laptop (Intel ULTRA 9 185H @ 2.2 GHz, WSL
Ubuntu) showed both attempts about 8-13% slower than the baseline across
N=50 to N=1000, with a roughly flat gap that did not scale with N.
For the official numbers run on the testmachines
### x86
**Machine**:
```
<paste machine info here>
```
**Frequency**: <e.g., 2 GHz>
Attempt 1 - branchless (arithmetic blend):
| N | W(n) [Flops] | T(n) [Cycles] | Performance [Flops/Cycle] | Diff vs Baseline |
| ---- | ------------ | ------------- | ------------------------- | ---------------- |
| 50 | | | | |
| 100 | | | | |
| 200 | | | | |
| 500 | | | | |
| 1000 | | | | |
Attempt 2 - branchless + sqrt-free:
| N | W(n) [Flops] | T(n) [Cycles] | Performance [Flops/Cycle] | Diff vs Baseline |
| ---- | ------------ | ------------- | ------------------------- | ---------------- |
| 50 | | | | |
| 100 | | | | |
| 200 | | | | |
| 500 | | | | |
| 1000 | | | | |
### ARM (Apple)
**Machine**:
```
<paste machine info here>
```
**Frequency**: <e.g., 3.2 GHz>
Attempt 1 - branchless (arithmetic blend):
| N | W(n) [Flops] | T(n) [Cycles] | Performance [Flops/Cycle] | Diff vs Baseline |
| ---- | ------------ | ------------- | ------------------------- | ---------------- |
| 50 | | | | |
| 100 | | | | |
| 200 | | | | |
| 500 | | | | |
| 1000 | | | | |
Attempt 2 - branchless + sqrt-free:
| N | W(n) [Flops] | T(n) [Cycles] | Performance [Flops/Cycle] | Diff vs Baseline |
| ---- | ------------ | ------------- | ------------------------- | ---------------- |
| 50 | | | | |
| 100 | | | | |
| 200 | | | | |
| 500 | | | | |
| 1000 | | | | |
## Analysis
<Why it worked or did not. What surprised you. What bottleneck this hits
or moves. Mention generated assembly or a profile if relevant.>
## Takeaway
<One short paragraph that could be lifted straight into the report.>