5.2 KiB
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):
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):
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 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 |