Files
263-0007-00L-Advanced-Syste…/docs/optimization_cutoff.md
T
2026-05-21 19:16:40 +02:00

6.0 KiB

Optimization: Cutoff (close-neighbors only)

  • Author: Linus
  • Predecessor: variant_baseline.cpp
  • File(s): variants/variant_cutoff_callsite.cpp (main version), variants/variant_cutoff.cpp (first attempt, the cutoff check inside the function)
  • Status: probably positive (approx. 1.4-1.8 times faster on my laptop)

What this changes

Skip the full force computation for pedestrian pairs whose squared distance exceeds CUTOFF_SQ (CUTOFF = 5.0 m in headers/constants.hpp). At that distance the truncated exp(-r/sigma) factor is ~6e-8 per pair

Two placements of the check were tried:

  • First attempt (variant_cutoff.cpp): check at the top of pedestrian_repulsion_dt. Function call still happens for every pair, only the actuall calculation is skipped for far pairs.
  • Main version (variant_cutoff_callsite.cpp): check is before te call in run_simulation. Far pairs skip the function call entirely, and the redundant in-function check was removed.

We picked CUTOFF=12 (not 5) so that make verify passes the full 4000-step diff. At 12 m, the truncated per-pair force is around 1e-20, below the double-precision rounding threshold for the accumulator, so the FP result stays bit-identical to baseline. Trade-off: speedup drops from ~3-4x at CUTOFF=5 to 1.4-1.8x at CUTOFF=12, we kept the strict version for correctness.

For the CUTOFF=5: Default make verify fails because cutoff causes tiny FP drift, and once a pedestrian wraps around the corridor end one step earlier than the baseline, its position diff jumps by ~47 m and the comparison breaks. Workaround: verify against a short 200-step reference where no teleports have happened yet.

./main_baseline 60 200 reference/baseline_n60_i200.csv      # one-time setup
./main_cutoff_callsite 60 200 verify_cutoff_callsite_short.csv
python3 scripts/verify.py reference/baseline_n60_i200.csv verify_cutoff_callsite_short.csv 1e-3

Code (relevant snippet)

In run_simulation, the inner pair loop:

for (int j = 0; j < n_p; j++) {
  if (i == j)
    continue;

  // Cutoff at the call site 
  Vector2d r_ab_pre = p.curr_pos - pedestrians[j].curr_pos;
  if (r_ab_pre.x * r_ab_pre.x + r_ab_pre.y * r_ab_pre.y > CUTOFF_SQ)
    continue;

  f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]);
}

pedestrian_repulsion_dt no longer has its own cutoff.

Constants added to headers/constants.hpp:

const double CUTOFF = 5.0;
const double CUTOFF_SQ = CUTOFF * CUTOFF;

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 variants 3-4x faster than the baseline across N=50 to N=1000, with the callsite version about 4-5% faster than the in-function version. The speedup was roughly constant across N.

x86

Machine:

<paste machine info here>

Frequency: <e.g., 2 GHz>

Main version (variant_cutoff_callsite.cpp):

N W(n) [Flops] T(n) [Cycles] Performance [Flops/Cycle] Diff vs Baseline
50
100
200
500
1000

First attempt (variant_cutoff.cpp) for comparison:

N W(n) [Flops] T(n) [Cycles] Performance [Flops/Cycle] Diff vs Baseline Diff vs Main
50
100
200
500
1000

ARM (Apple)

Machine:

<paste machine info here>

Frequency: <e.g., 3.2 GHz>

Main version (variant_cutoff_callsite.cpp):

N W(n) [Flops] T(n) [Cycles] Performance [Flops/Cycle] Diff vs Baseline
50
100
200
500
1000

First attempt (variant_cutoff.cpp):

N W(n) [Flops] T(n) [Cycles] Performance [Flops/Cycle] Diff vs Baseline Diff vs Main
50
100
200
500
1000

Analysis

Takeaway