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

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

Analysis

Takeaway