# 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. ```bash ./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: ```cpp 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`: ```cpp 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**: ``` ``` **Frequency**: 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**: ``` ``` **Frequency**: 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