From e7af3103e8f24d63a69836025c6e903ecb10258d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5BQuy=20Anh=5D=20=C2=ABElliot=C2=BB=20Nguyen?= <23906-nguyenquy@users.noreply.gitlab.inf.ethz.ch> Date: Thu, 18 Jun 2026 11:59:50 +0200 Subject: [PATCH] Perf plot --- .gitignore | 3 + analysis/fig_exp.png | Bin 34896 -> 0 bytes analysis/fig_exp.svg | 1000 ++ analysis/fig_ladder.png | Bin 48236 -> 0 bytes analysis/fig_ladder.svg | 1440 +++ analysis/fig_milestones.png | Bin 113195 -> 0 bytes analysis/fig_milestones.svg | 2369 ++++ analysis/fig_roofline.svg | 10305 ++++++++-------- analysis/fig_speedup.svg | 9301 +++++++------- analysis/fig_speedup_all.png | Bin 389787 -> 0 bytes analysis/fig_tracks.svg | 1423 ++- analysis/make_csv.py | 162 +- analysis/memory_x86.py | 63 + analysis/plots.py | 109 +- analysis/results_x86.csv | 316 + benchmarks/.gitkeep | 0 flopcounts/x86/variant_AVX512_1.py | 100 + flopcounts/x86/variant_baseline.py | 58 + flopcounts/x86/variant_branchless.py | 27 + flopcounts/x86/variant_branchless_sqrtfree.py | 27 + flopcounts/x86/variant_cutoff.py | 27 + flopcounts/x86/variant_cutoff_callsite.py | 27 + flopcounts/x86/variant_float_baseline.py | 57 + flopcounts/x86/variant_float_inlining.py | 58 + flopcounts/x86/variant_float_inlining_SoA.py | 58 + .../x86/variant_float_inlining_SoA_AVX2.py | 41 + ...variant_float_inlining_SoA_AVX2_polyexp.py | 41 + ...t_float_inlining_SoA_AVX2_polyexp_rsqrt.py | 41 + ...inlining_SoA_AVX2_polyexp_rsqrt_loopopt.py | 41 + ...riant_float_inlining_SoA_AVX512_polyexp.py | 62 + ...inlining_SoA_AVX512_polyexp_fastinvsqrt.py | 61 + ...oat_inlining_SoA_AVX512_polyexp_moreavx.py | 62 + ...lining_SoA_AVX512_polyexp_moreavx_ilp_2.py | 62 + ...oA_AVX512_polyexp_moreavx_ilp_2_moreilp.py | 62 + ..._SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.py | 62 + ...2_polyexp_moreavx_ilp_2_rsqrt_morersqrt.py | 61 + ..._moreavx_ilp_2_rsqrt_morersqrt_improved.py | 61 + ..._ilp_2_rsqrt_morersqrt_improved_moreilp.py | 62 + ...lining_SoA_AVX512_polyexp_moreavx_ilp_4.py | 62 + .../variant_float_inlining_SoA_symmetry.py | 69 + ...iant_float_inlining_SoA_symmetry_ILP_16.py | 70 + ...riant_float_inlining_SoA_symmetry_ILP_2.py | 70 + ...riant_float_inlining_SoA_symmetry_ILP_4.py | 70 + ...riant_float_inlining_SoA_symmetry_ILP_8.py | 70 + flopcounts/x86/variant_inlining_7.py | 44 + flopcounts/x86/variant_inlining_7_SoA.py | 44 + flopcounts/x86/variant_inlining_7_SoA_AVX2.py | 41 + .../variant_inlining_7_SoA_AVX2_polyexp.py | 41 + .../x86/variant_inlining_7_symmetry_1.py | 27 + .../x86/variant_inlining_7_symmetry_2.py | 27 + .../variant_inlining_7_symmetry_2_ilp_1.py | 27 + .../variant_inlining_7_symmetry_2_ilp_2.py | 27 + flopcounts/x86/variant_inlining_8.py | 44 + ...lining_8_SoA_AVX512_polyexp_fastinvsqrt.py | 49 + ...ing_8_SoA_AVX512_polyexp_fastinvsqrt_sr.py | 49 + ...t_inlining_8_SoA_AVX512_polyexp_moreavx.py | 57 + ...ning_8_SoA_AVX512_polyexp_moreavx_ilp_2.py | 58 + flopcounts/x86/variant_lut_exp.py | 58 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 64 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 64 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 64 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 66 + .../x86/2.log | 64 + .../x86/3.log | 64 + .../x86/1.log | 66 + .../x86/2.log | 64 + .../x86/3.log | 64 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../arm/1.log | 58 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 64 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + .../x86/1.log | 38 +- .../x86/2.log | 34 +- .../x86/3.log | 66 + variants/variant_branchless_flopcount.cpp | 426 - .../variant_branchless_sqrtfree_flopcount.cpp | 428 - .../variant_cutoff_callsite_flopcount.cpp | 436 - variants/variant_cutoff_flopcount.cpp | 434 - ...iant_float_inlining_SoA_AVX2_flopcount.cpp | 449 - ...ariant_float_inlining_SoA_AVX2_polyexp.cpp | 207 +- ...float_inlining_SoA_AVX2_polyexp_expfix.cpp | 472 - ..._float_inlining_SoA_AVX2_polyexp_rsqrt.cpp | 190 +- ...inlining_SoA_AVX2_polyexp_rsqrt_expfix.cpp | 490 - ...nlining_SoA_AVX2_polyexp_rsqrt_loopopt.cpp | 180 +- ..._SoA_AVX2_polyexp_rsqrt_loopopt_expfix.cpp | 503 - ...iant_float_inlining_SoA_AVX512_polyexp.cpp | 140 +- ...nlining_SoA_AVX512_polyexp_fastinvsqrt.cpp | 144 +- ...at_inlining_SoA_AVX512_polyexp_moreavx.cpp | 155 +- ...ining_SoA_AVX512_polyexp_moreavx_ilp_2.cpp | 148 +- ...A_AVX512_polyexp_moreavx_ilp_2_moreilp.cpp | 134 +- ...SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.cpp | 137 +- ..._polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp | 235 +- ...p_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp | 632 - ...oreavx_ilp_2_rsqrt_morersqrt_improved.cpp} | 2 +- ...lp_2_rsqrt_morersqrt_improved_moreilp.cpp} | 0 ...ining_SoA_AVX512_polyexp_moreavx_ilp_4.cpp | 147 +- ...polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp} | 0 ...inlining_SoA_symmetry_ILP_16_flopcount.cpp | 1496 --- ..._inlining_SoA_symmetry_ILP_2_flopcount.cpp | 703 -- ..._inlining_SoA_symmetry_ILP_4_flopcount.cpp | 816 -- ..._inlining_SoA_symmetry_ILP_8_flopcount.cpp | 1042 -- ..._float_inlining_SoA_symmetry_flopcount.cpp | 489 - .../variant_inlining_7_SoA_AVX2_flopcount.cpp | 458 - .../variant_inlining_7_SoA_AVX2_polyexp.cpp | 233 +- ...ant_inlining_7_SoA_AVX2_polyexp_expfix.cpp | 513 - ...ariant_inlining_7_symmetry_1_flopcount.cpp | 423 - ...ariant_inlining_7_symmetry_2_flopcount.cpp | 385 - ..._inlining_7_symmetry_2_ilp_1_flopcount.cpp | 633 - ..._inlining_7_symmetry_2_ilp_2_flopcount.cpp | 753 -- ...ining_8_SoA_AVX512_polyexp_fastinvsqrt.cpp | 57 +- ...ng_8_SoA_AVX512_polyexp_fastinvsqrt_sr.cpp | 124 +- ..._inlining_8_SoA_AVX512_polyexp_moreavx.cpp | 130 +- ...ing_8_SoA_AVX512_polyexp_moreavx_ilp_2.cpp | 127 +- variants/variant_lut_exp_flopcount.cpp | 492 - 153 files changed, 22042 insertions(+), 24220 deletions(-) delete mode 100644 analysis/fig_exp.png create mode 100644 analysis/fig_exp.svg delete mode 100644 analysis/fig_ladder.png create mode 100644 analysis/fig_ladder.svg delete mode 100644 analysis/fig_milestones.png create mode 100644 analysis/fig_milestones.svg delete mode 100644 analysis/fig_speedup_all.png create mode 100644 analysis/memory_x86.py create mode 100644 analysis/results_x86.csv delete mode 100644 benchmarks/.gitkeep create mode 100644 flopcounts/x86/variant_AVX512_1.py create mode 100644 flopcounts/x86/variant_baseline.py create mode 100644 flopcounts/x86/variant_branchless.py create mode 100644 flopcounts/x86/variant_branchless_sqrtfree.py create mode 100644 flopcounts/x86/variant_cutoff.py create mode 100644 flopcounts/x86/variant_cutoff_callsite.py create mode 100644 flopcounts/x86/variant_float_baseline.py create mode 100644 flopcounts/x86/variant_float_inlining.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX2.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_symmetry.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_16.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_2.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_4.py create mode 100644 flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_8.py create mode 100644 flopcounts/x86/variant_inlining_7.py create mode 100644 flopcounts/x86/variant_inlining_7_SoA.py create mode 100644 flopcounts/x86/variant_inlining_7_SoA_AVX2.py create mode 100644 flopcounts/x86/variant_inlining_7_SoA_AVX2_polyexp.py create mode 100644 flopcounts/x86/variant_inlining_7_symmetry_1.py create mode 100644 flopcounts/x86/variant_inlining_7_symmetry_2.py create mode 100644 flopcounts/x86/variant_inlining_7_symmetry_2_ilp_1.py create mode 100644 flopcounts/x86/variant_inlining_7_symmetry_2_ilp_2.py create mode 100644 flopcounts/x86/variant_inlining_8.py create mode 100644 flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.py create mode 100644 flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.py create mode 100644 flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_moreavx.py create mode 100644 flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.py create mode 100644 flopcounts/x86/variant_lut_exp.py create mode 100644 measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/1.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/2.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/1.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/2.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/3.log create mode 100644 measurements/variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morersqrt/arm/1.log create mode 100644 measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/3.log create mode 100644 measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/3.log create mode 100644 measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/3.log create mode 100644 measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/3.log create mode 100644 measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/3.log delete mode 100644 variants/variant_branchless_flopcount.cpp delete mode 100644 variants/variant_branchless_sqrtfree_flopcount.cpp delete mode 100644 variants/variant_cutoff_callsite_flopcount.cpp delete mode 100644 variants/variant_cutoff_flopcount.cpp delete mode 100644 variants/variant_float_inlining_SoA_AVX2_flopcount.cpp delete mode 100644 variants/variant_float_inlining_SoA_AVX2_polyexp_expfix.cpp delete mode 100644 variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_expfix.cpp delete mode 100644 variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt_expfix.cpp delete mode 100644 variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp rename variants/{variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved.cpp => variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved.cpp} (99%) rename variants/{variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved_moreilp.cpp => variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp.cpp} (100%) rename variants/{variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morrsqrt.cpp => variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp} (100%) delete mode 100644 variants/variant_float_inlining_SoA_symmetry_ILP_16_flopcount.cpp delete mode 100644 variants/variant_float_inlining_SoA_symmetry_ILP_2_flopcount.cpp delete mode 100644 variants/variant_float_inlining_SoA_symmetry_ILP_4_flopcount.cpp delete mode 100644 variants/variant_float_inlining_SoA_symmetry_ILP_8_flopcount.cpp delete mode 100644 variants/variant_float_inlining_SoA_symmetry_flopcount.cpp delete mode 100644 variants/variant_inlining_7_SoA_AVX2_flopcount.cpp delete mode 100644 variants/variant_inlining_7_SoA_AVX2_polyexp_expfix.cpp delete mode 100644 variants/variant_inlining_7_symmetry_1_flopcount.cpp delete mode 100644 variants/variant_inlining_7_symmetry_2_flopcount.cpp delete mode 100644 variants/variant_inlining_7_symmetry_2_ilp_1_flopcount.cpp delete mode 100644 variants/variant_inlining_7_symmetry_2_ilp_2_flopcount.cpp delete mode 100644 variants/variant_lut_exp_flopcount.cpp diff --git a/.gitignore b/.gitignore index da05fd2..ba3b13f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ *.gif *.mp4 !reference/*.csv +!analysis/*.csv + # Benchmark outputs (keep .gitkeep, ignore results) benchmarks/* @@ -21,3 +23,4 @@ sizes .vscode .DS_Store +**__pycache__** diff --git a/analysis/fig_exp.png b/analysis/fig_exp.png deleted file mode 100644 index fb9c537c939a517133494e3eb60da546c52132ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34896 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 diff --git a/analysis/fig_exp.svg b/analysis/fig_exp.svg new file mode 100644 index 0000000..2e551ba --- /dev/null +++ b/analysis/fig_exp.svg @@ -0,0 +1,1000 @@ + + + + + + + + 2026-06-17T10:45:24.889513 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/analysis/fig_ladder.png b/analysis/fig_ladder.png deleted file mode 100644 index 49d9d194743c43527a978ba43d52c2d8e1a0c160..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48236 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 nzZsic)nYAhXlqdN=_d&?} diff --git a/analysis/fig_ladder.svg b/analysis/fig_ladder.svg new file mode 100644 index 0000000..341f161 --- /dev/null +++ b/analysis/fig_ladder.svg @@ -0,0 +1,1440 @@ + + + + + + + + 2026-06-17T10:45:24.420037 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/analysis/fig_milestones.png b/analysis/fig_milestones.png deleted file mode 100644 index 6fdff91f987e7edbc8d35c8c359f10ae5644101e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113195 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

{?t(qPYlU!I|m{hSpEjN8;m3}4k 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 X$#*_wf84qo`A>LUTtvso^s0XVqL;Y> diff --git a/analysis/fig_milestones.svg b/analysis/fig_milestones.svg new file mode 100644 index 0000000..12e234c --- /dev/null +++ b/analysis/fig_milestones.svg @@ -0,0 +1,2369 @@ + + + + + + + + 2026-06-17T10:45:25.175739 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/analysis/fig_roofline.svg b/analysis/fig_roofline.svg index f74690e..fe47367 100644 --- a/analysis/fig_roofline.svg +++ b/analysis/fig_roofline.svg @@ -6,7 +6,7 @@ - 2026-06-05T06:08:49.278850 + 2026-06-17T10:45:24.667709 image/svg+xml @@ -42,16 +42,16 @@ z +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - - + @@ -109,11 +109,11 @@ z +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -127,11 +127,11 @@ L 151.373736 50.483625 +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -145,11 +145,11 @@ L 225.582514 50.483625 +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -196,11 +196,11 @@ z +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -215,11 +215,11 @@ L 374.000069 50.483625 +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -284,11 +284,11 @@ z +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -328,11 +328,11 @@ z +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -755,16 +755,16 @@ z +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - - + @@ -778,11 +778,11 @@ L -3.5 0 +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -796,11 +796,11 @@ L 669.565625 246.474008 +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -814,11 +814,11 @@ L 669.565625 208.263914 +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -873,11 +873,11 @@ z +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -892,11 +892,11 @@ L 669.565625 131.843728 +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -945,11 +945,11 @@ z +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #ededed; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -967,22 +967,22 @@ L 195.598003 73.850165 L 198.818818 71.282163 L 669.057804 71.282163 L 669.057804 71.282163 -" clip-path="url(#pa8b7964e4d)" style="fill: none; stroke: #bbbbbb; stroke-width: 1.4; stroke-linecap: square"/> +" clip-path="url(#pf201e75209)" style="fill: none; stroke: #bbbbbb; stroke-width: 1.4; stroke-linecap: square"/> +" clip-path="url(#pf201e75209)" style="fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #aaaaaa"/> +" clip-path="url(#pf201e75209)" style="fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #aaaaaa"/> +" clip-path="url(#pf201e75209)" style="fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #aaaaaa"/> - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + @@ -2279,7 +2264,7 @@ z - - - + + - - - + + - - - + + - - - + + - - - + + - - - + + - - - + + - - - - - - - - - - - - - - - + diff --git a/analysis/fig_speedup.svg b/analysis/fig_speedup.svg index 7292749..7194cdc 100644 --- a/analysis/fig_speedup.svg +++ b/analysis/fig_speedup.svg @@ -6,7 +6,7 @@ - 2026-06-05T10:51:02.035313 + 2026-06-17T10:45:25.506374 image/svg+xml @@ -42,16 +42,16 @@ z +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - - + @@ -114,11 +114,11 @@ z +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -150,11 +150,11 @@ z +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -194,11 +194,11 @@ z +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -214,11 +214,11 @@ L 319.229339 50.413312 +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -249,11 +249,11 @@ z +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -268,11 +268,11 @@ L 484.334139 50.413312 +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -705,16 +705,16 @@ z +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - - + @@ -746,11 +746,11 @@ z +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -765,11 +765,11 @@ L 633.535625 228.753071 +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -784,11 +784,11 @@ L 633.535625 166.832829 +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -804,11 +804,11 @@ L 633.535625 119.991975 +" clip-path="url(#p83f650a52f)" style="fill: none; stroke: #e9e9e9; stroke-width: 0.8; stroke-linecap: square"/> - + @@ -846,16 +846,16 @@ L 633.535625 73.15112 - + - - - - - - - - - + + + + + + + + - + - - - - - - - - - + + + + + + + + - + - - - - - - - - - + + + + + + + + - + - - - - - - - - - + + + + + + + + - + - - - - - - - - - + + + + + + + + - + - - - - - - - - - + + + + + + + + +" clip-path="url(#p83f650a52f)" style="fill: none; stroke-dasharray: 1.3,2.145; stroke-dashoffset: 0; stroke: #999999; stroke-width: 1.3"/> - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + @@ -2133,4851 +2026,4851 @@ z - - - - - - - - - - - - - + diff --git a/analysis/fig_speedup_all.png b/analysis/fig_speedup_all.png deleted file mode 100644 index 0c656b60235bdc325c513797e4f075cac1eabb84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389787 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�$LbIwP7i{D!d^W5coIpu5?K&z{$fJeq{+tR6h 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 z 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�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%0A!&XPB<%2Y{sz z@d7r>>_Ny4{1vRsVa45#IAQQZ)COPbugdlvDENq26mHKMj$zwqsCV%Vw7ZOn{q$@S zb(8nPvT{{{mRgwy@RM8zu5sJH?gjXrVG<%-|%ZvRj5%j++ElMWEkX+Z4j5xZf? z+=Y=ous-n+MPw(sh2LzgMof)g%9Bfcf2Zmsa(+ejA>#$}J1HCe=t6*sUXYxtf=|2IVNF|`?( zD}h~~d4-J^j1a~WjsQR=58R4&3($7+Ze}2PD`s$aJ+@aULBs)%0b@y6P|+Ki$xp|6 zlgnDhUhM5AxAtAyb3!`m77TQjxEhTzAEk;zms3jb9{ZqLOs>ImGJpp`wLX#2$PAaP znP*({(FwG0OU>?^P4QZaleGMV-Ky&~XXagdwG*{I+e?ltFMm%;k|4J%pBu7H3{f%u z`tg5#0NzbG{7x_dBU-7r3H%E}YdUHOmjofKPc(jT0jP&-|dR zg~09fMHnWivQ04nn&omWhcOo$tNX4l=mA;Dqp#~+nX9rXsIB$L zdSN4*Ur-SJKS~c>6>l1JG)}jSKyq;c#_;){f(4}?J$t6&HNxx`BCMpJr(ky74Kiu& zhf~XO(MQZ*i4%y)VJH?_(qz1W8Dj~yE(HYu~sS2=klI(jP{}zbg z@56bE&ImhJPA>1exW&^K>G#-K z>K1{_b-TgN_417~;9NE5I2-MT{pgY9Hg5`*6_8Ibjut1^q#w7sJ8uknv0xzgYA1`l zyt8+cSlH&9JwE2p)DKAu;o3lL)>?_V%~Zdro2-*Dtj3l5g%OdBMU zt^~2)tI-)(Tr0G!QIRt=6;f}uZwQQgwGO8?DPW+AH~WEZ1_ncYa(pq^jHqj9v=J@+ zYdL29GY-a<`{5WriG_>i;c5rAx2n!MNriW)zvgi8{ieOA^+urgjxfG8c%iPS@Y=Zl zKFFYd_!kgCe_J^FJZj@s_hzt-b%J$+Yz{hkjW^fV8`KzgiPB$vcf)Kq72d0`7x~%~ zKQMuAUe}BLXmn%zL$(2fxux%j^#ruCvx2lGo5gN<~&hHZL50 z3^(e{eQZ@pp6}5$o>g}!ME1YWmnsf;+>H@yB#C^=1+zRX+=hYAZ<6$I9j$vcDbghR zgl?5&&P$*98c1R&K9jFO&lDD%8m)UN^pgmo`?n}X60hJ2K zYTvgGbLCR#R-e9Bos4Vh`o`R3?KM59nPbh9f{U{pD_p!lypz6_*!|xR7TQ4&c+!)= zjukXAYp0L#H!QO&w2*(TudYWvVuiC4od_+)*+&piIo}xAnDeTY&y&em<%vbMthp!V zY?*UyRe6BLrjbsMVsD0zihGU^=`KY@V@Zu9xdvc0vH3HYT6S6~#SqOVT{hMPZtkTAcf=q(T_D}t@4$f9Eh zAtDAS&7k)a$bhM;7n|dj4^+UDAHSIhURB%}ClC3&&d3MhiY*-q@sIY{cj|3<0GDOx zi;u?Vdg-*#H!2Eh<7W8U_G4C-?xEV~W@hB(xQScH{BrObaLZ{pWA8QjceB&=gHvBJ z$l|IxRbjE@pj z=yvP|mH@Sb49_SVo)dlW(XG>w=6u@*zy1MBQ-o!Zz5RRKzi$Ee2n31+3$Bkqc+YEk z@#AEN(Ka1i82FG7xbM)bX<}>>++{54)kHl=lh(S)c_+$&E@~lg7vC)vGJQl!^@%f; z7(@u6p^?RhXY=UrIarXlf#WQvFPRu>a|!j?9@_b}8s~g3oa(u5&X;8Q$XEL*INLIu z_QP4e{yY9@J%%hr~*;i#gqEA z*72>@&i;H(5&n%mT`ImGnOKZUiC^*OLx$$=alF^C)sc))>y=yRZp_zIY&gvFIWDf)EM z+uk#O0~$IW$pPx@6QkMP*-EqHlatY$L<3o1Qn?Fwy`UQrY5H~8UlGy2o&`zLcmSu3 zD_Ne$VhCOjs%$k~wZqdok#&$9CI^~a&Yav_*|7Zd%UugKU0s$PfG8Om_PcWF8yZUZ zxmuk7tQoP}qC^A)Bfye+XEq(gQ^`}M`ji#yVwwxov5-5RuA6X=>l)DKVR54+ zApt&W(seE$=>JG1O*^Brcy1}5jLeTP+!&NaX(Tp-^Mu@-Ye%^W_=k-uWaQ*CMK-@Z zxTkd{Ue+?XVXkv8e%O1wJ|Z`%+QDURt2@PwE0FvowET_5pRIpNtZMk1f9s3K$Fl2h zCpNcJk==J?5+6T_rxwbZhY-IIo>o@tm87D!w)Vu>m;~q%v%%LE4xw7LzfZSZ@j?iF z0BIU5nPLsTss9o zV)!4d?F}LDtKa*!2_{{cfN!pj?g$-flTu)PNOK7W2S`w7LsJYXAW8h<`_m3ru0*>s^y<7@tgjyGe`xP_ zo>77gAew$T#7IRq|K$OVJ7#fUZBAd`MQ%dU)}Ob+JDfm_Jp))ODk6`DG8b=zzKs<6 zeg|h6JR<1BlIK?iRKplBggUmkKWzt^$&)sMya+jyfwa8Yg6I0w)E#jJe}K!??d7zo;JqR%zc9$Hk? zO|z2>1d>-#q@=Cj$Hkj#tRAs>tc!cq}NZa_pOhxQu7 zbQe7N(R>l!_HjjCP*BfDfTgaB2r`1-&_!BC2e-6;4{|!+0>mL)y)y`y4EfJzzkq%9 zpCvxVG-Skgg^==)kG>wz2r&8uz#94?tVrK;Jw9Ol7ee5A=St1@mfxZ`f>pl#1}vDw zKE0LNyVF1k#57s=#RElHP)-`o66>V&R#MxWHSpjRLDoel0vmjXy(AX49M@-!S9Q6W z^w=?j^*jj}c`AwDqyn0R8iVSPvCDZBO9wq8W0-Av(Pt<1N-+PDYj?&g_2^a~fJozN zY+$cPWFd`a)P6jp*UYIM#-TJdW5QVVnWgL_DQCY7my$t6K*cRWm9%h4Ge8R)v#!0# z0PY%j3#N_xb4}ha$yeGcPY6knxxV8@=f%eOU@O;+^l2D>#A3q_E{BmVFqwvy{%vsP zVYWy6|0wi~@K(#oaJt)>Aiyq*fOsbxXI%vePR?y2;eCJkI^q-x*bC^-=i{dcAf@mQIXEZk`CUzYH}xJjz25M@mU2cAI0Lh= zRtHS#nwFCDa^4xJs{gw+WZ(yWJxBT z3Imz5sF_pj(<)Fr?|jZWXGo`4*T_2b64)%zR7E_hci!8XVeYIiYv02emMHq5-bp6x zQu`iRXb-ANuaXZNn~6Vk?U+f1`wE2fl8yzvGyj3oaFRQfzbf)|qGu`9>YOY?EQ5_@ zhg0eRaGa{5o5{9@7rk!+u>{CLzIjZEr)>z~F7>{4lH0eRwuZChhp@1+-Um~bN30)+ za;GF>YB~`Fq|{GU?F}_G?~IYg1nGN!P$;1w$H|@n`PC0J8ot%r04&qu1IU=Yz&ovX zufKMV{5vJh{4(hWQ~(R|AuP0DWgXxRWS!fF_rStM5eTkZ)XY$u>FGsAjjUo~e&J`- zXhy&Od^+u02V-_LYEeR6xs7_6#em&OU6#m$n=cPltM_7U6|?mFs^f1K4^N4a2_MzW zuA%9e?JOtScdWPyS-CArLywrJXXRyErL&IOTI$f?e?3RJ!S@%>vHiMSenar50yTF~ zZHz&$aRFa0QX5h;N}}uQ>F;I)!D6-z0ADkDnXD!mIlre2V8rSVd1c=!RPDs?#AQrR zRo~gEt_@5k=0V+5Ff^p;lr50Y4aM+=Z$LKI>@kX(w6*Fa*0}>pIiLvRdPj<_n()HS z4J%<0jNboPCxpg8P@0mH(?j6ExJdpJeW}jy8!V&cpMc@7xL)l7H6Ujz*9=Mt-IlJd zAGFSt zGSu+8qh!JTb&?RA4F#IxK6TQSX}(m;#2jUBI#&4sJf;-W zb}l2#6riGkGC>?Z$nl_v>aOp zG88gmTwyWE1powc;ncQ1en~@EfdJdLZmP0kV1R#wsv;Qr=`oSv$z~4}L<}1J>g;=$B`0a=)XFBvO(J zi)Wt4$#0%lw9Zr7V<+N0p0UzMVOp&R44sLE%?vKTjN%8A*%Xg4z#SXV!6WjHccQ|I zx60l?p(Is9wZBV!!k{V|>4lL)60f4OVk^tSnxmHdL)zMyiTqY9qu1Fr>Cex3#v>~4 z^j;_WU}ssF&Fj|OnoKM_Kc;dE+@AU!q~sjlKi!S$G(MhS4gU%NqbEL&vbbdRL~wbg^_u>+c0TuMk58 zU#~k+z@H!FD}jJL?d95o;SNyGG6qy+XJ?E59EGB{(JfaH9z9~ZA4UH8s51(?H?jyz zE{U0ocFR@d(oJTbym7e2X4C~f(GF7TvM}KQtNljJE5U(1!*A+!ipeitF49eVibT&7 z6yI{yKj&NgFtUfa^rfg?aYpo_W=o^)_bWT>JwK$IhtSPPA=^~oL^`Q%R@_Hj*Jk{R zAQFvO_Bn9~3!7K4Pn2xuuupuuK%Kboh2{4vVO-ob1=_=eogSeVblPzCKC*IE>2BG+ z{L3!>C5OD@YdGW!)LeOHvR_$6ZirG6Waf~@JryT3D#RLgf1v%~UecuTN5XgUbas56 zVCd=VLwKhJ$7!91n#1>}BA7A>*Lak#YAfvzm~wWVJp8BOlII83LzvkkbQtFEQu4LYlHiiw)UYC7_iyQY| z^=p}XuA0bE$cTq1Q-eQ`v7a&yr*<+S!(6i|C3%}ItU*oq2x|zt=!2bogosDiH9l$% zpR<-$XN5op9uJ22;f*ox|D0Or2-agT{gr#!0b!j$mepS-WUI}de@w`5iIn`)h>!6x zF%p1_d=pYa9D||6C_YD{IA5v)HKiswgp$1Pk+ZBc0YW<-gZhXB3MikLpJ!P*a^Yl~ z@+S`~0@*IvtJ6x|4wHrI#D@?N!Lr!#{B4ITWTvA(hK!GW&5*eEI85xQ|+Erb5 zcRmRDIkv&g%c~x7YY8|F$~2pZVEcby`GCJAV;LE#tE_wzI{()1dpL#Osnt<`7U_*v z@$H-aMb2|!$LpDumE1~q)nxoTW}H16LG0AWHxbvsbanjwz{p{9I7`Cfp|8}0onxjE zEXFCL7_64TtWcKkZYADT`oo&H%!1pNJ}&en4hQde2~+bw)x698GO7`%0S2=49!~%dAaHipf`f7EBs>rgKTsd5O#xvdgxd!5a+&lcVi&a~Sd@aN+r$w#p2?RN4LT z4%3gFZIuE7oyc;>2+Sc;((~M-BRP5k92Cy)JLpLQ`(8Lnoef>oQjqC}0b4)2Pxbxyx7jGbKI6qy0;H`ID%oUPl<6gpXU&_=( zQKOH@DJhsxKZzdnevA33NyaL3GqbXdQ-_J1)pD93_D)UJLbXRgxRHC_JsuB@@2U3I zr4dXXU5~yg461V(TwFB)*^l;6u{hWpNj1N*Sh)I^?Cbr(@5&R39{R7%QTCf@tUG!) z+^9gmdr}MFQUqdvAP|emoD?9ARk@Y3w2~|9mt-sV6K(Rvd=IKWR;oviZ!Esw=W@KG zsir_bS1-p_qwqC|8B0nnz=re9&FDx`>M&cSiOE-I<^+N-9i^gxl`B@tkfSj2)W40N zSR#)?;ng-6w~2Z@^s;Xs5jMb@18yNspy@Li@>|pcje$iweH-mF^y}$;hMU# zgIOj=&40o>sq#Xfpi9kU$bgn=(s&L2V{848t4=l(cMQX`YMtOgRa)(POqxsExI+e}00WvR*%)`y*6 z$6tNxjGsn0k(e-F8vC(GwdmZ43cY%dx2mC3#rBZ&g-y(-EYdHd*4P*l-p(&=b{-R< zf=f~J29_Q&shpn$ulA$o1S<@bJeZ!Ry%JZR5iD$D@Ix>^P|I;|i4I^maSIjnUCGQF zar;;X@EVqxjAr^smT(SVkyD;2rT*N>iF!{^;pX2kMAQ|)`_ z4Yo{MzgMg;zdbKN>zaOdnvG{@xHK-2s#|2`@{-NY{v>1w;jJNnovB{gv0mgsn(xg= z_L#f&p6=nHiTn^#HZn^^mQ4xWGKgd(rs62f4UWbjxo@kg>h@zZ)RK%G* z$PhM+@JO%>FOI;Eb|j`S`XBBG=j8z~Q>)vow1>EY`OP@U`(f}(%nb*;z$j>ZP5L$@ zflXH$fw0&O4EnLN_rzk(gca}99=x5K7aAAngCQ}pwxHs;T zC-3Y}FKM3{;>yDZ7T<_*qs)f&?;j=S;6HWXde$RDVqc=WIJ;crI;A0*YPyY|KIz8a zE%G(R)TJ1X21=t7rUs8|+&M>K1C80Y=@nepFb|;V6OFp)qbxv(^yI57(_1R9-sZ{ix6*&$65*Hz=34=Oc z&ZpI$@6_mb>`Ez5R>o6V<2p;&ItpX7$0}E^Cc-Co7PpbgQZLhhUlBj;V$NQpdrw56 zjZH$e7}mG@A{cuFdpFM7Ez11W`_zX_op%r{*X^2^#sgvE)-)4*(f zend?Mrs*AMK~F5qLR0Lap~OyyOr{X-`2OU7Uqq-+Pa68`J6~IId3r3wOLVd z5NGIIi!*F$*r&%=lCuGck+p4fIwQ1>B-_? z0JG^1;_(Jiteegg!G2+tMq$$Px) z_egp*EmU*1K9XFvN{%!9%Z^{yCcl4*a{A|(hp@^DYwc?Cm^=6EMx4@_5-Yg?U1lXR ztD<#1Zb3VH(&18s#7NjlXpoJK7ZjZW{?*N&?%KCit`4C4?H4cbpM;IX49f7Nt(@KS zPsS*F;h%i1%nc2#(E_SHo5-4gJ79UT9mRVwV^ncxZYvLN7_-XM%+^ zYmLC-v6Wd~aG!Zj^3yV^blPj--|xgP2(s7a@XYu zo~cu94TUJ`hLP+S)&f^jVu_=gCHcNS%9;$?R>wdKl9DR$E5HmRGhWHtGxQH<-E}>qO~p;KyLKo0_S`r3f)F@*mK%$iEoRXHv54W$(3-2VmsrgrL7~@1I)A-z$6V-;W!6MsjQGxA+l>?s ze{)5d$Q@CP@Z+m>O%9xqH7O=^ITrQA0o7-U6TFE7k|{ zUDv`quqe4$3y=!-+8NBVe8dba9|4h5_UVicTiSVBVlk(_{Ml*30VkEgcX(96mURqx z6!&sDg1bKE7AgC}R+&b>5!lpi6$N(14x4IQCwFr!tG#Z_Ks7hS<-SKkLJ}8Kdhr&R|DtsSO$)US z_)iO>p?%?oW+u7Zm;>UpMx-=g%0yAWH}(yjx=uHC%k3)eO`&d$^EX$9ca%QkQERr$YWjC!_ z(o{-dV_qh|>+r3bv^mKnP*d+I7JGE*Fv4&DW>=fk0goV-rZ7-&I|-CySwY$bVsTWV z#n$VBTR%R7xB@vevf%&zHI1C#A2kCl;}(Qv2GLv5^Lx1voU6XR-V%tCT_K(;NIc*i zZ2(a|^ee2ytq%~3AOf;NS?VX6x~o<}MThKZ2A^h~GP=2*Kq2e6Jm3_@Fa%fvOL89< zYlW>lG=$O-=1^6NEP1A*{UgvcKHy?|=v=)aIX>ae#MIawhSYR@*{y_L?c{sFFWboH zh<0o7OH7`KLKSyu6TNMkj6+;slyLjW+|Pon688J9xoV<2WM3mL_ZjXhMb6TGWM)C0 zq<5imc#7`bK;YXq^k(u@QN&-`DR6E3{opz*Me4}*rty-Avm92fsLeSsm~TYbtji?{ zASu`ed)X5HqbvQ@Ryi0@2voVumVb#J|4-u5vDMRa@NE3~$qfP*x1%g3vJ~XKd4bTQ zuV5II2as9uNF6pH(@h z-vh+5&|+)kfzenoF#}3TTOsk?f+Vuk`kRYV`l6IfS5M-cs0){6nC-JhxklCod9r zF8kdwcX0H}rnjG#9T1g#5mQeH(bp(VoVUW5#&OV0&5-hcPC=P?nm|me%VjpN+y3=F z+L|evvqTdxvo4>cm>l48J+xW4HAlY%U}Fs(`^lo_^AhuMY$ zGPWoZY_$5kb#*D5K=T4o%*Vhag60QM=~)fE7jRx2_C4jcwzJCt?Q$&O0pA|mayOp&w8j{|)p{Qsq^+FZ8nZHnQ?4>U`?YqI)ZKKfoD3`{Z)(Jdp3G+` zoYBgH*tN18dd>Sbr&zrn$Wyn^>!PlDpY9bU0lXdzS+l^97uQ$$wzrtSb6r%Anx7C8 ztHt{fhvC<^iE>)QTff1VPq|2FZl_ZObC>adU@4;=-PJ-0PS zA(Li~L=DN3UUTvl0`{7+Me~-HMRTQj1yv=IsaJM$#S;df3_>YXWxbv*vyOzuCXx&= zq&*Z(w?SKwuHbo@5Z+OJ=HrxuIcz;gDFOL*N|r=#j0XV6fBeDK+=#VbU4eE`KxM|V zHjrO@0HVb|KmIZqnVA8Q`#q&J?u}(^K@2>sKk%$`%XN${3@!n_5b8^iUlwO=xeE}Y z@rBNMRje~0IMU(=_~f5K>8@boYQ1nqgBA)DKWJh(A(=Wq^Qar%Za3_82x>rzydU%W z31&JE7<>IqgnQG^etg>yC#i4~J_+j;ED>?4BZA=#;s^K0S~a(D&AuPdHXYegcR-zb zShlX?2VbB+Z!_jx;>w9hDtoTwNb;#IDrr!pFxCy#xzhch}RXl|AblItv-X z! zocOUo{Qxb<=NPA^rc{iLGcldmYnKD_*_i7tQ4p(SGW}dA8Ulerj&B(oi7zcs0%g01 zkB<)$*mC;afFNA|Go*azK|$+itMOcgL;dv_dO0vGWY3CK6lBV>yNgPA)n<3IZjlZr z+~YTRHRW+{Imz{beRc@oTHsg(^>oM4X!`n!+I?x%KGAEx`2h3(+%yF(VIkZ7i*u&#D8f9fb+w%79eUQT?jx`hgR%Zz{O+4qFUqk=OVOe@H$T6A zf`b-&3&_JVw|&pUcMaI+&DUf2?YwnaJpTwZapv?jkh)vp`gm6E(xz3B6$GNV^QHA^ zP-c?3PCjeLod^Bp`8c6Q&t-&ihC+cIyxEEbT(F=~BW-#ODwKpm)12fGwYUViivsO9 zYni*L^Ea;tR|yGy-AfVNUdl3H19;@ptvdv&g+ZP@_u?mM-R;X#i`hlpPkS$ZVTVlz zw%dJD3&6Ka>{EqJZpkEO&}%vn5%Tblh?`KI@T#dA+R>j(O)CXphUZcj^9(i2R^6r89R4g?b+6iCxOL2}yr^c$eq% z=`ExJe9Adck?9EKwTO#Nf*N>=neZM>!DpKJVp*yJm)GGy=W+TzxRpwLKh#t;ZM z@Sw_d<^hVYa=QS?lf?tU4B@1#TH?S{L7}dx+3)848!jv-D~o5FS5zbyXaz@~2N@Gv zP~x4775u&6#agV@UcLfoEARPm*uU^DI zDv%(ze^em%@Dd-2erq&@e+BjnTwx+>8^0Pj& z=jWJ(1o!Y&YeY|yrz=G$TV#Z1-g~@T+D$s*IGWoQ*`CsY*?;@8w@@xc`{H(C$9ncZ#S9e)90< z!Aw0SZbqHV4&4}|rA*m)tnzS~A$PKZjPvXrTRB?EcIh#7TRO>jQPzFFx$6APYr$kg1yC=BNWF8d0Jup(eglmxHni@OyqnOT) zUm!T)x!Z`NZ@mcn9k)sKo>v+QoK+dYDgurgYh~V~wBnpY#ZqMo&I2mHOgtf z`em5+h5Gd9?LT5Y5sjtax}il?H9?{}zpKKHw0pOR5umlG9LKY?x5s6Bz@FEBtOuMD zbV`2*7sU>_87?SNRY&`j@lbQgZ@G?Bw@{7R(E#Bi7a0Liorx(cDk=h@g`K=pJ1*l0 zFcC{fivGwCPKpyJK;dcsley5&V<ec6tSk9K{!~&~*UH=)b)5tb>NHg1!e~JcmD5 z!B1)eMKmBU{2#pZat)8LG&dk}?pMIV%LF?=MES26IreBr@~}xzyZ5y^14GNB2ll!i zz``SQ;|&L7sAp}Y%9^`6K!A0KWP*zlh+*(p_HcL@1fFdHe@sj{9A(n^vmF?+ zBVu!M*0oTD-tbyeKqQ&hnBG*P$N5z|_u)_wWC!8)k|lATUVRa-4X@j+rhCTDe7y14 z@cKIpOWAYz+$$xDXO)M&Hk_&?nv6U9>WpAqCfj5Cpz)jUZEfY&?D4UXe%S|Nolm8y z0?}dv7T2S0ty8A3SgMbk>&!PM^Hr;lP|bnIu)9hx+g>ClNzau7)=kUmaD%g z{_yDF2g!&8=6%ola|4lZ=BLA%zb1A^m^hyGKf3XET3HuKSiepQ)%aotcLEPK2_kG{l(xW|5I>^sA;nm# zL<$dn4)&z=m;)fMh<^EYIEEUW^XEY~4Mu-0Asx>DUlCFdNW!zls#%8qE7^*xGZ2xG`X)?$g+H}0K-$b6OS`3<#TMKtR-WEmCIg_w1e~^1<*HAZ2 z#~3-Gm|LXI7Zi;`*}BY1&PgyI`M^HVWL(RO2Su~m;^@;8{lSTaamA7*{fWtG$xA+) z1#5XHBUYU#+pHv{wBHTZ;^$f!I!PQi%yIGFFtfU zhke1AvP0}jc1^uUy#gWGk7nJ-HooIXk$ZCe@x)Y(kIb$Ty|)7(3SHBBbIdkwzUA_< zgait5NnBV~$g`V3zVYXWOcX{6hz0iVdPs_DvF_tE)=HTn>|;UI`9d~B9yiG;z7k-N zURTeWr;1-^A`9!TmDR2Vl4D$udfVd|5)z_<^(IJ5m`47;V>FseOWCv8?gMDSPgqr< z4m|4l&Q!tgwoN5>pwG#l29W^voxR4je?_@haev_#t!`rz?(R5S`q}&pP}3<4hE}*r zk4b+mG=rTj#5x)W6)nrKn3!gphqkZpRsLCIQH9eaf>*EL-4jlGLLGH=DbyawK8k6p zrC0`7rfd;rv2I}+!kVmgjlzNc&bD@zz}`f`{0k@Pt!6tmqO|he`4L(-UR`OY-uzPb6u%nZX0c=Fwdd=&$6W-2#cFxr~NBDVXBtTw*+Hot2)frgr##&>!?iOo!2{e$h&x{V3AS*4S)Dd;Xi+l%j!m#ubjM~|Y5iG-^U*@${OtYWAX~TadXXir%9H>zK#Op> zYw~rHE-PDiKU$7EmujF0oeyqu(j1oMr>H-h=;h&DbFG^zf{{9hz`2g## z@SDwz&EI5hJz}9szixeiCyF<-eOx?=Z7fNpLv$C-9`$>xjyO8bMSGi>+o%$!??n_< zx>iw6x^X69Cfr_s>iMcV3$-y(zV7>PFxmo$`p_`lN<#FkuAdlo< z%EY|SZRUfaUwxX6Q`^r1Yg_|iU%hARxyFow`KGr@JD4@>+RBEJ3K^HobZP;@tSj&9 zmY4nig30q(ZCBxJ#cOAUBw_xU6O0e`?~sOe^RwP#`@yf|Cd$)Tlj^MxIy2{OkWyYH z`E>dPO77A8bNYDr<@bAO7P8RxCu!L+MDNh8F#+_klG>--HCa%A{6T)Eo=(&&2U{0gL1U=Tu5LP}FP)t-(+fNupeeQ+nkNZsgRl14>vvve_8%&Ga2~ow+tjq9rnRMYi-#a?R%?15gfkFafECUHYdH%I$W;EH)(Nnz7_;)ok0UUO(i{Kw?P1Mh zBQD$%s_^y3GZR1lZJ_py*t&LWQsM``zHfNQQ@ZIk(|JxBfsBx={n?-WHh{UR`Xgo8 z&xM^-Uv&3vF>Zj@%CvJq_A$Q0?3TTX;ysEpYaag4% zN)EX>tE_nQYog7MH&vZcuL!QTdbmG-W*=fvUUh>H0;i4B0Av39WSiht?|>pR!kB^+ z^z4I&?ii;CYl5x@roGA-Jq2eVA|Z)&UwMY79V+ohaO&hOG~@DBjYcV|&4IxGOTT1q z)Y7f-Xe{$vD8tW)6$xflQ@1J*%L(=U_tAL>)a(&OVziTg z$*(Tst(rgDMviyOofhuZh`*uWV1Jenm2ml^HnCOu9*FN5{p4eId~(ASJ1VSb1W~ET zv(j<4ZK0$nJMjgUAGHqlyMI1cvOj+Q|jj;Lj7dOxu)5R99n%Kk^u9A zTvU_&o#YM|+XgI%B^}ot@&K1i`wrUF6x7bXJkcnyBh@{PL3YXrvZjVb!TvpQ<5-Qr zi)7my6?oqN1lp8bF@Vp3ZiZl+e+HNd@fwr@D!ON=&uDj?B5}&z-)-a_7i-LtT+lv- zy{kQTj4Z;;F^=fgVWTQT1MP}7t*a|jNlNu2pKmhPJDa@Cqw6JsI=~b4N^hx=KLvhS z^?l$geO!c|XNblLzi@l5AF|*C%&%r+vH|~#Yuk={1VHnx>(<1rAcr9yoB{$W0rvEmVtm^c@wN_=}3K4$5YfkMKv)!#e$EP3{65WCKHLdKNPXx zJ8BTZT zY!Y2G(-NI;;1_Y3X{EgHyJ3Lwb25=TUL0-MR~_gt__g2Nes4xRn>TM@jIdB74Og?H zm&GO{M^tV*_Tj~gdi+h4xki?mp)QS9>9n@dAW;puSG*a{(S{B(**}4p)b`b7@-Af4 zQE~u#7qRGATKyWXOKF{$Ut+^$6d637cIW=_rEp%luJ*H_SPpp59(E*5oO;A&nrOt! zZ2qx)m`2>O5;0gliT;3OkaR51<4vMoH|qkq(K{_nNMHToro2x@9~rgw$)cbj|0^li z1iMZgw~VPXTZ1ng)t)WrT{lnQ_4VWE>?Ci2{h)8W5wPj<(0rv$i0)K{*()ISZ{4=O zU-SVrSS+G_jqT?Z6OK0CNOzs3xLp*haIZYS&ag*YJ)oeH)lT~!s89$Q`Xu5%KqqCa zoV+<1SL;4?!>;xG75~FqwW1F^|4$>7_3P;CP(YLG1q4uz1Im5M+p?UEgd9>+V`I8a znQ55NZ54ezfkQ^5^B~|ciwKwmU_myj4nSR5E->j9h64kcTE=BV3P^s=>Lib0g@LAj_YX_n(;bmll3)q+`I6LTA^8-@FRUA zqjA8n!>0eoV6~qn{!6Fp0`d1b8+Y4_QKf1hA@GLa#E!OBV1}0|7{Q|E$6X-0Uuer8 z7+P6*BOU$o&_H&}#W^?bm}nFDGp$(k+1%J+34EK9$?)LdR!>mpGfP+uQuD9i3FM>X zVM_J7Q+wy#UI~>sN5~5b22VFCFjDD;4S}%PJQv*LkS#q&PnE=P`K&0&-zNZnW5kEz z<(FBr9*447voGKFn-?aQjAbOu40W`+xi-tqK?j#)W-Q_o>B-1DEf(xD0>oJe7Cod? zP*0ajgoJS~)lx|y9rV2b>w0k?T%p_kgFakGwj0=2-GO1kzTW>O@zpGfGt!4+R#EX} zhhdulNas^W`4yp;mN#A}gxW=s8R1HQIp?YF2Xk_aEoCp4h?Vl5#djr4eVVl_OnO&#ai)9Vvs##SF_|B$*%Hj|8POL1wiR{l&02JBt(f>cj$ZK3fL?<{{0eYC3codD0y1}7CV=6}wW%t4NWaTqIaO0c zIG$)Rl)UPCfeM)X%U@%7u+F!HXds6r0O0jF#l+ICb4yi6;`R=K$v=Fm_@edTj67UU zHtNx%M-Sph#@GF`6nILC(0l5i`YyfK9ZG&yQ>Wd9V5tL+OTr4=QZj(mMrTk3wdErZ! zkL)9oy7nZT&Mb1Xs$5v2IraU3_IkMCrU2^AfAX~LB9r7$;u@9L>(u?u-M=)SO{H1HMtG`JFh4Jk~A-}q783CNtN5UWHXG60G- zwFu`<7&HSH0ehp6UoXljm~eP$H!&>Q^k$-p^mU`rVl~Pt>x?z6#Q|q&U&+Wlwz@-@KbO`5wJc_NzD{xuW{# z=h^0CI#Ze&N_lz~(yoHg|+!~$DuV2>Z)q>vBAkHJaHM*}SS6cY=zwW#0pH(9OM+@2} zTLSnu0G_2tZ3dDI5LwLOs4wpKqUT^9x9#%*iyK4W?g1>pln$@w`@jlc0Z5#>0pNZ9 zPUbeh04G2A_oTOSI@jKSFM#RkA#i?muCtgd|24T`xDI88M_I}}>Hs3{RrPk&bwkut0_S6Pj0jtXF zM4HDwO62)F2%HiHWHp`Ld~ProSlnEhl$m{^&w{Ng920J`p?I8;Bal%a4;;!BR=d2j z&!k@~kA{YZq{UvoC&t|cj%DVYOTpr1-GpA})0(6S?(s?J4vg-oa|{X9R^6~tO{)bV z5%+Ho5@%H$xjtW`L z+0oQYq<-&?JJL-bR1q7x*{{a0llRPE);SrPtgZ8#L3JQop(sXtY<)D{AMDZeLVs>@ z*|CNnAg+0?afcZXPaXiHQ~A9m zG!8R-UGgQBq)ZmLI;%pM(o+v8+za$?9gtae^V>XJkT)d{o%xkGA|*XBF1J|0yN_;2 zC@oLQ1Pv}bIk3boJpCZ3_ywhsThBsSXTz>%mX8t1DMV~|D3A( zVZbQOseZtY9r4kn3y;5Z8$_cwWNv8yMmJ>6w>}28ct+=d$Da+DwiH$A!W=sOti+jp z{V>tIW`u?*qgGzl24kbvmuSGBUsW~rl10rTGA0k8vZkh_r*cPVEt7SYr0W@{bu8^D zy-i8qd^XA5G*JZ~nW;1TRS{3v2y3UL!3qvbe?O`5y(a`LF3K~;0?s%KkSR4zkNcUK zsUE`Z4Rv{KkTjpfN)hJh2v) z5hTtctizF($4#l0w>@&Nj4+%rWw&-rT3`uEE?I=@r4_Lk{rqVC@7MFm7a%263()y` z0U*>iY&0a7%U7T8h}{Co($v;m~mi2#abt! zCjr3bR&f5Y(}TtjFG7__j_QPgYMtPy{-h|VX}Y7V5)lsxoV&ya7f8vkW~@=J?3Mh6 zTX}06=ET;VJ0(uqrJTyfrg?z(IXGLSHTBunmT#^4_oq+_94MFSNlYv2KmZHPM8iCY7iW3QfIZh*aFu0b$*`l4iL7{igJoRNT;2I12Q#UhDp??~q)GO_4zN1z;s%x+KbP$uN zuwv4!pKkHpvCZByp}HU;fWCw!Xq7$*y6qIbbs7FEx|73Th{(+Ki~y!!19nwa=^b7b z{k)?D40%0$TPpWWwC|Nyw+KP$cAm_-eYiFHMGkg5ztio1cy!Tj3qFTH3d;)tSwfxT zw644Rp8U>KqPCtMbGfL9^Q`>{w-fpfVdov+7i|#M2-yWVK!Xq!^wbHBQwIeMC2FCi~T~XCrqp z{iwYJn$#Wz6(4?8GWt`SvH_h~pL}(kY@Vi`vwSY`vbk+hf|q^fdHTvXlR3I#L|uBx zbI0O}j@ZpCiHXebxrPN{kc5HdDFdpp&G;}vc4ko_h0E2uCM;Cpq8Zl{? ziZB#y4tpoJn*Kl2D(|X6aZ{i*U`2k_?+c~X$k1V!! z_AvGm2OTxb=jDzSg=FPP==K>J7ToA8O%5en^G&irFmjyd1=C7PU82PI!k7w+8o`E7 z1&H`xZF}Y4o-xe#b3PN8W|YJtmw(+*9T-Zme`%um=KZAKi&WEH*3!GPW^ZQ$+v^a* zg>?vYLz#(|MT4XMEgqTDfz50xmK z_7?VL6DG3_xi6m)@_&;Ls~dlg@iuM2EZQ-*DT?4UOLj4sv){Iselc=T;4ybHvTdP{ z0ijzAgkW$Gc=JsN^b=&_>e@-sd^(0-9xKo*Ep=4qPBNsp=}7E|!5m!KXgW&8id1;? zIEw^7MF*+&?=`IHzlE7ofw*%9?9*V|f{_DgJ`+TnjUK4Z$y}j4!$@z7Z3$L6`=s1e z=^a7xM)yjhfbTP#6uzQNe>Dz(904#{g%c0B=kXuX`UcWFiA`rpjGHUF@#y-@+V^m^ zU;ruoNjOI2+N#kz-LkNqd$wp>vf)x#Qh%q!p|o&zkVA16H$0C#7Lg%t}&XZ56MoOO3 zublbytE(q`PD3vY@W$hr`mge&XIV%u{vMKU(WvkP)Uh~wSG6|%ibo_DVo7ijHLfh6 z#k-?sBgx5qH^*rFA!gW7k2`z{s6feK*s-yi@Hw-6w3U!aMyXPpvkQttrlyKIB`lFNou}wv z>xD*TL5@RUdT~86-XV}46^2|O1J^ZgySMQbEXZsC7N-1OY){eIl3++Ujm6|c^;dbd zL6#h#G6m!iG;*t*;Ff8o{-g=x0|?H(ZlJe(!T;0SLmt4_(SPbe;OX*b0W*yo__d>+KtVdHLo+c} zBfHuZ@Bz@OP!6Dq3O}hr0Z)zV_D*w_QkOx#_Q;3OKvwT9OaoYVsT!cGomL*i4nZH2 zX4&tIqdHieHgI97k zb^|_64=c6Qao>ZKZrSgD7!IIERWwAJyH`MyL|>8-XS=s(WEUMBCk){t{R5l7>VxfQ z;LnOd?6*dP%Ef|{Mq_CG3u*J2xJZ{?cPUbB*S#4@qmZ4OFqCt?Y@gO^g;5CV!?Slt za!%#!bO}L@)Pt&6=5j?fKorqAmGP$EE{Q@J=Z^$QhO*>^b^Wy`%mY{yCd-GhrTH4j zPeO})NIT$zJ~tg!3w+f%Qdddx-5oWrf>QgQnEKr&lR@vj`R`hI;640D@-H0ZlOm^i z?5u5ScGd+Aq8QnD{9^05LKtgOJqDDp`|Tog1|9nz)6c;t<~VCOw;v!5-TwV)Bu!t+ zanC@C1ihMwjh&cYl(+B=((1n(-U)wD{JsZ}9jFcEc47~0^@GwjEo<`ISc~1s4s~27 z=`167MixD&!~!9T!JlI`nt_N9x2` z7&J0I{d^9iwnxIq;@6ruJH11aMQ9*_UEe53Y=5&35(%%{y9q8@kq2hr-;&2a!NoS- z#V44g)TPv;Cr^Mc^ZvxNho`3_+0i%LT*Z9uq@KNP{LP`D9|D#u0+0KF>|`;qkx@F` zQ+4|`OaZbwU}8JB!EiyK@W}R!)O$lwGgD8G=SQd;J1GJp5!4pMAkj}fdKD4EGjCy? z&XCb`0iXW|-?f>8_PNs+GpmK*512$o6371G+z#xDxs|n|7VsVwXuHi8hdT@wRZx87ntX_lS~L z9Z$O+-@+K4AxrIZUtx>yQs3%<+wt8&*Xf(Spd0rrnxuSYV!5M7cxz@WAVW^pPW4_n zc99a`o0{eUGNdF+WlJLzuN;{&Al$qD#ZHF8E5MR|D8eW69SG87nm+W_A+P%Tu8LzQ z^8KLbYvFG`34GJ=L8>L{@<<|q!5@Yh^B%xJD6RcCqm@VC8EDzJZZY{RcVy<}ia`57 zM`di%&GKYj4?8lVk;HzxE1%5Bw5Ej3k3s;1B1brC8UQKID>bQOwR3YEiX_cnL7thq zg=r;8o7(E06$T7yu>9Zd4#9B(+kc45{F|5{0{}WP)b+Nro^LN(^N|}H8?%g5xA#o5 z?TMVI{*ojh3Z*W7O+5h@Xylrew&T{g>1kS2$%>TnqNbBRARME(uK9Bwm-n)r)x{kr z=k_elfWIwzkQlL;feiY^$>-L|jb(4JQV;Ci!Vm#BGniaT-Iww*AZIAxS1|DWhylBy zVZTMXHU9X~-?#0XFh2yy3-%_y(`BgUn|kWzK0@lq+O^kb0>eZ5BmPvylq`Xtz!36U00;U6{0K(DAkQR^A%?eg{_nd(iIF%hYOmpc>In#8B-g)5gcT(;RKLP$@mW3pL1)*ptO>(zz@ck1yiygfG zf?ofNwR=ZP$?v$clTT(IUpB+YauAL=SvC{jrTJ+?t8m2yy()n=A%NwB&(}zyRB=(i ziD^JKcDS9}vsEXV zy$z~eX#hJZRd!MtL}F)lEbW9CQEz#_h?<4*2hyB&$oEU2xEzglbJV$MS? zRlh23Qr*+nPOh>dqCWgVy2iOS?6!U>v7-H3bLTOYxE5n*_TFymdE`w(d|w)Q;S zeQaN9{C#~qHZ%VYujv^q7)-uuwhIU-vRmZ4Fr74*l$e}slaP2(8PZ{<)ayxMs>Uy0 zKQKT-IPr|R)T zC;OYnh~=8+B=sK&d+l5QL_9rGx9m-eQm`*_6RD$nUu#ar&S%yN|HbR85&GDIPo_;7 zp*JBV>2z;&V0`AmD1)#PMpd&db*hhv?y8NTivM#5Ko5R)WW#&u0)R}Xs~H;r9D}4} z*VKk&6wN*(l#b%I?Mn+{+lyBx(y&wuZM{C@C*#nX zPHpF#Pg^Dx9U;l|Tp+C-Q&uXO$4bsxkT*?oU}{7aSH21>Qbrl8X1}NHyoKd@v%*#p zGB!BgE#8(U`;l&+7IT=5@dn- zo<*Nkl9~vyHo-qSTZ4+C?0VlKi>1W%$8);>Qb1}oH`Spi2?j)R{40*H@@u{ZQ;EmWAtvt{AhId8Vvn>+ZoXHEgIg-UJ>5r2#X z)}GNSlin!ApECH6SUrn4|VI(0!M`t*{?jFgkl%GPC&eq-3US zk%8%cVrgjOCIJtRF^_{uv#O#PMf@kO%$4wGRI#OEft<9_6Z$2iA%05Do?|AO z(&O(1732>D8wODc+ec*$6NO+`GdqprM*|Fzd#_ky)yo@5`!6I_d%pl@z;#%)ZqcI_ zJmti;1o!ro$9jP&e;AgUA z-sd`IzzCe2X;xmdG>f{$w7B{i+zY8|le0^~UvPw;Lbv&VFlCRID~VmFr@knALc3HuL=%U9`rJ zQ~2Q6=%@v4hvaTx7bJX8lY1_C1XAu(>-j-_+M5@tP@cs!#W&;O(&_ABx*`9U_{Dx2 zSKbj9#Vm9uiA^rB!+W1ki>3^>bYDXP>rD(hWf9E&Q|~>9`eTIm;9d6+tMdXFX5fE6 zc6^BCRGmFPO>fizl+QY@0ui3)wg%rXM>fgn4o4N`4ZgbnAK@k5=R?2U&Rh1EXGe#h z3GO~ju6;>l8z$1e7wa{uK!dp$8M$|DEpoW+8kzwpe|i4_ejKMBgiQ6#0}Cmy!*v~T z9y~$7GXIWk(o(B0fenwLO6u+(o)k(y58X=ZXHBy-nDttUM-L@X{m}4D&d zJGA#k?v3FYr9Jzx`0nrvcW8dXLY3k!e$4%nAzo7N|Rw= z&A@Z=K91uMOVsRunw@zI=Abo>(*Cr~2=&ME;!57Kjq|68t0Xaqs?s8P{QKc-o*Z9a zDrxi}TRyGYU&9u;Fkx3uQRpKhk-4JkirxBcFzC|LKdWs3Vq;P#l=l)p5+>LbVXG-E zP*@OqS2=jiDFPNGC5T^sf~IGp`}ft84FNG0T1zKABCx+#mx5rh?DqCH+GPPU+aCNp z_^^O6$@$v5^{k8WN;;hUQGOD)5%qk}bo;T;*?Gs`!0VNuO+RdId!Z#o63DL`2XIYh zVl8@Mr+ZJ=9_{$uLg;dy^&7P_9YywjWK1c!POpfUJg)wjR93uv)(cp;rey=Mkz&24 zMIvpHS2(FFxe{XkzoN8JE7{r{@_WpN9Q`bRG4a zldtUS&07`R#Tw8?3LVSn6ChwY>gCt-zi;jaNkj92xY4uyln(W4>sSqS^}c5-=XF3z zV*rpg83j=PQw{9~W4+hmegQ|NmM@O1`_6X(Q4v;Lt1hQ=!!+nuf9IdeXv>RyhT6_f zna%a}Ts7V%`Nskwk5dWj885!yy7sXh?Ry0Q zJ^(ERpL8)M<4${}uBu;3NU@)^UuVcApXuJV#S z4J=y30(UQG#X`e<^mcb8RX-pt-3P{u6Htp|9Zv5uwQu1~torI98#H>vBzl%KQ0h^d zABf0+>rCjVObRTH$Psx+8^5;}Qt-{D*4RG2L#?oOWs7FC#wbSvn?XH#R-{nlnyjAu z)A}7#mix~<(ln8xJl3A@Vq0kK1ICSnXICB@gC|FQ=Dled(-dQ~Gzkk^$kW{*llgg8 z9GT+~=RMW9k4KSSJ}9BRz{8Z9*>pC=}apn*^8UV7W`&pDO7)UXj@Gvl(UpTL*4 z*r0ZZqjF`X-pY!e#0xRKLfHHzr*w|UV?OiVXiVh;{oUqg4;eU>rLkH`D|OVP%SR=5 z?hl${Fyq;)n)@a%CVmlX&uUIA?m7Ao)U$I9=T3c-U<2B;%V@fr^5XwrR85>7GDI2zi|^la?aZfTIwoeWo!3M z*Q-@m*l@PQKY$ubz2(JE8m}K8XvXZ3S&E@YMxm@n?^@$RC8O;^0OGo+y87AuapN1$ z0oU4%cZ?TLfbHac6`Y!6`XErjiJ0j6hS`tV4nya|WAGiSuM!xYl zX3y0$yRo1!DK+b;tOUC^?p|JrA;90((3fm{ydr{|0{o8emCiTrZkOw|ho~{BsO3L1 zrHkzMQW4}GFVcTSabvCU(NNZerwXB+3);~@X)_2JpgWAq=xHO!ZD-$abyrWzVe{EsL4mlK6C&!w+|Y~0dWhOZE!v~F^G4HhAhax0X|B!sb@QY4b)+k z*|uuG@p%4Q?`r)_&70@@`z943wI6$Moh6}$kc{{_+#cFK3%goqqalIj$^C`h|E+*_ zT2Nrh6>7Ob7}?D8ex*<#eq{efdOZ&nDuhujUWJWg%vZh#mbU$C@ii(=kG|PvJDScg z*XF*p!MQGNN}1GB6>qlc$dCAhrdbV5aa^-T_L@8z#ea`3e(x~8<3X9()L25o&{WA$ zhU09?Xn#HHa@C9{W0;DKpVD1W@T9*2H<9mOKyC%=6= z)HIO^*Uo2~o15S=9Qs)-09%Af9-*{Gfxn*6$gj|bHLY4t*(oev2agpsiNoqwZt6xq zdg{JZfC;v6U;yIa9ZYL&WBXo#K%knqP$Cj5o@Z2)13tgLJ>4G%+N8z#nvScjj^xXL zg)IxEoU23R2IJp7M$awLv@H_Llec~1ITy?9p4h*?cdj&-!O4@72Wi90$-P$3?&nAFRD4;mrk%5o(#R&w|SQ zERFebI8h@mkR6@oiuDh;7yv1eZT)WhW8weU{mwj~{Ij#ai;t&A_?9+R$Rc<#N&dNO zhF1$YHO81Jx=KDd1rwyFe!Gt)$w9L`yY_*PWkULs>OPjjJ^iGy*#NrZ!pFvbZd~tt ztkA~b@T;4gIX4Gcb7|kBgCM3}=;KiJGf03u6J?tEqerD$DXQH3zVVRh&ICbszmNK6 z9$d83W<(#`rDry#8cfUK(nVUVBxQ^8i@&Mvk>bOi%mcNO^NXilq$IYcV`;ZxOxc_V zdtUjQIo;F#HixOC0vehfOj#(VOm%{2*hW|_$~ggUWk5aIXv+h2 zDFLrv3bZS3^r7FrChp z@Dau?-2UX7OjInH8iG4|#@wJnLnaGjOK%j1TXRg>a81HE3S{vh=M~FWM6Vi3BVqc8 z2;1ZlTUq(>?{k?EVbF=QEAASTmHc!by%a1sg-@z-F)YERhwusbAOFzTABeG5=391( ze9=HS2kLbz05-DBM2(jU8mC*S66>1lpfENAKrtS09i070GRXKQp9ZhBVWkBqe+=+Q z=NwBKOl%w^wh6c&l3@Z@7B}P)9jJ%q)STF1IFLRrah+KEnWWhg&X!9Ay=*YJ(e2U zk3jwmepUDSQd#B6G&!tKWArve>ngq-*tdV#FJv3u^Nh}Up1IlcH{`H}IojVm3$x+F zqi_R{?k+Gon!4Q>$y>Xc)oc?QD*CNmFgdQ zMJ3G6R}XB)KWy0OeF(LU=pChDlR_EsyvTo}wfW`)S=~u# zLwr0~kf=dflYn#vWST0|k1;A{wNN1V%)}KYtvgU+!*oACSnd`Ss5t6r3k94*;yd)l zzFuHO`Z!YN6H_1Rz=yrYOnAvOg74BZmb>e&f6I-Dd^emj-(ywFZJ9oTI9dgoLq0v8 zcs}C+Z~xbX5uj+1KK`z^wCq{#lWfy29+ySRuy?&2i4ppjwZ@X8EcM|)#UPz-zR*7L zBunLnX|D$gC&;K2-mCV=QMiBP`z&zzEN7Gs|f>0sMANaFtQj9~!6L+!pvW5n!XMP(#i^SzrY$+nU0z|rzN{kBc| z%N|a<%ziIGtGdR*BEj>~T{SJOJDMi?Wl!BF+hiUSCQfZ0tG(82D=K)987cOq?Ng1G zt4KV?>tRqJtGC8#M*Z=f+M!KN|8rCBA}{U4`vv)~VQ z5+?wp&!b1(71fC~;l&k-Tw0%BM+CGVWbKRq_d2`(#cB0e7nT~t<>a5L%0-V1rh1n! z>$m>HVT4&JPP@$?@T#`q&m?LDW@0F%mEP1fp~kVKk5tGMd^|cz;5-|i4jEWJG3%Et zHA@-EuXx<0LM1^SS{w5@Y8;z0LC>hW74zT+55y1ta}(2A%vH#$c*`Mx(S!ZSg0pOH zG-txv{mXcDs=33Rbfjl>pIwm0Ff;of>V$S#7t1~YsHN3h0>fKkPQ)Ij$DeS! z(aYkSPr@f2vufI!V+U2@<|kx)O84$^YKy_4zWY=t zH0fhq7I1bB&t)}*lM5fAq07LuS;dU{8-onYq*V<0Ej%8lMo1ZUnin@2ElfLw0CUC< zBo+!-pUu34LRF<)pMaTgGws1)E5gyS$v2xx$I|rQ5Cs&EXV65RU~gVPdJ6oV)A~2{ z;Xj!#i8cm|M^Q0QSd0TRfye3(Bbgy?^Dd_J1r@{a zcRW7q3Yp}0nlm4GedB;Nj5RJOQX1wzqaPKu;ZQV*GK=_`h%gq`(8Jo~SpK9l%As?V zI-oG+{V=#aKFDp5+jN@wy@;(2HSIkcFAM|{;o)hkHMedb`21~z!xNI*FSlLCMLJG3 zROoRXSQEh(pq(a@r9nmmfqype5wG3vGWUCp@uGThtm9bVhI(k)ZCiab3(+`C|8-o_ z2GUF$lNWf5xc#9-%X|usiR!$;hz$a~_y4fk#}1P?8bpnwdNa9jI90k3PFi&{N#W$b znQ>dZO%{nAJbeZ^<_GIX0V`sjOTm9s4A`U?9q^43Ji3b>FVZaD3JLKbL_Q9tu#jk4 zNj76E|9b(r0jXbSLtF%ozE{SR#^HC84sf{|A8gJy93!JMCmgroqpcO{mzLnCnEO0A zdn#>>hQ5Vbiu9WZeLn}P=LpTsZX&Hdj_}NhaSB#-_Wi>;C1IM)8#GK8Z?2ZV7=fj7 zIG?`XKsGtUn`}(dXA&IlSKL*btS2hU%=|<-Rfbqbw-y#*?8COBTuPmDECGL)(3XyP z%^S7rxszr@2Jt+8&<_1OS|rgQg4Cu~H$eot?oE+F(S`}#haez4v6!f3QWI!Qv5lY6 zyzrRO$d?unoPYM?jCI7z{4*$HazWBo4im)Qe679-W}^3zE0eZVB*VPB!yRKUnq7ea z_s2(tCH*Ps+N5Dsjew=emu;Tw@3;3bJ&<&lRup5`eY3-$I0dXl1_T)7~wsiYWa6I^!__bTA`G6{G9eQuigwf5Ks9x#P-L97f$ z!&GeV3kBYI;O5Wzs64*I%jy%3u4+}hfUVbnr;rIEIOyGPl^Vc_aG}8En4BZ@@jP%lhCwA-a=5zip9t4C+H;Q5iKKU_is+dq+S$Sw1?=S z)_!H3CIqls0>wmlN4GH+l`t&$bZ~O2e8T@V1QEjdHrZqqY@V%uqS`NvfPXo;DN;;c zQ5=cHfj5bxofXqG_$oKxQ~gCXR2D`#Lq#-m=q+`&dW6;eJCA3O%FtVXmJ7j}p_#lx ztK`?~3b=B8041TJ6H804$o3o%`RK3-1dN(!m+f;dD$0|MA)2=o zKlXDNUSjrVJcLfwDTkAZ0_2~hlZIR#^1T4e5CcDrr2YMV+XP3K6Y|ghuSxjw#=om z-HknoxF|$-8e!djT`6Kq>j*fME;X}L7m$M^vG|~;R>KrC>Q>eC)x?$tJ(8AHviu*GlLl3j<=SW(cV=q9GUxnQ=QiIv11b+s`qRL&8~CM%;N*%E?3W@N z1q4slOTTWvt{w9;zm=<_@ZE>16!O~fhSj57Bb7_8p++Qkf-e4{%ly3=aL1$2F^Ft4`ibIzoS^>`NfuabD zVm@NA?6vX`u2Vak3u5hF`U=LH+ptv3*St;Se*rd@IWsz9FYLUanKOob*U@rn{wf?e zC0nnb?i6{`#7ZY$IhBKfC6G&FN?$dt9)VdzrZvg934M;MXpy#ucA6=r?df&<-e+^G)8Cr7D*PQeD)u?0Hl!Yw*^G)_t2M_sJWYYj^EAfC&hvPfIW1SuV65x7Yv|Qn zg{c=a``sG*Hr(JEl+zO^uxpBg(WOU5m;LOJOLLsgYwSXtf7=!0(+Z#sR3GKydP()d zFgVIEuh>BKq<<$Pgcj-ccJ^~G)$5t}tW28kz@hCD{QOV!s4WkxQjdURlX@Mpe)j#A z6bII&@duFu3qU@sx9tS;T@L@fxEs6|ZCMDE!JO=}_HKk6OVa>WgIPKDL4+NCtF|6L zo7tKwF91r8!E0Ve^(T#5uz8jVspfN>szUOqU;C7~Tj#&r7l6)gWC#AvE3IheYKY^j zeccdp8PL4@g~YQrHs|iJ&(RlcUdXD3;MM|X zdFtIi<#owC_%8axROwEKtl-I=nXumiETtOR{kA2nNo(=$86E~jvmTmJKzfPk=Sud4 zc5>QE3-nA?T^u!>!D-+1+V^Vy&^ zKofE_0qRUiuopfmOL}E1PM!bGVRH3Nr?r=xarq*HxFY4=rlTp6Kq^#;2?usyEmcuX z=RzjIM-#sa38`p6g%unUj(BtW0`UfD#Nx)M?>OqmzBT1~jI18LZ}R(~aYf(;Xa&?z zZjsAvjAX?k?j+MU2$gH4H>e~vE-vS1BI16UjzWbp3{u(+n|0AT-F4_mS)J0@d@4)k z0`djoQ#6EPrCZ1j!n~i(T%c~_-yHEhdFYdze8ZQmxuRW*!s4=`C?}$4gF5nI-0am< z@hc^NX%+J-wCmsT+*-AZGSsXZQ!!XUl=bVOqZWr4v_@~#-jZU+OVwCB9rBIhxF$PB z`;)j=>LR=I0YO99kk}eS^D$ZE?=$1y<96_`XJQxMStXu~TZ;uU_!26yyYs(iQOr8T5oyMX30;ZLWNY)frlanf+IQW{BaEkb@v6 zZrxMy`Ffn&Pj<&@jZwzO_V0wQv{r+Ia%jAE*J(1_8j0HI8ZL4|+BO@7jN^fmV7TtJ z=z^}ro6kCPOc;UZ7KnIWr4L)`n#f(2tuj5E+Qm?K9>)Z#AE0(wvL%nK>WycDl*_)d zeA6Qj=VmRoP1xH_GE{wwmBFkn;aB_gYqJu=8=m1aYRb>pv^We&jPN(1dIK9-5Izql2| zhZL;4uZcq(-PmW)DLjCB9;n_s*@yxiJGEK*m}iwa0vB@$2(<7de`i43=wwU@fsb*2J#UK7=ISVE**z}*_T`|Oyec%#0i z&^+=*h%I^&8L#9(bj`pR%~>m^XXwf4LDzcNy3IewY*DY-^|+|@4J1j=P{X2P@-*eM z6?(K{RZZ3K{rpXD;!L$lckHk4OF5BD*R8+{1|?H^L;+2=Ks2L`)UhZF|7x(#G0%JG;C zoJlklHy@6-cjBZQ+w~xd-ZPaWVQ(*bQn2k{^^di$(YqSURmZi2#U=W(X{SmJE{b?d zI-NMDSc15H2@2oFU-c z^>q5E@s;FBr|Pn`6rFK3e?DEemQ(`LqJ)S(zC0=T*jiyn z#E;Gw6%}RL$)DVrD7TK0H+J^!Hn72)otG&cS&DxPvY z3gBATsKJuS9 zh?)(})d;>8W@Bb1apP%2wXE21hL)s-Ruu3kg54!bY`){e-!G8WN&r9^`j7?19 zMeJ|BVCkn+#4&I#+AJF}8TNcx^6!rX$=jfM%6e9PN){rR9(;#|*Q2}-I8+&2XCRIj zHEBuF(zC}k2z9&oEp=X96z0f8Y@JtE z950M3<@}pE=;jp_U=2)&VD)={M!y)s*#O=YKU0G%KDStI0|W)1QYI-(I!6IzRYRdUb9i? z>{WBsZ{TVli(oOQ3U37lm~dh8On}(%+PEz@B{wLNEdaxd&5{>YC{;#eU@DZ&$M3Y3 z*;*>a`j2OBX#D;c1L5Be*--<0Va!-hsV=aOrqY8{`3*$cFr4JcU4 zey^bTmZWumib_Ex`Rzky;X326?g*#zahXxO%!(03y~~sj!OAeo3O#nmQne5+$l~Jp zCR4o17*saDVmp|MTkYLC*jj`op;#Fn@7mxxaqjWRGJ(eVRqPkUbXAoyYt&ACuc(mm z{hiBOP|@>8#&w>BOfFbak31k)=no0Nd_c~(-;YmO*NOxH)7-^#;22RcxmeX%yGj-U z-EP+LBgoOBs^N(J9-%4FHnd}i(Aha{|ITy}qo8n2$x<=vg^Nuvq0TYj{D*#aRj_BQ z7~7SNSxnS8@Ly%PjTiIy^9LGTNG*+V&7DF++GO-HdP4h*rHG?#rOJ}PGyN;E z^nhmVX1L>d?~nTWwmrmms~l!-dGjIk8s%imQ|(Ak2h$1$GZ z%0tt;_Jga2eyqJ!?o<&d-#cw`w`HOzePcS=LVM?h6s#3-^~0`q_puuh3xtk^X_5?~ z2672aj=uu3OO8Kx+JN(O!TK=mp>R7y5B|0tSAJ`x*@WS_AEPR>#hu`6ZN2dr@Q#alwD$dFsEKOO*&9InUB*tI6p~hY%gIv$0 z8b`91We-}Y+U=d>ies4*rc|7=LnnC{y~R`(4yrzl%_ug$xOtLP*D*6FZEW&pAg%-> zW_vr&JU!rPlxphDmg}JoAlZ-1u9_o zDrOeYjPCMq-%ZLzSJ=eoR^2IXD1Tg~^{LCsRZSFww+c-k%NCh+#} zxL{Pezxg6w)8%kL&Ap=SZ}_T+**V7t4r1c|izH1G{?As{Q??U=xp`bBW2Wmudnfg>|_ zBn{ROX;pN}rA?fqYLLvyPHDOd{nhlAFW{4=GE6t(m5ki{m{aVSg+xJe4AqnzI=B1& z3ODc9UfGo|(?6Al-~TEb&!EuuChqw_^;}ILNlZWwqOG2rw_XOX*5K0PpzrPaH0{+`L2Bf{h&Jj82 z-;^L?EB%A+uiJqg*PNO{k>kF%br-)SAUCx*Xd+QoFFe5S!S)zmpz^4V4EcAYZCNQ3KB4WnRxE99OMH~9u2@0bI~%XG0NY$d zv3Sw6pL`sDo$hP;xY+(9tIgbRCnJt=1h!CSu!k)Q|on7!qRRvY3>(MVWPtBAy|a(R7J-a z4i;$KQ{sQ@BQ8l&Dn5rSs9=V zG;F6~+{7(^Mt-7Z-1sBJ>M+{Z*yT^8b zMP**opApNNgH<;Vtf2F(#nfc|rqAwlw-?>1W3~^So49e&M)9T3>IPKo6QfB+YyMs!6XBewFJeR;YtpVt0V?TPO< z{O2E~78mB>h*xVC2cM#Cu`a*w64W$y_`-`HTbkv({Z-bhd4-qDLRBP&m{nY&P;PT1 z!^o54_b(x0F98Ov?c8sMq))lvc-*&nfMxA3fsk*b7*FCx$}gF?`t)*s`8sT}HaM;< zVOuEgW`N6esM#Q0+aG(TjrsAaG%Da|Kg@kp>+xrzM~y^4_Ltg<{h*D5l#1w3gmw0^ zMTv>Aq@6;39R@9Qu#m-1-H*OOJurxO@|GOGWdXm!kYu1!XTyfF*=1Zb1&(%)*ghKP z@hjN*U>rv_k*r2V5yNqdpG z#LP_lnR>VHMAYAv;mOc|1loo2l>MdtaA#RyBD!0DFI(Ql(*w)$eLTgg`3GA~{H0#K zDw!{}m{L!BU&KGGn5F0c`sy=FU2A8B{_^oM5EKz{#x*?xyWgj5KoFY*uX!L7`X&q3W+` zlhuRxS!!0C-&K7VOlwI__)+geP#7`kc~r1Y35t>tMWw&*_|##=`oZtUNo3P{Z0>8R zTS~N3jh@o08107f`86hk>ld42^6it?UjOHY6Ij5&5|khHt*J z9?fxOkfs(Wm}yr2G-Png-Nj^^6W3@GNB!}}lsP>cpPH`SnlQ{Fm*D$-SKvehH=++9 zVlkTucOPa4k0>@W+iSg7&aGO=eLKM6t3&}!`KitES_L&WmrEEG(q_XXq4;#gb(Q39 z{|om-$oQ|(fy&w>QmthDUA^jR=`07(3-99@cW0ZgkFh%dy)q=zTtM8HEFTNtyrhE| zmW;}pWV%y}aHivswvnS8gu7MF)uEl5;lLt3QAe_R>+k503{*kAe1gr6nq*_+C`N?9=yVVwDpwy#}#7Hahc9Yy_*gHTy^*4$Cu zOI^6P@kIdzVx&FMcWtN%b8Upvm|~Tr0k`R_C3zYE4%|FzQ?Ne0{lk}*HapW*@t2u` za``WMXd1?z@!|Fxxm9jGCXF>fz|HmSnb^jJXO*V{Ky;^Nj*5I`x+oV*L7;n!>g?!8 z)1HXzisqLzSn$m_TNzCBcRS897X&<^I#Yhp>Op0l!IR+YY6P<~LD6dzeKIdJIE7m} zf8jm6=^6BSJd(u$frJ1-?^cRrhQA;dfBf_FYy&%qEoH;2EVJp^5>D0|mCfU^tG;L= z;B>6z$>e!D<+fL~Dmw4BXyLW#5lIg0+d%ID6B)bfUx2pwkibu|OLxufbdu zHvD0UxiSo<9l;IK>qYj`5<=gy03*QAY+1|7LCAz}?1+O?n!F7-tuzwfX)Q|LTar5F zYIYPGvjt$#W2B@$lIhY0y^FxCh9G_&R-)1M&}Xlc`i`H5}o9ZGa4hE`K~ zUYCB#$8qvzgh%4F-LKSj08;W6>hNpC;lkh#ss_*}KwVw#syxz0b&_qScjQVBU3zAZ ztl+53O;0Twtnwb z^>a*xAiF7DBMy`^cE(;*BWd;N$Eigsr?ALK3EHI=qX%HiFzI=!S-G15BwE1Of8Mt3 zaqi-nHA2zu#*?C-Rj6BjKEy?F{X!!X{UjezBz~HjZJd5|!AB_EmJ0-yu9@;}g)kUS zd^?+hN|958rd|x2RdS~>W!MM?7e)a+wQTA7Ic%}qD*Vf*2_@=XOYVgyB;MsR=auBn zAswhik5#C2SFN%(&)#ryFEX^c+oBVrz{xuE^rd3396SG5z6Uq8>9JfF9ZL}p$<+U|t3;9X^5aPSXJKZpyLbKqn!GPyZUtJ!fA7kgB-dNvV! z#PfC&shG|7Fh-4!;Rk6^cybu~kf~Uf0b}}SuDe4)wOD@vB7?)Uj&51nc9{8) z=5tJTOKC@-&VNwbxfZ8Kzu>HVcWv12NUgm0B{wx45k%&_CgMDt@FY`xS59hyELwv_ zSgLb}TyfjoEX&^{WRBNHg-w#*uyy1MOi4j%2tE)APC}-%?Iq>l3zE0Or$L~S{@Eip zeV>u~JgE^SryMCPwPE+a(jt~5fZp#KMGq*4?2_-vj}_~rAQla5m|#v;#*Waib>=mk z7j2SY@vz4)!_AO$YF-la=BH=z&@$<|krf7@22g${c|sVbZJ*m=Vd(6AW4z(gc193H z671t^FNx}vN5Emi8f${y{*NK@wlKfkCxt~;tK_pix^?3lE05d7-_yEG&gmET`i4h;svt2--`6)*5e-_cd4R1FFVI1JQ*qSE8$D%0ar2l}iHmc7KK{uryCRiPUx7R%=?MHxpHt!9?8 zSn^s?`A%QgOxDOZ6J!^$b&@Zhav;vbE+a{F{j%mDi462{W&;#hV-&1ku<7oknQpw_ z8Gzf9JpMbf??nKjJ`~xy(w+d`!TzGIbA-4dTOS}y#>T-RlBe<5tQKIrET;jYSF3kI z1fb7PUIJ5jkTOB?knFT9r)Atn$(}wf7qWkfC`3L*l7H&od&T!xbQ8a^l6531;|EuT zqHJw^y~({r(Vxn35e0|iCtA${lCh0XPkg#RzGI5#a^DTqq*u9lY1I&LvZ7eY9AT0F zWyy^MP|nRV5rIepN6c`Zce4X32Jz8Dch zlrg;M_+Y%C-JYsnRLrp~4Fm!w^eW^VpgZ{}6=iM%<@JCtF*mY6A~?=o^lnVrr`K(N zw%N)s9N5KGq3hxcZ`=mxL8etMKi9!(JBd8unuCcsEJGzcdMZjVk`;@pbI((YN}1Zz z@5A~KYLYHtVc+_U4ZSaRq)_#DM{v6%#|%&2Y?z>W#FT6=HJw0gaMGaOZ6?%0OnWCC zF^UuVl3m~l_Y-s7g$n)4C{Y?XeHYkpCKgd_Ui!;TAT=5Srd6^7=Xd%(A2&2~yA2$_ zP=<3F?z^Gw&v}7^07l11AA6!@ohmd@cNb_M|E+fI^IhWK2ZEgvuF?Z*0T=yFt8#T! z)tFmW<;}5AU)caL>idmH4OgfRX6ZP3=6T!mtYuuQP-fdL6w~z{{J-(N- zBTfVw287Un{YcBq_e0*9$C;CiGk`pmymt$7=}WN`csN@aj){lh#960 ze4ggV8x~H1vEHH&P)e+6rJt5ob-DJq@>Tv!%=`Yu{n{;oJBqlv2&p`pW-Xg^-*GQi zV*<`3ZAbjqRh0#kDh2^AP>;sCwn5Pn(&o)e1U&7f7_cN<=>Z1ZqDg>-{EdOe4^%iO zEm4D>Ye_r=h2u4R`H=){Au}tup^32$z9z{z_wsso{B4_N4R`QQR)}q75K7(0_4v~r z{tduIV4_FCdeyzODI!IlgYa{@%P;z2Cmnbbo+LZH)PWj;%U`spNR&|TP5EL~aCb4T z3L|Iy{OLS|><)eYvMAU0YMe1z>-r3xJ zU|H~)0LM`-FltrfWboa7so&%;egzkVEb=xUgUNR9?6e6r#`4rIgE5S>v>Bm{{)zuk z>i~;!*`~i2TUU8bBIBEY_vHP~$m*s!1ScDHMCT#bjmxdSm&FcSJHI1df_k*JS8f45 z{O=_jjkxI{HofSwXUkleMLQ8$DUBegpE#?MxxiCYEQcDyJg=mIAbC$>NLAt_cD2Hs zTx5b;{nrw6#1xG3W?J^|SwC=GxU#2B%w)`p*^$7?D);@^NL1&e%a!cM0JTaLF^It9 zAU#18ucb%u{#|CjG~scuAOHp1`8gGmr$XMZ2@F%eGWLONwqkTo=UlaqRzg00bJGj( z>UfN`1?vZR=`Qry(L_gg9(R8w30c&R0Jdu^z=@^`LgGh?#de4^OQN+ zH_mLiL|?MMLd#`Pg5Q&dw#TLSz8dbHeWu5#&}QdpqPS@wh&sbSb!+H||lht734QjLbamLs$JI_`C=l#Y?TFD9V7_|4ob_ zrDicgx6C?s>UiDId{(GOIYLFQlH7;Gd7mG{fu=Q(=q}0jxF!-}SlJn$6rR|VJ*R$~ z^ln%EyIz`wH6G=9fD4t|KQW=??A1cIw9!07F|Y(jKI%3*^;2L$fnzRC$C~h0U=cE; zZPI705S*}6RsWaP&>XOqPZ)Ie-=nJq+-+BiFIS7L7X3jWmMb_F1if`0Wvlv!f(GSh zS|o4nbE1r86vG&xqh;-?oDI{yd7#C*zJ-eqoy*m=TCx5uBR%m^itQ9cW!uw8)ZYXt z(=-jeT1&~dG3w6ya965^p*+!Rl#wP25RY1P0@!D0TTB-a9_;|pbmY;_o|=ZaJ(eX7 zO!-;D)XzO%&umSdMT)(3NSDR0B10FUs_93_KhAX!a>L@h@yNSHG<)QwQnC*~ACG}I z(CEq(ZnD(#5`Z4MOkjs8mp=uOo`SJa5x#<1s6ID{K1@*qy|eaPxg}%k zg^i4yfItp7L!esiEJI*;>A67ydMVo~KPsu5T0wD7A=o-{9r)62#IM3|-`?oz%!^2k zi)MYZWs)|BfE#XdHb%E@-lK`KpqtDsumFz!YNFaX`)X@0>pl6CrWyc9xR(3V0UDz950dT_4JA(l!av5pPey|31B9mBQ61vxYGFWtqL|)Mo=!ua zY1-4+u*t3GTlFZiN5YTOECG{ujKog3YQM*rk&aq&hYNE?BlXU}E5`fHVMl+jGad(a z$QUY|Eb<0mfXfm|N=*D@-gRU@kRiL@#aT>s(X&wK?3bSvBfi(ezrU)}RJ+>#>1Y;+ zAxiL0wUhifRC)X}{^Qo!gQjCy5vz|MKMoHJyi7233m3CG7vm#wkUVa_3pm^}WPH4N zx65ySGJFNbXzsY)~5C~^VpKW73IefR54bn5RFj_ol*E27kud`TP+Ud2Kw-Bbyw9skD(S95Fevc$j_A~a$6@~Y(xdf znYhTy2^6m+1&vNmOK(gMF|n%*4%PP#o4t*QYrCI%Y4PEFM|k|(%a37gwzf%GZF{-f z!XqRw`&lYChLNwG)@=aUMuMb*7CDqPs1ZLX{JIyO{sq|RP4PG$R1glKBR9YqJA?mz z2==%i_WEQ)b`Ij;T#xk`(H^@t9RRXI{Ws+%!1(?z;by>DbpYpq-xcF}#pLgSNyKz? z_aeuq5aC{1fMx{-Hvs(ZY#H!Nz@YSI1K`i`LyNfVOIx_AuOH0i^tfFThQ(sRUofu; zB7&jR*??~IGURZ5Tv`%PQp=j1wetGE1Q;w&u%5qck=Hx`-vzyP$+Sg=rkS)l?0?g3 ze$L`87d}Wms=qSnU|rF;6X9})1b!+i)5bMXDpPGg##19HH0knj{d&N_(l7hs`1uon z@X~R){JPZX&kw9Sz^_qO02Dx9_WVoxE6rLFlk;lIH=vBF=*|z#pTktS!d$T|V*0o_DmRr2n0h#InzAN6E6SfXO%q>)y`gwyRZmOoDBDtjM17d;-g# z`7K#FUdmJ;ztmq+gp|}em!&;z=12YhEusMVduo~t<=ZxrY=jKcUZ;Bx;*v=+^#8T2 zuSEBJl-l=q>p`N=igI%5odYGfYGn|T+~@W2=R5rLsoDz94QQ9s$=;E0?|Sx^4O7_d zK{X=k6c(W8bwnU#BL9u;x_St@nkR2r^qlr#WT;A~4I)S|D}Q}q(ObmPQ05F1GW64j z^@fEeqJ6eY$CJk{{ZN22&Ko`G%RRhGrpE6YY^|NU9m(23!N7ZO0IC3#-}!&Sx7?vJ z>I@g{98Jg(MOZ+7dfrgFmX{k25P1U}l4QE3Hy}`yDoZ>h%W*NQAY%@lG_55!*;P0; z6|KGxAF5ZbZkbTSpT_OUg-DXZ^ts|n^haK&2dQ9U^1G4RN>0-y*v;L#cG#yKPCru~ zx7mcEOiC@p-hqX!y^y%~9=%2_ptd|SaudbwpYQc)=;$Q8rq7PDHGI=6u%D~yhX6E^ zmr6`5b>aa@SjsYE{}W5{a$PlkR^!sItrI0L+6)6O4&d`%K=ktvkk2hcW^Zd7>v7Jn zd(S*PH_@~TD6nJGrq8C2me3amCVwl}$JUIGL{G1v^}ok4e-jPbI}J^azdcwEl{|mq zw~I>a;r?6KboM;9TUYz6$65kqz!1?Y6LeY~a3)l{nwKvVG}m1pz?FzuyBD#+7z4;Z zCreUf#i@Iay^@K5B(5R_ivj068z~w|L9Tc$IAdqP-O{jjc6&h9mg~gTx?|qj>8Vd4 zK%#bWaY1#9@J@AWUm6L|mq3ZL@je7s>vrj!-|Obcn0dq8iWFPn_?Td&C7`10<^J=B zCVSWw@`!b2r-_7`N3UXiZ4G51+~9Qsl=u!HJ_1!M#pR8P{7NOcz1qPUe8;v>`KwbI z!H6;!7K$WDF)mZ~W(}HO`4VUEK`~CPz_xc-@V&Z_0JL{IyH}w*8QlRib0N8^30|Xg z3~Ibb8eH_nr`CYpMwDh1^}5ac?GU&a+>9CO^CjDg+hPRmsdOxFS9J;JKQ}S|T4LB% zW&;Qv2rD$)xx5gkE%Djg%QT4SF0Z1$!N~icxr*nz6K`81`4T`wu-h1kq2UWfKDx-D)Q2WJRnoi?a;s6sezFKnz{WB8XJr`o>b61)G|yVrwti z$YWjIE2`(#pf$cCCy}xhRneBin-dQ5O7j+7RuL)+ib_mul(d=Ozq2uWfb~lkpfJAt z2VB-RCb#o%066#kekhGfLL?rCbH!Qnh!h$XeGGzu@UH~1f-JVoh+)1Y3$Dy%t;8iQ zHXoYSk`J1E2R`GXOnN_9;nWTLmT^tH3MOLCRNrnhJu|Q+P@do!2~4Z29d*EL7=cJ0 zQAY?LMC6l;lp;7E>ut{=6!{HNCzZv1DnSqxzWF$Qv?WaPVm~D)D|GQ0jGe#SYsZYd z=L_{<4^XW#5g}x`QpYq3q>#=LuW5R?vTVmBUFU+*sgbQb{sG!tS`kF-OTiBi0U||@ ziv2e1m5S!6_|Xx%A$2SGWDA;X>`voO>4=Wk=*O!<0m7}--SJ=Q#_szuGXQ~hSvBAk zC3bnPyE1DTL9K=65Z42=jcxy1ZKK!Bmk=mzRM-5EzdyjSVe+Bp&Y!YXB`+#E5`bYE zF+i;Yfjq7=vRntfI2n_tmiBURFmcvx7toY0B{v$eGJauB9Lx#Lw-h6f+nHPDY`b_I zp?Qqq6na;0G`wPqvQq)l z6-{AW7~aUq?X+_=kTn-r$0V0Xa@L?7-B+9(wJ;8B#(+)#r${T93VdYR)EOXwven?)5}Lzk*>Gf`X#XY;J+Ws4=hwTD2fp4-i6N+ z(*OkQt1ifnpD7cA8j8z;ato+T6k#2Bjv}WYfH=^#UBf&}YZMW;^oTNaGIAbjtIgO@ zw0P0U8}S=%0%6j$K|HVrKm>F*Q~Jv9=8xd$QV>;4yAnRZHBa|h1)|q%tzzRg1Q%{4rbvEU>+j-CZcM>LuI*C!ePFa@f*d{GA}myN6+KAQu=F)Nfh+x& z)UPM_O6pfpS*evLk-%#LGcJaU2mJ4~ejIs!EnYhT1|n{eRSxP4b9g+9_j*lId<>`3 z8_CmwnAMUopiKug|ASzH0!7G_jDW6}#@aLWwQhnuEAy-}FVxva)$OEOJVk zmx_sbM}kXG%gODUettd;_!)>pg+~%QPy-8@ozW5GgtJ9hy=s5A^7Z#?+Q?`)6J2)c zPr|4+78@{mmU5MkZ3TRA{7zI`o8=>hzJPZ%RN6&-Z|p`=-Xpy>)plL@j_DtR-^(~d* zDe`UX23#oD;0%f0NL1C8V+(Q^IFl^Nv%Y8}i)Ywuz`vq|rse+vvoGW7&jc~;Pmu#- zO_v_INU`}yew(Xq0GNd8uFqG9*yx#- z-r=9Mkg+J2KzVs`Kpr1pG!zt9Nsl`D=BsMlw32-Yq7q!W*?=A9Q{~VE%Y0AKkZpyu zNNL}U&j`o&AO<@fbTGNB1RRVsEN2GUYhj*D**#@rj zvQ}GtfK&ZtMuJl^xH|Je%Wg(O(lAl3{SU3@94kh7>x(XGT`L)JEwTy#d!H;D>|MH! zqpXi(;?<%yZ;mi|!_D=Uyp>Hl+yQMi4sGSPMU)056?$y2a9Y$(7=q+znnFrD7Cpao zaDsLGB_uu8WO(PRmMp~%;=V{kydF{z{&TuMFmOI2ZMwO)(EGMuDv^DY&Y#nO^*QjT;%(eEfJzPQ_I<{a>?EFWsJt$ z7ppiNq_JUKp4a?`=o%MB0*I`V9g{p=&V+~qtKrQ^0@stqHKQ$#Pu;>BD*2Ns;q#-* zOccZz7~st%renKG^GLHX%*>b!lxXlz*$gZi)6}^)Ubc?iiZn}<==pHsRjpwDhQQGW zG+CPXQ|Fa88#A%t^N=3G6o2N8o(ac${lFY_p<-70I%wa89v7y&$)Z1AZ!!20)oBH= zlNT(Z*swNuqVgN5ByeFVm#BuzZLpHFSTt;3+Z`J?lu*0|4Yx_k0t%JU@)rQVs`8ud zTQyaU&`7(y`U>}obTCCy&FC98F}c#=p0R+Pt)TSnoI81ZY>~_+AKmf2yavkjbb@&! z*xnEi<1)ol3`lOJ=`|SkKNVBR!;4zQN3iZtC#7yr(kaS6`uZFGmhTL@!J`G@<|| zgfrq%6q$;1uVq^?9f(-up>9}@`vm0`=K0I-cEs6YqmgrQfE&yVl%6e3cvmqndA68J`gzp!`%IIA> zm_3FJs=>(;(@JE%+o~-ENM65w9kO^TEzV_iWpIYS0@Eab*;QBBl@%#%I1rNT0~n4f z{oRxND}Cia1yn9hUEyl9h6G(l9soe&R?}c~LeXAo9`AROPW7KOX}973Cz`aBvQ{a~ z%vRq>j6cvUQW5mtX=>Z~mn3cWPv1&*8Vrc0`XKhl%f>}~DWP$n8T04Q4Up;P2UW{| z7*!x$)9p}N?jkE1x{1;Isfm%%YQ;P|%H;hwZ1_aEcI7KwXenb7UfsQ4u;x;o3k;U_ z%VDZ#6^*2%U!~p;*$F?3Y)348V`?#Db^Q=t@=_Skx^iVNj+T}PJ`1Ld7->Fhng2b4)X?_lR})^`_FA5% zr`-rPFKBdhTzP|2TP>8g$*D$kt5k%Yo){5^*YsMFg)$3po2=xj!jytJ&llCM>ox+M zq0XEeFo_`x{)lP?!pC%$9vKO+d;B=>m#cPEx!tmY*#;OLX5Qo$_HOUgbwz}ZqROtK z@H*IA0r4RFtt&D?Hs4A{U4=Y!LU>@g=8UTJP-ubG)s2=AM{`MiNm)3v-;~{ z@&8=?WqWqGv42?IgoQodfL@^u%Rr7^=8MNKBrkVj-sJB6zx7`!ZLP?;j*hTlF}wdy z^%p{0_Q!qy?T?Ic%AW7~-c3sbA>_6r)qpLaKWcgxT^*);m~V1{Fg~gsQ-{*VCkX}`jFIm>8Qas&#*T)Uc=T|K@&&Ug2IZjmzX9WvXot?=W0$L9{K8wA{t&%=LI z-lzWzi)?7&rA1G0X|Eo7<|$aq5fqM$464m^-7w{8{_6>S;^;lJFtK-YZPo(HntC5$ z)7Ax}9-_cl7zI_#H+K<&(pt>#_3y`HMMPATc=;

zL_2O^?@Vm_H1DyN7qbsuug+G44$V_sKXuuH=ZQ#Uy z%CGzT%2dI2tjGwwvEyn`>%Njg-IMCG%1+ZN4B!&kOo^oho$aTi2F~$+7?h&V>-8Ia z1pcvmzJk_~Ha9_ub-HE2CT|Hy?rlC_G70QCKK@dln{(44H6}WG2`nXW{Sk<->B>T! zn(X+f(IuQI6P&cTSY97Ut>Q|owBa&tnCF6*w-(#j@fOn(-k6~yfS)it>FY2A*8YBdf?94n*d?&AH zDOPaXTG1$5U5$?SOAlPX)_5dd>s#5a2&>cEs+M`i+G8GGdwX07+phBM;M&`LuN7_L zQ^9~A@{s1UC{BR~tFIQd^IuMO^+tSItcwj@Ud= z+|{(BEzkQMug1)S?ZClAnmH2|EE9#C+J`VOQzM&^=o_|5N`D5?@e0>8mU?rIgB47o zv^Q>Xga-VXtlQFYA4|mo_TVxDjy#60f)~rHyz?a=Lf*yn*(#-c^;jLwt;OnKc^`?o z%SSWnXu6f@JOg~zv$S`H(k`KXfD38inFLUFun`gir@3?@16ZZ>=qlnS zcmdnML4;~_bi0(IKsGzM3PvYkZ2>x6#W161<;?~eebwI1QCzV9yx|1cGzRvyQm3%P zjXp*^3XY{Zv4bg|%<)XgFb#-Us_H9Fc@;YLq!e^?a5j&J2;!C=3{`=1oF7&)_~EOS z$r4E8lwI)+&=skJFk?}Oh-uQ>A0*_RFMB9!Au2aulaevNdK!n`j!&Db z+Z<}QeG^w`F^sI=jgM}#Yacv_Ia#x{HOcWL)L=&S_7AmRN&&aBLX+sW=6KWDiOAHZ zONudO$|U>1l#qGK8Utq` z&t5tti>=-LmMF2K6yLnw+H{76=hNVjrHMI~x_F1o5ux3_n&Epy>^O%L*LhgshsNe=Vq+DZ`&1{D2ZiAOZkQg8gjXl{itsyq;{Xj>BpCGU^Zgi;%|51*((q}~vRjsOV) z2zPC^3;{ZxGkW7&w)}U1yZ}A{#W^Feo3(rU5&%y@ zpwByOsV)CyDgTD5C_-Kp*=EQVhvH5#X*WFHK#EqvdxJxXX|u3D}!>GZw39GF{A^m~}=`$oS~RRl3Rz zWd&28W+k)&F4L1fAqxS#CLN6R-FiRc%8#k%%R4Q7YAng@HHCe%>D8(J&m3pUq69sP zTh3lxy}dwh&_Cl&;OW=;Ty`5bB8!yKs?m~Y$zz7_D1dIS{(>%&EaAemo+{lUkt~uP z{Tkmpl>r#R5B9|km-9H^94|cPzY}=E^1|I`!a`Gu1J%`Ottgemk`eVl-c}4NA7phd zGppvVDaZViOAw$KSpvBS<|kZgi7EKowmGynK*J-h2}e&eDe@=lUnW_P)-o#ydP52vu`>yM-po2IiYjl z@Y%k~A(KIu+=Yyc=zjwUEM?4}BsmNjDsVic9lQ4rNG z9fwX69${YG_#Kz#T6;Ap}O8pv+|T#A0;M*v#7mNIy@_EB{RTlaPl z^(vM7>~e5xfAG1WvD8Gh;S-O6@caJz6uEP6Z z!Kdsg2*7Vqn7xn}!Zu&?a$YoaBwf+khzPjj-YbfNs5uN@k1yHAsuQm$%fb6tCL9{u+$k{71Eg(V+%oLnKz?=`)hp~Jcj{i?f?}; z;En76TZlGpZcRqaf?}X;vCLR;Xf&$*BRHy;imNW1@Il(+ysrx7_r{){<+k2ue-Kkr|5@gN`jvh%aQ7XM=Ow~w;l2oOl9hQAniqG!ULf(8{(V5R*eV!0Pc_%*w@X7zw?y*VrAbo4j`6n`8+zK zi-6f3d6`^jwo2R56}=aZfs)FE>}n;8Mct8K9d{}4y^v?WqWB)hJYthS zSlRVluor5`3tZa5d%n$I2PitqivrP0|2e-X%c5WK)B4lnl9t7=7vkG2F7>%8P0)0Q2A@j#0K878$AHnTMl__(!?;{Xs3aMscQ47olKOL;hqoR_ zbz!YhbDw~TYBl4Hm-#)H)VFjwQsmS1m#gf9ua)rV_pEYe_g_Acl<7>eA@>< ze8?FWJolZi$iM!r&NJ#hs{7U)#otxQyFQ%X;~4rmy?mVnrs+MR%?i+<#TER=hH)0q zr-Z@ja}PU@!!X{hv?u;pAGqf&`W#u1n)BG9f&3$PA#Sag0>mSWTlU}J9J51d(OP} z8_8<8`(T_3A~L4st$I4UdPEu`MkT8SsogEx+eJ7P&Al%YX!e`eB0$tykro~wXPU2K z_+$vQp78Gar1hx~iMz}PzyfvKYH_T{*frWDS7GIi7mB9gjv_srwZZ+1_tmT|PV0o0 z231}qV|Wbt68w!3wimBYD~~hlTG&}?lHoA@r!$fnHmXM536lV_w6VQk-rfw2_UOff~Lz{!KYI?t0q7Dapq_F2OHyLb%T<2=h>oqyz&RjJiE zYnhrY@LXEcwmJ@Ua>^P8mQ*}<76=JFs2sntw)w>(z}$gd_QMqKK{a!PY&qZ&&w$Jc zKVYO9gu`Hj-h5AfV&r@sPzwVw7|f)5sMw+F;PB51m;4`y8xL&K=pQ=E(8V(xYD2tt zfqo|JvfI}z|8)~Npc3{VK9Zxz6_IllmTnbAUebcoiRit>G<69ktk+z}3dOS{qL@Fy zj?jwAsn6Q+#d2BV@?2)*SCK%h@t)U3!2{mD(mVm^^j2ux?^^y?LQm0~AGe5_Z2Mtq zplE_I>V;o~>uQ&_=MX5{IA1*og6t&QkM6Pht!PoODZGbD!)W;~0a0x%Do}uqEK3Db zD2mEkKx=XMMhsE}q@`h7&&J98CLat&fRxE z0A$&e9vYY5l5^IRo0a0?uGi7h5%mwmQ2S{}&#~sVe+JX~ug*R87`Z?N1nqsSGdsH$ z#)D-1Qp@@6GtN7qb(}#`5AqeFnrWu&!xmP*p>ho$R_EmO+oEe2j_a-ma*RKBx3YF6ttUlCiV!$Mb zpMPF8YcI>GJEAtYcN;?BJC57kVchZm!3h;AfWq$G6R&VtqPZwT*>bpPd!#XUSPjm% zZeo7`nkss9v-+(oFTZsW1;QkW0%6>REkJJU|5Kk170MqJ!#tiVq(pJOS$sD>{VJ2w+*zmRX&V@%nNtn5KyR>opsdVHUNjn zq@%FqXTTz81g@~@mBV6Q3dS}bnTfAj{^m^K%HAB@k7)1aQ1o}ps$ z>G+~UhG~6r-yU=v=#3(hL>Fl|g{03L@8%!v@`OeI+1y7u!+*H%OrfP(j3ip)Tv#mW% z>u|8qFCQ{Ngin@zIpjh)>FH8^EA567!FMx^-qQA;E!ymLC+k)!4DC?ucKCX_x9NTxp3VkC{gU1p53X}n?;G#+8@S^>-GQ% zfw7@X1u4{R$H7R7ZYi5icsMoewmE1f2ETwusa4HQSt+4S{=j#-Ow)GcK0=3dCD=DE?sB^g6WZkNe~60O5x=C;jTsV$RT&5eJiKQUG#;xw=v1_fvf2Z)3oUQ&jWyLv4}mfsKug zsky5=qJ@U_-%!=4B8GjyM2^7o=85q$s-pI)H*#$oC%(M%6|aZhwyyFOWEB%8rfI}n1bah{4rUMy7URqkgg56IK28gWG47PrJ^pocP;;a?XI^SOEDbWk zkKYVjghbOWBhN+iYu(E-%S{q}ouzC+atcDqW1X8Bhsw=iinJ|K?$|6t&2;w#%3Ahb zuo~h`0|Z^boWzFq32Angqtr=iYJkrs^}Wb*F7&eE?AAX)SN|&wQn%q)ivv;!<^ZdW zjkllIg8#m6Y{2qjP+_t-T0*P4*}@cvNN2BXTv^4yeT;?jlx5BMsAQ!PEoTLSa*ece zKUWeo<{wA7-A(2)kZNMj0B0HD+Wyu+5$l#L384+0_;KuO`meDw=$zL8NZFG-|MdG$ zR$Cn?x_?pNuUhk&E{rn!^XYIBp1e$0Eq{!`-cY|JTlZN$xKPE`&sXV%N)aE|KZ(?{ zBTQ*{^r65OJjQ4jngpbY7x>q$xd*DttXkiPzKijNE$p(wo5E*z-ya-fp$HIfK? z@e-?}4}K7(*T8P7SJ%ipqWEBx8Bjcew?aa5tG|K817Hos9`vH_*Ax`}vMctmU}~s- z$z%Epd zJ*lh4Eo-X2H92+54pF|mhg-PUFXutJl!IQx^%;j@S;;2wEA>JauRxM+67^BOvM%Op zA23{kEBKG}4s~%LVVTrGD$K={*E-j=7mF#;$PmW6%H8PdlS|K4S0T|UWa_a%Q_6ub zhJQ`X&7Bc~3Y+4kS>m*#vljPIf}{$G;RuQYY7;%j&Th@4i`NLnG(` zmidq9;GdVD5&!V_?tf0_i}TAt6Hzi;pA&_z?^ox(rk|*k#cHwd8|D5@#hHyZ>^`Mk z9adk)VNkB$*ZtCW0aVZGDn>;LlTS8fZB3oA41z7uqIbexmb)C~&*Y6(%>4RMP&k}t zuB|X!YL)5|Ul1kikt?_lQM;h2V!zu-bnbQu4{F$v#{0A&K^FHh?OM6ub3il%^)4(Z zU9FqOr`Q-m(F!~hlj%n0)UkDamh-Sq%fjL8%@RR`9A&F(Booyo_f84F`wYYmU>4^y?jOux#2a)IBd8I;Q5isS5s# zAas`>kVW4|!LH?##PRGk@P~_N{Tv6~XPGA=xloOtA1f!C*30TvI4@Ah&H><5ctJ&v zv$5Zn3P!#_tZyUs>&>6FMq65}{=bySSF8WONaVNr^zfqxK1uAnDky~rb25}A~2huW1&MH$GM5C_d z#pwUUOZvq;<5hUg6o9@8NnNpmr&XOzD9rztpkt1iVS38(#=dQ;TmKGqn5kM35i()0`lPKQtQtW+X zD_d6R4J3Qn(AP7~mVQ?8J}JU(^npP;7brum)9(J zwLHFQmEl;DUeXPz>BnkoJrsGV;1^?R=ItKnZ}#XXoE(t;C%iYs>OVfs?86KJJ>V1#NU&0Gc7I2}5 zlrL9*P}r%-;hj3t?87zh6z+cH7)I(MN!QTD!+f@}N80txLw9REXG*v#82w&zJI9uD zDfX)#npH0GqKCk&;hCgAYK7jwv zT`b^Si$)2V?(RJrT&ozk&v%ow))=esWaUDLWCQzkGS22)$y{s2$XEQzW>AycHO)^I zxf3flMLbs7-M1$rNE)D}V;Nrkb)nkh1^oMa`Gn}0gd0%|MP$}x7}4#l%E~k1RWAbk zyOUaKYR{iLXVSFs-d(GOSbVAQV9bN>HIc-F0A+P&_$i)0e@+C?XwXIb@O|}m9ZwfH z1tTewVOlfeds&SSL0eVcN6tAO&zDScbW1<{+$TKfen%$C7GMC~c>oe0}^o24{i26T!|o;ZEf$MY`0Sh(A}7l zVqUC#Ow{duWJ4N7EQM7u-Xl9&CR-;G3p97!e}BIy`^{MHkr&`J9#=&J071eRRZ>rJ z274`1K{NZ7Z?4v9-PA8z2G!4_KB#+5DWjh5_bkpOIlG*9`uq?niJXAzY!_l}eLP|gk4)>Z8XPVPf#bW~tN zMmH@9VOwvfHm1i#X5IDpov2zL$eKUf)r${&7LkL>X5b1N5r~THEb~#z%9wx&oN=j@ zquGg##3@Lgf_RHKH%xjN*zuaeczxT=2{6}3XN#I2j_tt7eTgN#K&BzR5^Yp46JwT; zOzco)D&>A}nirKKz6S`x&H1r_UT)phe<8NM1q3+`H zU}JN$iO zwOlb1L8%X&X(>9je+9$;uaq$Ehi#YD zQ77%k8R0^G`B6JRH%m%8QCzy_d8?p9XOhYPh=yYwE8LYo&YRJq8n(48k4j+4q2tr% zHM?0d3ruN{5WoW~cd1%T3_I24D1 zE}v~GWxv+j*dU~3vWZN6XGhowFSeIGB}PD4kG^N_ZRROR=4DiQc~v}b+aHnIPuyW} z6)%&HXT^XIIS~+$MRq@Nn?Cr%j{&~@l$T=ACQm5nq^7#7O=SIZ0Z&Bxol@jWYUWT= zw0%kjYI%%r*Wwvmqq!AJS$0-HN+DKKU;bL470|_#^P?&Q4U@#D#CgsXU1@$y_GtZM z&yB@_f_Eb}r598UcA7NP+QJYVd{Cp8AuRITMe0j}&q2&}2Gm+(k1KY8iz<0G0r;d_ zA*%I)^nrDpR}O>sf4*8uC$>;@+!}QWnyg(S)jpS{anHCcD>d~AMNJcZ&7)VF{;un& z(n{YG>eXHsx|+Fb7(~=>?KzJ+^>s7zLnk*4O#v4h)K#w z)mXULk%TmUMg43TvK3m=Xnl7gQY9ii#K^b&J5*za+tTaz58(w zF$uz&7bkKaKirdtt)V~IFnMm{c4$9tjoIvXG>A&Xj+X9k1X^Q9KBV)~N^S97*-{++ zH1nF%KA1xowc8-!MQ@FDL>KR&9#&@D_n@Cm`&pFWM3SDcE^osS}Vm+$7KT@mG zetvYqc7UvQExD%vDk4poPC2|zyV<`VjdBx>940$3*0c+jdz2ydA?$D`sm4}q zjp>>@hmQ6{yKhc<8gtq72~NCpooTgfOSiu9Q)XURgX(6g(UIxpJ>Ay(n8|{! zu?MxgDtj*uOLfMl`AttTdP`3Lb%W&E_hj8y9*jWBtS*fJMVwW)mj6Y4Snmz7hTg{e zEZ?nLAk%&?;@6Em2(-m(sg0yD&Ht+zylNz{%tXkF-CoV}Tg@={J;D((`7nU7oO17M48|CK1*uDIPRQ zf-l(qj`!pavK&{FJST-^#ZFD}B}z!XizB8-z{UrH=T|(Pez^diApLqz=X8_J@Z@Uu z!}iZUd4nD#)-!s(??Li@0ywd8Y5x(6ejwEQj8<3H+^g_T^BTu=q8|t_b;EzFB%JCj zx5E0DQW1yyrF)HIT2Z7S56Nu8+tzC-v*UbXu z9;5S(3wFKMjHe*QzLxY~dp^&k1ljEaPSDqXi+xP#LA$}xpA59S7UcooxA7bTPa2bg z?|iK-LmbZu21VxD=-;m0mspo%V*5SV_>7UBK~&B-;G&$4DpR^|IQgYD87a0X+1`UU z6hexl?=5hW@NO(_mgfol*m6KZ9*aaJJ?A+>9jEUcjFbkgbss6N=kal;TM8J!Er*no zfwKWFx*I208s&e zaJ7hSlHE*u=(3b#@aKr%mm;iiiPfNH_2y7D;zkv+)ot?Ac;t=aZ${Vr8s$FuR7xES zP9|DHprVvObLbq0z=R`57w4U=BAFfSg++M0Yag6Dw&Hj;rk7|k1bc&?R+yPi!d|yx zJ$Lp$cCUic@TtE~p6)bFBZJ4v{qnUPdOY@Ms&LqO@8QgxBDzyOmM1m6b%&??n4SW; zm-z2?q|vJ**o%cy^XV7R+MSgYY?d|4ySn=Dv`iIfX!X!k4n9HoT5lAm?%BpFq-ZdT ztkh$}z6_^h&Au&kOmRzcUf%0?^qNPWgz%0>R4&XU=MQ>-TjxM$sCw#>`$SfM_-G&% zP-6hsr%xcMar}u!gipPIR^c2Xnp2?ILQzq~@?|k`;XfJT%HmKRjpl0#VBA2(p`}kK0iy*E^OwYBD9LbYY~X-?Z@D$M8YspBCq>`&5O~MyJ;r3P zbJ8^ZLQ$j6Zp}vJrhM3_=W_AYb3Yl6Sq>mTdMvj`9IN{*cum0rjg}P($>wutR&`K@ z-^iIO7YK@~gvJg6b|<@#V%eW&uJ_00)f@L^sr1m*h-|cM4~?InVr375qel9G7Ge54 zchZ7xNvB)0!Cj+A#GeMX>Yslf9*|B1W)kv@Oh}QktL58}URd|G**VJFw7n{z7ll01 zP5H|4s(iAey&BrpzI70|I8+pLH8g>(VqvZ%-LaKmz23&ho$PEY|KB?=eJp809LMSS zCLpG|aMkZc9x6JZl7ezTurBAt6b#Fe5F^R|0|~w*K2f`*?CG*Dg!|$#88xQN8^KYQ z+f7K~1V>wmZS`CIB1b-p%9CxKdHTPZ^B&LqUjGzcOE@p0Q?41g8`%DggZIHCUS`7{ zqcI#q*(vt}sS}PVA?^qhelXOt^PqpKBOnLPEf1m?@V5@K@U?+aUem|K=i(p*kCi!#P?Nte)M~l)@s4pS%TsTZBu(>u zE0@w|u@zAx&i%n7L?dh{8s3&RTlzF1ooF`3ZtlMx+_eoIm9iCDU?Q50__7(;gbx%IQJnBr}`WfGW-Mo=9K9Iy4LZ&Jln>QoC7pa{8P?9Hf8h!reDGL{uA2Yvj z+3)s0Kt!X#VE3}^b;do;G$L7puW3KFbSYJdt?(Thbpx#Ig)IMi9Pusv@;JlA$orV> zTxE7G{?b*j8IAjOj%lv*v)5F=Ol1?fAH?h4*uOSJ{N2PSVO+!TohR>=aAICKx5{KMiijwV4eSawKvhY(jl+iQe&ys0k?VZQ8 zL5Nt;AyndIT88KTwK88Sa|f&<3QF8wFPgk!rdFnGTm4NNWFAG){vAg&35RCIWr7GP zSs_ao7nO>Ncs+q0Pq4h^3PN0%KxM6{R+fnF%hZX0bv%Wa;gAb^MpU8$?H{*_9CqML6r0t~h^Dn-O#8}I#^ZT7MfuHX;UhiABMa5E)R77{lYxcE-A z0>CGl3mz(i)60!o44E(LOa<-~(vdwn^)%x4Z1DTB#Ri})3^o<$SIhc#Wo8|(qzfml zd(tB&Dg#PA1GS7`6cawm40j5xxqb}`kqh5VrLu{YNBd;-+0CWl1(Zm4{^;)~0Au^i z1yDv0cB8PfZD32|?s#QD5%M<8#b{fxgH`}ps2$z=G3AS_#Hm3bq_qF;FsPkro{_`j z`mkTgBfM%>;L&QC+v~7IBrEr4wv?8Xg!fvsS~JQ@Bbh~|?{q5DeOS4kN*?o}%g#~F z1cvGb{c`;_{q{^E-*!JO9s;s`BGxL+IHkvHLbsB^BhI|?R9Z=ngwVeENTD`twQ*<~ zD)fZo9uMf?zA8B4dd#8*-{Sj^=t+_UP6zOq-C(rVI4|W!Ya?7V#n%m0j)y<#0LJtDtU-^^;R|CtDW zb}qTE4XrgI9eRkk`1tAt6t9y{&#pZyAmZv4>+SEJq4}!P?BUSlI-w1#VCfVVa>d-K zn@S2jCSEErR97V5n?!yyEl`fF(Do_vVA3Hieg(8rukb?XF=By0uKW*yJ?F%FC%v>AxY@I?5UC8mtQy`?fBE## zGqsr2)q?G_2=<2y2eM|}uotX(w6_z*gM@HE=C@BRVL zRppi25ot{rUCY6l{K!X4L8s##HwewMSpfb0&fvcJ_i(49`fmjHj=3zN|83A^QIHndpG%WpBv@nsLCh%oVE>s$A0 z;4hN;K4ta-V!i~vd`18n*1_U9YbJME$3|3GE_LOlo9(^lS|;6TC7Mk#=3s+7KaEkA zoM47A#H|9O81lsxDpNCXWQqIg;Wi^PXpQFI>gWXoZWr6~$nt}95~GtTiBG&yBJ#G0 z*IK!e!*hu`7Hkwdf2>>`!L_dRhEnQ@Rg$+uq^G@i(*b9LJ*_-gEvaITw)@X1K;3#a z%<^_}K}Y?&`q0S84$SjY&na|t^H8etk0*rT?_2LL8~`Ya`{MQRV0KbJtjW1*UpEj- zaktb-Yr(N`sK0ZQOS5BMBkr!(Noxxbn`ZpaD3X3MazNO)F~ui^ZGXbpgQ`LgS|Th2 z1Ws?nc`hL<>*lPKVVPZ|h`HS38|h(e{SJh5yxbV00!wnia{WZdc4zo7|HCtKb1^bz zTsy*We)5o|P)e|&ll+1u>u0Bd)pA)Hls*6S1A5dMF;8*)D)-as;zl)fR!wwe59LIy?qmx1Sfe-H5NS z3i1dz@nIpK?)`h-16k7(TjFiBM5d#s$gA-npG}+dLD(;_iBFSrCHe@+HPXqN?-#k= zjA4t1QQ2*0jz;8DTqkp&jj;q6#3OBGpju-fUA*^4eF;u-bUy zZqFR@nE6WF4-mrD{}v+Y^xtvie)7O7FeW)R`uZx5Nm*)HPFY9#sgYYVXSJ~MrK|d6 zz44+6wWq+0jq|Z2mdQCv;q3Ee_L{$%g@XP|XCIB?n*##bJ23{yGxCt?? za4+~Q zcX{V?j==Y{!F@Jw^x>@8E5?lEALann=u|3m+{@Pj=uKEz{iI3G{v^y5O8~#Yotnc3^yRp6dvo&wPW)z0qI5-zhk%$pa8`N+Z75>=!nE8{j{qA zASd^#l%Q%|5ttaL;qib_(3#%@WjK#ko0eukx!SBtLjq@obu@z5eHsxM>v#(J-cuZ-0~!c>sqom+{@h2ObEff6?leeo?Cn&8dK$b(;EE;628Azd?t3m{ z^FI53hOYVFh8`r?pbPTc-a-EkO*m~+5k#|BfrxxdsdI%tPMM}ddKKutn+H4tqsPdq zfEF9l?0+;W?jHd8YDZaf&x%r(y))hkTD4aW3^V4Mc4g9vIFLoC^f1zf6A_Ly zR~w6k{n^aM1x*gA1%}mu(aD0fN?|9q=Iz2l{jxuWLSf?=iFqZdbtA{IxtE+~F) zZjQ{?^`!Z??4s-s{H7G~@q)uB_x~)(U1e^s}c@$)Y-b z$GHmtU*b2@kgHJ&%X!gw#qLj)UUYXck3ZAZFm`?ClalEswOEXcMj1@AgvwaWxvOh9 zXf6EHO9%8py52kuZ5v8F1u8SD*%6?z~=hUI;{||VQ9&7)e%HBT~ww_%}stVT(<1 z@1N>7tAFObq&V&^d5u4yl8f}*=Q2rtkmJsOOf;+96|o<%bN_oxJri5L_IF1@yk!B9 zAkKks$2mKm7%rC!Yf3}H^3-iV9OJwo9}Q%<&OP$*`}8+8-sjb;CZbU2ZWue)16vg6 zBU31$#(62ogsgfwGc9UiLG0OP!OW(MjLKjype){PO}8}u3$qPb{&mp-U@G7c=yg}j z8VY>}5DYA4dPSm2;6BBk-Vl*;|Uj1+rqRO8fjsqmr7 zLXr5^mYDN=pvQ>o+oyIqr4xlhz=L3TBxPYPP7f;%3Gnzjt4xOVVWcugM^5ln@y4P zPgq|mHHOwEzS`7BMG|8Au7TVx6#AWo58vSzS&>}H>jn)!tetFiKU;8>`vP{md;}T zZK(eNTZZrxoCnecUSjoKRH3sU+tS!E+&L>Py`EXkmpCMxh6^+6HXA%4h!YWrV!5aGv1q{r5 z>~bt`VSwv4?;NKAbXRCO#Hh6uwN3T&@()_Dp9<9eeKFJj=kAigh}Yb%HFx}?d_S>L z;8B&qk(7K4_aRC53v?HHCfA#zIR>pX(zvAroBLg*9ikG`5&RBwbp6(C)D$tycPV7) zloS-^oks*Txv5(%Wt1J}zr)DfZnDTKy3nIrFDjpSquy?p{=NmzP*yC8ksrv@e@o5^ zD$B)cVm-7`YyRP!g3{U*Xtv70!Z!>|1`on&S!bURgR)jMH;T)K5&u{}({C=+zJ&ee zmsT`Ba7&SyQ;fXfhfOC^aQCvTIjcJRft@Z!Ctjqr@be&>qS*$ zoujXuJ6Bdj3w59-#oCRg>hD{0O+1xVS*`W)A2jgX8J7_XAob!K(x6AtYc%^gxO00u z(zf<7js5U*AzCib3psnIM;>{XLqc1v*6i;CZQ=U+KEbm7q+hGx&_cy4T;+qxPx6?B zt1qN#0v>7bsJylTnklvs9|Yyi*WX;7VGe3lZmtEWNof0Q)Yv%s?=FMfqn%a2Bna-a zmOHdxbq4}8Z1Js=eTYKsrnIg|rgZymW0y_@_PHdH`3dr$8euo1fV5cxJQInu2*CoW zC)709X-Z*NOmtr|T-NP=3b=sSsdQCcctE$?IpN4K{ zo$UK(cmCc-i{Bshsm-z}^(K7>KQ$loeScoHOEp;QdrTCx3gXs7ag1;&fWniO~7!0yl zdT*9g1p!-@UeH=FF$JY0$0?)6*X!x7&_5msGP%TyM%Y!8=xKYCu)La`zlmjgg}i9s zFjL#VBY|QQP76*x7z%%E#m#?3F`})!@i)Id$_Lid4TJ%bj8rXr{L0S#;5rSGa!jR< zytK771Z|6kmbXDv5b*Q;O@yUF>37c%BR`Kkw_^%>qeQW2fKKc1uh*#YfL)Ozb`F}g zb5ozT`*xJ9YD*kSLrtT*uOTo`$y5cV`HSbn*@zdk-CtigO{#WjiFfz9)1|?}dSNeH znX|_qf$~joncJPLKoY!AGkGArhd-+jrN2pm%jOk=K8Sz9XxPRH)nJN^{jtdO+g5$p zbxs94geVnWifk@0@q;%0Q>J1pIesd|PE6UO(s}QD=+^g_h)k-z8L@dAb{R2M3n3?@ z`JzVQ^6Rq@Z#s}4{Qmx~Q}S=RWFSJoKfvWo-nrhTo$OCg$(VD7XOmw1cswcL525iX z6`Oxj5*J?kpZj(P5!#wf6E6*F?Immn?Z|iH65L#FE@<4?i5CU89s|$nieX8ZWrnG~ z)p>E(T;Hf$GrUcpQ6apZQsixSIj{*hd3blPN0y3CHHDhI2A@rHt+h+QU+W}Zbz%-% z915ZO>bIPu&{cAcFLa#lnB)&eRH+qYi*ND<8lsHsqg&=D@5Nwj#w_yXq<@(3Qqj{# zxoKK*YUeh+PgvCpyv$J;6MU-4Vl_q}@JC>s^74j71$eKFoKs(pV`x!9tL((&AN4d_gr}f-(sQZ9 zdR?{4VCC+VN~|Wq@HKf?f)DInWW6f?i&m{O81#Uv(9!c}t?TL~Ba?ig+pmzPHKhvC@IPJ6`enn>34i#}ANNM^3baFoOpLcw_0aAa@FF0i zIh|TtJbXV-=*kAili^UNSVX*{(5cpsei`yl? zH$}_p`Qt^h6y>ueCHeI0FLDy^yoKv(Xj~H50cl_dL`$bkktAyJ3^J<>PR$!AF}$w; zo&eQ$5!oTr`A{0owMa73n)A~0by~Hzw>9eS{p8?I&DA|+yYp6&8_qLb9p#Nor!gQ(VT9>6s5m$cfmzkDu!?^8 z@S#bT$Dk;bLL1KKuMADUV93kU{dv4lpgrK~#@p^L72H3*s!`bV`vg2*F1-7u#=a+U z=Hd(fSPH=XW+i&Dvt$j4JQdd2SXo8RVQI@@^u&v3)}^mgRT-m-);+va!;L0YIxlzLTEe7*8L&hBA zpV4IA#-gSS`6LdC4kn9se@ri{d(fk7cTCjS$+;3bePF4dP(PS6_0x)wIEpFdD{xM1 zIxYaoLY{k1qV;YeX7sHvBe)AgQF6t4^J#gd4keHq728?_d#0k)mze4=H(i^;7xEuX z-G!L$KB^p>yCYWfKG825)s)uL1AA?RA)-L)9~N_w{cEq5${?(mljl-)lH2KRG@7gQ zEK579c)IBWM`RVOGn;A9a&_4I4~e)NRO4Y-jV{q-3?p`8KNDcEg+f$dJ+Dq{IC+s| z*lk3IsI}~E=LZNy{x}+2Ecfk@r`Yh_a}cfNPagAlAyC|+?jf@MJtACM14^riTZZ+y zbExJ~WL*nT(5siUPsw7wWK4n4msB<3r|Dd8ju$|u z&E_`g^G~Z*$GKM!JO6>K#j5`=*s}?(q>^Qa%3qnpVfIha(9j_`)MI0+7DyNkip^BFtWA zU_u$(O(zRV)CvG6kS{bNz~~A`kfP=$N9pUYybSOk`rs|7iPX-KjAN)zUAv6X4qGUM z`o?s3Fx5BnwlZ6mIt(rOk6&Bcd-1b=E-42t&evy8Q0;_no8E^ceWTR48WeF8&PtLM zS&dhN75TI9CuI4X>T9;UGv-~MBF|yr3dxb<7-9zJpw<%|A~nU3N_%sYF)4BJ@b07S zZ^l$`Y5AkYVy)SbZ2c!hOd6z7gZiVv{PFZX)up{rXV0U#6b*~KZTA=jKk27W5!J=T z)MTJ<3$T@Ejw2?fZ`@de1|LZ-(m3G>ep1g-PQ}meAyadT0x(16KYlMyB6up~ahZUC zO7ixCPHS^iG8d=)Z%O?lz$3yY$uePrB@lEsaHAgzI%q_2m#xJDtx!(p@}Gw%E_HVt>J2+3fBTu zATYfz9&apph!KzV?$L*7SBq{EGfl08qP5ax%pGfJ86-3Kj(jQH`cs6W*qz ztx%R@(9es+D1x5bk-H%<3*_A(>MpR5kx#xul(Kn+;mte`YN|6R-3ApDC7Ll^ohCxq zjNVI!@eRi7QTv|Jr3FLm^RpeYW}_B&v3aNnOX2wHp@nM@Ly1}@8nT)@&(w8vnBv(I zP#P>kTv3Y3RfaD=JZ00~86%@9j822`lebz>b+xct2ryR=r}tzTZIkeD9B*O#NZ1{hlY$ zJ)_AC2xsGFO~`Z4$f5y}NnYM~Jn$)h8ZRzT66T&ZqFexuw6&;AW+`sr`)jEV; z#jk{sU=7u1avxd)(pibKb3zU{^EL?cd5HM)Pr3^xrMr#O%bFW5a3;$B0P_{z-gr0m zknZL=64oN$aKKDTPoGUIq9DT431gxgI^(4z>R=SAX1M6SQyTfJeO>O?ry=BgIVp&p zbI$BfnFZcEe(S(j4GCXa-61V}fGnSxn;Qa@)A^ZzjsPXnsxFWkZ#7tWT03W}quFtb z$4hs;e2`K}l`t*Vh7EeDBTuho!I(5vqAz=YI8jg0OmL00(eh4E@jqocKYp9HG=SWR zxUu1Q#K9p0!n`@q3a#klM1ctpG^Oekq_zI&<<<{y2JO_Q>*XwfG|aZ1^$o}5|5`8b z&&Zyn63SJTeGOi7?T@du^L}^g)ipm8j^KViijy<97m6}yXy%Pf-c3(u7kgFNga_dz zCXA?5E7IXH6Ig4vdOMR~5TfNQw`_+C!|Z|fQ??E|P;Ijf6oOUc7ylQlhKLjMI%~n4 z`IX@W;T@CvT#ulM(9QQ)X+$hvkylzv`x4j3x2%Ow34 z%gM`M5AU!y!oVfFNi8d{7+@2SNSvDgH(XP9*Y&(fC`$@*EiMC8&Dk{Y-p~hbtl{Fv zM$?(Ch6FUe(16}GFgz|z?=-em0-f*mh!|+ouW;nkZ$Ca$z&@ZMx?0gRD%f;cLl$iX zTryNof6inzK<(NUsgkf^id24MGIH%pHBL1>eM*eB<+Y!zl#E{Y?j2MN(9C@b>2!a3 z$n1zSDX8t#5N6oyxbeUVtw5uTmJ6HL9ZN~xK5GBz^`*?df9vqbO#1a+f+^$HPwUI| z^v0^szX((@Go#mieAKwdStid)vrvO*19mCF zdai4qF>#41MEXvyv!>bne879!kZQUReE5f^SE7tz*Wf#^g?{_rAyg_sAUQOscyoOI zh--NTw`rUzyQ_K!owXo+%_W^ob?kbfl-|ph_)Rgw;**hRNu4-RF-L-iRMBHD;dhsi zjaEyWq!4!pg>eX56dAGW-3Nh6a68cXyox*WVWhORQsqLqZyRdLnz~ zoo1Jo9!L-io1OwqnvqRm1J`kqGElya09M&l*FAnH#opC*0PijWG=3H5n?ApO4O$6K z5_=C;z1os1(27)GG2YbVacE-4w(R_<PB{oy5HG1oRKQvlFt*GrV^S>qZ}?t z_8`QVP0kw}mG@Xs58BlSO$Jl8`PXEbsKAA1nb)>Fe!V$=0hs0vlC`D9<<7;){Gi~M zc&Uu)^B2`c9a2ug;^$vQ>Er$%md-k^$@lyJ_(s73R1~CDO1eQnKoLboDb48a8s(@d zjdTr0ioocG(L<1K7(GISjSgWj>EG?=``d#*_u#SnzV7>6=bYY?$VkW|$f`~g{H+sIh?C>oM<8YFFx(ZaX{uV1pG>o} zSJ>X@=h>~|9*@Ed<68cy37H`kS^Tw(FE(`tl#LG(-~;yG)&WzlJ8FQ5&*6=5vPO?_|1nnt zS$7AarNR%#!=Z=Axa#M8h~qOrr78v4OdGmK&*BX?Vyj|MNbbL{tK8y2iy68;zokyj zca{<|MU<-YojVMzIJrh-h%GOHVM55;T}nxj3y(qZx6V9htpKodaH)nyeA-NPJzDJ; zKGpXIM~xf0SV~ciXuRre&i83Rups6aJoN_0acBL{EUcsEZx5N_DG!#ne~bf3bzJw{_O8eRt0(QP15LMouyi z5^$`_Ic`m#lp|TExb;xsFvx!Fwmm(K0q>mfL@h;`$I|dDYWqFAsCT)USi$!D;bA37 zgM=JH<*kFUCh8ydb?)}SJTN3k<`~8Z>3s_M9lbk;bVkw~2VF@c(`2l&O}eZ8Fn50lf_!yiqGsGCC2v=+x*4VBERe-BGEJFW zG13AGu-j)yKo$Ri6{c3sT%H!L?kOW`sC-WwZo(&u`lBo2|u?<4OrEvsENrNS_SzQulQ;ZqM)u&m#oUi`%) z+zWO(3%r~DFH{gd)4n)l9 z&Ho`_!T(v)8hLyiKoKGhDI#0(ViiIrmJXj(gRBDtmF$j$D1z+-Rj_KIr0>BuR1J(a zAL1uUAS3F|64&y6N>C19hnJr8oi9A2Vv6(G_{?@^1@g|#P`p&24~AEVXD_%HWDx(d zq&?9!J-vNjQp>U@^Vu2e)9PXd$Y$PeHe4p@^gw9m7t@8N&b*eQJ?oP;O_$`ad8>&2 zcnJ7(_SboYPusk{lyZVbay?;*EsW#Xx8j+8RZVWSr1UeFA(j5M`tp>8lWWlRI1_kE zR&7!KZ@uQqnPGC4hw)oO7pUEBiN1Ck3dBDomiPd@aJqVfirtHkEO+RiOn4PVG6`Y? zcDZvt=wke)JeNP{c^K0a=*m@>oOpi7q*mrV3{vg5KK_8i4}P=5aXAjmT8Px99h_G~ z+$QZ)us{A`pz=x$#!E`VtBw!zky}OY=5?R;z9`VS&$WF=Tki0UZ8XMuk?WP9F3!%I z9)_%9;gk45!&#qG#8-gRl^4+-Z^02rj)%lqXGRLlAfK(y4m?*XZPRxjtA~e3z0ERr{UL7LEoCz*K)YE}{K79-{#2 zZ+X2hkPf7Oo&}h$=~Fi2yPAbMC7^%-b-)wLSG$`sTvKYa@C6Mo%RMgj{-8vcsn%q7 z8#G76o*WCq!kFG1+1b5WU6$mv;&faRL1!a5QNtj;!wQYAWe>~eTx~q?nZI2r9>2!vY18f1Z`JwQ<2 zTH1Rz3BV0b2M?&po>*eO*QUE7+GG}B6safm1q)QS9+e$Dy6uP9G>I=ZaSAeX8pgQN z8PbPiSb~e|ig}G&|I=;VP5ofMFxH3#>!ch->;`jC+t?x;MAa-PPp?oXuo_Ew2d@Uc%t6Vw8`@*y4cbd2rpY2sJc;@uLm@hD zF%)?rfwp6ZbrAOo%HgpQZ;N~(kuD`H;vKGL;@jQ)Fw0kK#&2trh8$jg4l2+n=^q+7 zS3nbf9UbJB5xx9&Fi&Rd3R*#n0aMzLIpoSo&A<5YDt{(m`ZeY(WItF$aJpGu0pJG5 zeh25UiabkTcm!=Q5p{B(i4eHIHQ`{v6G>sqUZKd6{Chbi;$rh%9co7%Hbyv@Y4X*O z{nKSIXIvIql7!;OjPzS9BS{z<-ci0X4jbG>pE=i*E?1Wr!;(*Oz8NCe*KxXM#oBqR z+G)++J*@}ptg&68-IT}i`+8v14q4}e!^OZA)gyhy$(FuYnyJmBZY{S#Lz6>$Pwz&+ z8t-vQPQ$*{k%!oBHD0*PLiMxr{RT5>iBf#%u>2P>iY{nQE%HMK%O?>t)RbUmTdVC~ zKKf6}osJb%(cPyFKa46{`Q>3{XLw^|`lty8t>m54Hsi{YBlqoWn%&8v2($iYrzB_6 z!FgZ^4$q*My`%kxNBw9wYr5Z_8av;&6q{XnIYQBPGG-nAUTS))$SM zakOuvD-J-qxB8O^>KgQT_olEky+$AJ6D>^wT2^LK7`@VNB^yD<*~e3iTS!6wtJ}!Q zmpIHOwFM8cA3$~;6oGa}mA@OPT)S8^eTgvd@Iu?9mr zF69nLiRwfb{Ci?NXn|(-;+5b#cQ#$(0{>DfH2+rg#kap2Ctpz2abFqX^Q@fg*@+m8 zx^h1@Caqio{ekPnLP$}u4oZ^ucwXv0UtHaZVU-%pAILzq_GqAH^?Cv)6jdh2)p!b- zBd+-IJj<9yy;5p0Pb&hus=YOCs+4LHuS{wg5MG+-+%3A$UCht~;#fPb&NKP7l_%O# z$J1uOU00n{`#ojY0X#cUa&IcG=&N1y8Z)Bg(kGjZoogk8wAvk#Qc6bu ztaxfuqilMxg$7(cWzP}5TJFLtF(r|0;-2w@WJS&SbGONHi4B4zDvL7PX6mgP`AXT$ zK$)1bXS0|o4TGgSdzf!|HQ~u}=Rf_l@_I zvrHw3%zFy0WPoG~18J_09Sjwyiq#tH@J{|pS36^NCq8vndBz&kc4hoXw4QGbiFXAj z=r{r6sq-q&9FWT&t_k@sNjo>bjv5>Lc-Z)kCr?!uX;mz|p@!pffr60zi5i8EXgtDB zcjSmP#Lldxlx>GwnI~UF%Yu>Qf&gDYdjNvLc&FN1$w7B7W4!xay*?~{nk!Ld7)%sr z{K@n`)k4Y5Ya+b9Ze~K>PscPqCSt*C&UN6DW# z{Dce;tT+t4%pvI+DNsFe_G9I7`|G7BZHa*t8~)e@JhHRly#LoU4zYP3$+voEQnk85 z_8GP~EPZ2m9C3iZ+#7gT!JeOJ8{8jo@|UkVAg}G?Yn76y5T2N>+8j~z{*CyJwJxdi zmsP{z!MzVM+tv{Ra-E)+Cu;*%DMsRJ0}ieV27b250y5)GcE&t?c`KJg-N>F*{F;li z){VmwnCH##BUPMA;ns3h7%q!EKx)ljc~=>eAcx$H9_!JpG_LX%VlR9+F|IgPL1WT~T`0ZeL=+atZ0n>FCjvn}vSnSh6{B-8P&DDF>-w;Hhh6lq zGzQ~4aE!6*5DUw#@_cG8ZV|KDN)7*KtUV2hZT!OB=jq5jSP&}1mw``GEF7<^fI)4y zYVbs;$nL3K26WzaC&)PFQ4|>5=hJ$mYEY4m8X+Mmdm^4dC#1=EasJW>ktq#2U+GB>Z#+|cqA)8Y;Z_L6 z`^C)^PO3Z{x%xV78!5yHPm5dF`xJC5k^(>QEc>#y@$ZE4q5wmzv>IT7_Tx&}*TgLQ zmYdy?fb>cLw8e8o=cKP(+Mqb7F8%DO;DRyd#zyTb8;{5dT_e=@j3%R%CU}5Rxj(&M z<3;21S>XMjMZo8&ogcgMKNTt2yfLWpXPKHpF(k-jOi)+(Ca+(B=FL)QrcOkf%lz9Q zPx{)?Co`3y?H&hj%n_HSvnER{k~{oFaa$DpPGW#e-$+jSK$B)9&4A%F~{P#J@^o zP&j;JCuCOlKDqAOX_^UwFIlV%{3WP8xTdx&UyVV{eYz{ky*M9#oG#)^|GL&yyB6H& zEnTp;;z{pYUBxmc*N1sd&z*G+WvXKu;+R-&ng%j)ZEw8Bf{f?4&CF&;?Q|haH3+7qr4kp% zJGPYmzI|6UGBPrifxd$50C0@Ks~;+Dg0dfgJo{L?B$YL8@{Wi=Z>;xaUL!@G_ac)t zjMsbCy9f1EWk(eR9zPhp3|rkjW8ixx(idgP&MKX~#HB8Kk@P+mCS5_Cq<{3oL#Rr0 zS@z`sEQd5PRx!}r`9q$r zhS&J@W-?Jmn(OFOiw=|B(@XIc(Yx(qYc&{dYb6W^M-^c6a}xH8@CUgpMp34nXa1jK zGT!s`H@;fKSpDM;7mk~2nY5H$A=6t;b=F1T1hNxh#jBcmsGwwfHQ5*=#z%|PHM%i$ zao*H`Pn>ZLJuFttX#ckJD~ogVE&b4+DLm1;L=Q<9q;WroNW3eUt&|4rMe02J!Ie*T z{au~dV2GWrq(`3g$<{Lql@JY?N(*b3!e@XR+uiKN71ZeVBMNDiJr7QpoO!6^MmzF2 zJ=)$jdefG^T4YD@nrXD~WDYf*e)hsBBn#c0vYG$x7q7;J3J_{mFPfR>f4e9jNP~x` zM^av|^WMx6gS$sCUK2G%K+X4PsJYL&b0r2W?V46*B4Y!pfM_$_SEAlfx$`BIWR&7i zM#t{mCa!d5*7#Bbcy>MqefdlWj|r{yaU7?MQ{?9CrE6S!3)!0grst)Qv0XTS6m^!r zb^0aGvFSYMbvW-?jm_I#Uxi~zv_fh2mBK(u24K*a(BpDt=sI^${I<}(j=RtDtl;C{ zmWc6SJJO=3<+*B-Am)pW+=CxMRPTRR23tXAlu?9F1VT`SVHE2Ad29o&6GiYO;l3YAOj*pUx37~Dv0YHsNs0+oTVxESW<7wB@i z>&-zbmC|&&q#Di`q3L?QI^Eq2s75hSyg0<}@9$?@mXZsS^6%>J_PsHvAJFvj+q$=8 zHe5KH6)=0L5Ky31_80r@%QXtG#xE7eUj*yHsnX)kjC|zxSOS&PyriS8TOxltNpI+w zlqme?WIcPQsq4nvT*y?QRl(tD@?%GE_^)g(r>(gvubk<#x-i1hJ%7KAJZ-VT>imJ- zreNeKDlcuvc1!quzN_~hhkvm^d; z1M=FM`-VQ(*U1D#w~w9^pB<+;Ptl1s=KFH(JM8E_bj;Nt^z$Ej>&3&(Gq=pC>p)Tu&b^&x@@NROmBT_( zd2GB=6H#jEu0NZKeRyW!oTFn)nI#r7iliYh8wf~$%o5aO^ly9dt8PKmz2X$XAkQG& z-ni~9LgJ$?XX8(dzycZ9nYZke+jjS*=K)^Cn8!ut@V2DSs^BFfN$1!eE)t_LqW+#w zZe#dP;!wdMwFVXZ&0!$SM?-?`Sl}wNwC!A?K^MsS;gbJZAAU3GlmVPjA%Jfc`Tv~b z0N)F9>j5|)WKgJwaOzTbY8l$hkkTs}(xqZ0RP&M>KvIi8JNUV#aI6C2NQ9^Nr+_YS z6>yQ^7eBAJaByT60V3tVtJ#i?f*$VECwe88A7w}#phHo#mnV{V-yjL^sjNIpO zQm!(Lv&mk3Mm6u&Hlu{qa{VYF2UGs=$KeX@FZ^da*phN2+_5qbUevgzJ)p6m8^5}u zbZFt{S;!5$nvn~3Zti^rYwL~bm64cd?aEaKNHO<0cP>119#L0=c9fNT)Il5qS@uoJ zr&fQkWRjAKAjM?1W4q_j~8(RAoTavLvK z>K0P0Dj|~YCPAu+jdrt5F!fv+%NNgeDu6d7PsiOrb@T8~-BHcz2fFx73+jWFKLvqX z6Ym1uo>mME7q%1zM4ez?nzcTWE0p0FUr*btRtRvE$ytgT*cgsKfUJosv15eCbnO{# z4r~`ISz+^5L&;ZHD*m$Yt<)~Cm0>EucFF%qn<;}d5)#FoLZ}|JW!f6%*CFE!G_~tf z;|vM}4bq=vEa`kI8zm__@h)HdiSpr!R}p93qSlvuDfDdCa_Zu>sOSiLZJ=&%F@8K`H026@ePHD>d#9xFsW?p1MZSmU%Wi?rgVeILABMQv zIdGHe6F&ZiDGw>x!FP{>Cc8j+Z^@|KIblYIVzmbztgd+N*X~$IXoqcxKfWg@%hf)5 z3%gbJl>xaU-pXwo{RCV~KSx$oh`we@F}AN`Qaz#))|NvBllE-EhdB{Hx%)sb|PfBsj=KXjxE1;L{;8E;aP0&tYroEDf-+!)0V24gUl% z)T^}^dhJp{xl`5{_i=_iy_&ARyUNa%HG1>_#Kcm_>G{#~86-Sru_exxN7+gCM38Pt z#(7R7U=O+1*j@=)30px^pRK~j%OF2VB~rZ-=7hg%l)N`jv~w zJ5;PZt`yF6+W6qYLw*4+503*aRtB(ixwVO3Y(v6J3qs*AJ~(2ZBPIVZJ~e)>f-DbsMoQyo3C+}F30wF=u^I7vpMSQ_y})mSsg1MI z@a)$h;MNZ7RYN4Dv=Zc@8+`&hziCLF5{-`?@>hEUDAMf%ao0={kDAgI)!8zMWkciZSo77it%y zsNdK~{{DdvVb{jEJ}X}L-6*#SY{)Jqmr6rfW+hFwP@DWbSp_a|Sl@TI3DZPPzs0(e z;iJvgwsa>4Iyo8$vh@x0kX`0w#k$++RHL*&>cz^It=8a_$ zi8$mnF_Ix9NaP^1$0b=n_EYA|>a&d)?~$A!hE+Zr!>2AXD-cP)zxQ@QrUcwP@tDb;XRx_-~`(NM1=!q%nl_H~C@V`UaYL_M1697=sFgjGm zbpkTpmD+$HG7zFB%wR5M?jt*{ktii-wU`1@vzcY$0u>j_@<|@V31?eK5l?R04s_}; ztZPq8q7)eDJXMNs*ppQE>dEuYUl_2pN-{L!W+>K>`py<%SC%?n+=PC10@xFfKd_F{ zMyWKYRMgDrCT=!;ZCq@yNz>s;hki6pI7_fLsWV4t|B<(%>uEiIug%Ty+d#hX5p{D0fY-5T@a@VG3V{Jfj z23uHPOqE;X#VbGPxmmNintO~O-&1ln9;F_5wU(8}L3K}BlGBtiG)Gu=ZP-y=Um6D> zwKbz1-R)c7PaPM3hVDGQAeqV4jz8FD`MmUL&SBNWRMY^J&_QHnK@I`o3c9~h-Uv7s>tD=s| z23~0{Vb^f!8!)VnniaC~0CDU4&ZE9CFvLDtl%n(~ctuASW4DxhnWhsF^@76#RW%kU zqQNv2j43Q7HKouJ5WiMIhznL*o26}tQEyzWqgpjtxJ(DBJpljh&RhetvQC6&g!^O2?LV~KVJWo;6AJjxbQjYmOwuRP&#NvpF#t^ z4S*ISc&$cq9K{-PSpP&v1Yfg3~g=NDdOJ&dW zCf3QX7V^e(-6e)4J~{jq+Ijp`lX+63jK{(`l@U!7-Dy~bO(?QIHDDw+1UvuR>8X0- zn%(`>_fJ!8%361!V7+`*w`A;2&#G4h75s#wwB@|hr`pP&10kOLPV}A^*vDUmM@jBE z+`*oYj@f&9M-HyY5}r!?fh16La<8~<3~iumWZI(R;zsh;d8-u%s< z^z*E39G6wTN}*kqL4y6KVoe?mir`pv<~UF*Z-isVxvxp-qx=tiY0$CkFu13*^3e6U zRuBX~>AtIw+m3@wMPao}e>2+Qv4G}tlXL$H4xS1HY{ zf*WP82fFaC25jpzvPRqctQg35rv?IQV?wz|p`Rje=z&^f)*9JlweeeVmlp2;aYRo4 z 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

!a<#x!Ijm9ekI2R!oFy*P98d`j=dj zx*&~D=5Go}PStauhZ#g*13Q`BZS+8%rdx;B5V1gE{j|nngW=G$zF68+_5Rd3m<#Yo zDwZ)NuYtds=^za}GibljJl0t#;`x3Il#RJ3qgOxKa%R+XxcMa$z}mUv_C{NNoxgRU zmT=sw8f1Ygjcw;oU0Ywi=`MhUOM$mYUwhq0OiAo`RG&EtJG=9-4S**oC-f~x%NUVz zpxb&5I}_V%bIK{D?iSR?7Tq9yjI8R&J%lY{xVYsa;U;3y=gCD+0h1RMUEoFM=hWopvWYO*F z8EEgy-gR!E&3)Rrr_8%JA`{TR3KWJJ$FK{HcU&M37+*|ay ztsqCfEQVP4fe@ZO<>D@5Pv0d2O_vFH&vTsZM@1gtyT6Zv>c6L9bAzQkgBX?u#{Eyw;k@F>71D# z%^|jj*yfe!=3Q+^pPr$Z21zyQb!Ky zA+$uVudm9`kteuv>-y?FY!oo5k$+fwGXe;HAQK6GN=GENHp4BL6+ zTN^m4p#AJiM~F~a)a9>VV8^%oe|`D-zo^i_3p?ljwM#DK?BB5Z|LaQ}=ZL6pIBEQb S{&nfy^z*vLXYx)t-1!d}tvkg4 diff --git a/analysis/fig_tracks.svg b/analysis/fig_tracks.svg index fcf59f2..078a79e 100644 --- a/analysis/fig_tracks.svg +++ b/analysis/fig_tracks.svg @@ -1,12 +1,12 @@ - + - 2026-06-04T08:33:44.968663 + 2026-06-17T10:45:24.236157 image/svg+xml @@ -21,42 +21,42 @@ - - - + - - + - + - + - + - + - + - + - + - + - + - + @@ -212,100 +212,217 @@ L 292.238437 50.483625 - + - + - - + + - - + - + - + - - + + - + - + - + - - + + - + - + - + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - + - - + - + - + - - + + - + - + - + - + - - + + - + - + - - - - - - + + + + - - + + - + - + - - - - - - + + + + - - + + - + - + - + - - - - - + + - - + + - + - + - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + - - + + - - - - - - - - - + + + + + + + + - - + + - - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - - - + + + + + + + + + - - + + + + + + + + + + + + + + + - - + + - - - + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1475,7 +1553,7 @@ z - + @@ -1487,10 +1565,18 @@ z - + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - + - + - - @@ -2074,9 +2107,9 @@ z - + - + - + - + - + - + + + + @@ -2237,9 +2302,9 @@ z - + - + - + - + @@ -2315,9 +2380,9 @@ z - + - + - - - + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + diff --git a/analysis/make_csv.py b/analysis/make_csv.py index dec63fd..937c940 100644 --- a/analysis/make_csv.py +++ b/analysis/make_csv.py @@ -1,75 +1,121 @@ +import csv +import importlib.util import os import re -import csv +import sys -# folder that holds one subfolder per variant, each with an x86/ dir full of *.log files -ROOT = "measurements" -OUT = "analysis/results.csv" +sys.path.insert(0, 'flopcounts') + +ROOT = 'measurements' +OUT = 'analysis/results_x86.csv' + +size_line = re.compile(r'=== N_P=(\d+) N_ITER=(\d+) ===') -# the benchmark prints one block per problem size, looks like: -# === N_P=500 N_ITER=200 === -# W(n) per iteration: ... -# Q(n) per iteration: ... -# I(n): ... -# W(n): ... -# T(n): ... <- cycles -# P(n): ... <- flops/cycle -# so I split each log on the "=== N_P=.. ===" lines and read the fields out of each block. -size_line = re.compile(r"=== N_P=(\d+) N_ITER=(\d+) ===") def num(pattern, text): - m = re.search(pattern, text) - if m: - return float(m.group(1)) + m = re.search(pattern, text) + if m: + return float(m.group(1)) + return None + + +_flopcount_modules = {} + + +def get_flops_per_iter(variant, n_p, n_obs=2): + variant = variant.strip() + + module_name = variant.replace('/', '_').replace('\\', '_') + module_path = f'flopcounts/x86/{module_name}' + + if module_name in _flopcount_modules: + module = _flopcount_modules[module_name] + elif os.path.exists(f'{module_path}.py'): + try: + spec = importlib.util.spec_from_file_location(module_name, + f'{module_path}.py') + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + _flopcount_modules[module_name] = module + except Exception as e: + print(f'Warning: Failed to load flopcount for {variant}: {e}') + return None + else: + print(f'Warning: No flopcount module found for {variant}') return None -#we run every variant a few times (1.log, 2.log, ...). keep the fastest run per size, -#min cycles is the standard way to kill the OS/turbo noise. -best = {} + if hasattr(module, 'total'): + try: + return module.total(n_iter=1, n_p=int(n_p), n_obs=int(n_obs)) + except Exception as e: + print(f'Warning: Error calling total() for {variant}: {e}') + return None + else: + print(f'Warning: No total() function in {module_path}') + return None -# flop counter is uint32 and wraps at n=10000 (72*n^2+74*n = 7.2e9 > 2^32), so the logged -# W(n)/I(n) are garbage there. compute work ourselves; matches the good logs exactly. -# symmetry variants do half the pairs -> 36 n^2 + 110 n, everything else 72 n^2 + 74 n. -def flops_per_iter(variant, n): - if "symmetry" in variant: - return 36 * n * n + 110 * n - return 72 * n * n + 74 * n + +runs = {} for variant in sorted(os.listdir(ROOT)): - logdir = os.path.join(ROOT, variant, "x86") - if not os.path.isdir(logdir): + logdir = os.path.join(ROOT, variant, 'x86') + if not os.path.isdir(logdir): + continue + + for fn in os.listdir(logdir): + if not fn.endswith('.log'): + continue + text = open(os.path.join(logdir, fn)).read() + + marks = list(size_line.finditer(text)) + for i, m in enumerate(marks): + n = int(m.group(1)) + n_iter = int(m.group(2)) + start = m.end() + stop = marks[i + 1].start() if i + 1 < len(marks) else len(text) + block = text[start:stop] + + cycles = num(r'T\(n\):\s*([\d.]+)', block) + if cycles is None: continue - for fn in os.listdir(logdir): - if not fn.endswith(".log"): - continue - text = open(os.path.join(logdir, fn)).read() + key = (variant, n) + q = num(r'Q\(n\) per iteration:\s*([\d.]+)', block) + if key not in runs: + runs[key] = [] + runs[key].append((cycles, n_iter, q)) - marks = list(size_line.finditer(text)) - for i, m in enumerate(marks): - n = int(m.group(1)) - n_iter = int(m.group(2)) - start = m.end() - stop = marks[i + 1].start() if i + 1 < len(marks) else len(text) - block = text[start:stop] +with open(OUT, 'w', newline='') as f: + w = csv.writer(f) + w.writerow([ + 'variant', 'n', 'n_iter', 'W', 'Q_per_iter', 'I', 'cycles', + 'flops_per_cycle' + ]) + for variant, n in sorted(runs): + entries = runs[(variant, n)] + n_iter = entries[0][1] + q = entries[0][2] + for _, ni, qi in entries: + if ni != n_iter: + print(f'Warning: n_iter mismatch for {(variant, n)}: {ni} vs {n_iter}') + if qi != q: + print(f'Warning: Q_per_iter mismatch for {(variant, n)}: {qi} vs {q}') - cycles = num(r"T\(n\):\s*([\d.]+)", block) - if cycles is None: - continue + cycles = sum(e[0] for e in entries) / len(entries) - key = (variant, n) - if key not in best or cycles < best[key][0]: - best[key] = (cycles, n_iter, - num(r"Q\(n\) per iteration:\s*([\d.]+)", block)) + w_iter = get_flops_per_iter(variant, n) -with open(OUT, "w", newline="") as f: - w = csv.writer(f) - w.writerow(["variant", "n", "n_iter", "W", "Q_per_iter", "I", "cycles", "flops_per_cycle"]) - for variant, n in sorted(best): - cycles, n_iter, q = best[(variant, n)] - w_iter = flops_per_iter(variant, n) - W = w_iter * n_iter - I = w_iter / q - fc = W / cycles - w.writerow([variant, n, n_iter, W, q, round(I, 2), round(cycles, 1), round(fc, 3)]) -print("wrote", OUT, "with", len(best), "rows") \ No newline at end of file + assert w_iter is not None + + W = w_iter * n_iter + I = w_iter / q if q else w_iter + fc = W / cycles if cycles else 0 + w.writerow( + [variant, n, n_iter, W, q, + round(I, 2), + round(cycles, 1), + round(fc, 3)]) + +print('wrote', OUT, 'with', len(runs), 'rows') diff --git a/analysis/memory_x86.py b/analysis/memory_x86.py new file mode 100644 index 0000000..a9a33f6 --- /dev/null +++ b/analysis/memory_x86.py @@ -0,0 +1,63 @@ +from pathlib import Path +import re + +LOG_DIR = Path('measurements/memory/x86') +CPU_FREQ_GHZ = 2.0 + + +def parse_sysbench_log(file_path): + """Parses a single sysbench log file to extract memory bandwidth in MiB/s.""" + try: + with open(file_path, 'r') as f: + content = f.read() + + match = re.search(r'(\d+\.?\d*)\s+MiB/sec', content) + + if match: + return float(match.group(1)) + else: + print(f"Warning: Could not find 'MiB/sec' in {file_path.name}") + return None + except Exception as e: + print(f'Error reading {file_path}: {e}') + return None + + +def main(): + if not LOG_DIR.exists(): + print(f"Error: Directory '{LOG_DIR}' not found.") + return + + bandwidths_mib_s = [] + + log_files = sorted(LOG_DIR.glob('*.log')) + + print(f'Found {len(log_files)} log files.') + + for file_path in log_files: + val = parse_sysbench_log(file_path) + if val is not None: + bandwidths_mib_s.append(val) + + if not bandwidths_mib_s: + print('No valid bandwidth data found.') + return + + avg_mi_b_s = sum(bandwidths_mib_s) / len(bandwidths_mib_s) + + bytes_per_second = avg_mi_b_s * (1024**2) + cycles_per_second = CPU_FREQ_GHZ * 1e9 + + bytes_per_cycle = bytes_per_second / cycles_per_second + + print('-' * 40) + print(f'Files analyzed: {len(bandwidths_mib_s)}') + print(f'Average Bandwidth: {avg_mi_b_s:.2f} MiB/sec') + print(f'CPU Frequency: {CPU_FREQ_GHZ} GHz') + print('-' * 40) + print(f'Result: {bytes_per_cycle:.2f} bytes/cycle') + print('-' * 40) + + +if __name__ == '__main__': + main() diff --git a/analysis/plots.py b/analysis/plots.py index 5e6d729..37e17f4 100644 --- a/analysis/plots.py +++ b/analysis/plots.py @@ -5,14 +5,15 @@ import matplotlib.pyplot as plt import matplotlib.patheffects as pe #import seaborn as sns # tried it, the default look was worse -df = pd.read_csv("analysis/results.csv") +df = pd.read_csv("analysis/results_x86.csv") -# peaks for the ryzen ai 7 350 (zen5) at 2GHz. 2 fma units and the datapath is 256 bit +# peaks for the ryzen ai 7 350 (zen5) at 2GHz. 2 fma + 2 fadd units and the datapath is 256 bit # -> scalar 6, double 24, float 48 flops/cycle SCALAR = 6 PEAK_D = 24 PEAK_F = 48 maroon = "#A71D5B" +crimson = "#DC143C" def get(v, n=5000): @@ -20,13 +21,13 @@ def get(v, n=5000): def getline(v): - s = df[df.variant == v].sort_values("n") + s = df[df.variant == v].sort_values("n") # pyright: ignore[reportCallIssue] return s.n.values, s["flops_per_cycle"].values def label_lines_above(ax, entries, - fontsize=9, + fontsize: float = 9, rotate=False, dy_frac=0.012, halo=0): @@ -108,79 +109,66 @@ tracks = [ ("variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt", "AVX-256 + PolyExp + RSQRT (float)", "#d05a82", "--"), ("variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt", - "AVX-512 + PolyExp + RSQRT + ILP (float)", maroon, "-"), + "AVX-512 + PolyExp + RSQRT + ILP-Inner (float)", maroon, "-"), + ("variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp", + "AVX-512 + PolyExp + RSQRT + ILP-Outer (float)", crimson, "-"), ] -tick_vals = np.array([50, 100, 200, 500, 1000, 2000, 5000, 10000]) -target_positions = np.arange(len(tick_vals)) - - -def map_x_to_linear(x_val): - return np.interp(x_val, tick_vals, target_positions) - - fig, ax = plt.subplots(figsize=(9, 4.7)) ends = [] for v, lab, c, ls in tracks: - x_raw, y = getline(v) - x_mapped = map_x_to_linear(x_raw) + x, y = getline(v) + ax.plot( + x, + y, # pyright: ignore[reportArgumentType] + ls, + color=c, + marker="o", + ms=4, + lw=2.4 if c == maroon else 1.8) + if c == maroon: + side = "below" + xpos = 5600 + else: + side = "above" + xpos = None + ends.append((x, y, lab, c, xpos, side)) - ax.plot(x_mapped, - y, - ls, - color=c, - marker="o", - ms=4, - lw=2.4 if c == maroon else 1.8) - - ends.append((x_mapped, y, lab, c)) - -tick_vals = [50, 100, 200, 500, 1000, 2000, 5000, 10000] -target_positions = list(range(len(tick_vals))) - -start_x_pos = 0 - -for yv, t in [(PEAK_D, f"Peak π par. w/ doubles ({PEAK_D} flops/cycle)"), - (PEAK_F, f"Peak π par. w/ floats ({PEAK_F} flops/cycle)")]: +for yv, t in [ + (27.43, f"Peak π based on instruction mix [{27.43} flops/cycle]"), + # (PEAK_D, f"Peak π par. w/ doubles ({PEAK_D} flops/cycle)"), + # (PEAK_F, f"Peak π par. w/ floats ({PEAK_F} flops/cycle)") +]: ax.axhline(yv, ls=":", color="#999", lw=1.2) - ax.text(start_x_pos + 0.2, - yv * 1.03, - t, - fontsize=9, - color="#888", - fontweight="bold") + ax.text(52, yv * 1.03, t, fontsize=9, color="#888", fontweight="bold") -ax.set_yscale("log", base=2) - -ax.set_xticks(target_positions) -ax.set_xticklabels(["50", "100", "200", "500", "1k", "2k", "5k", "10k"], - fontweight="bold", - fontsize=9) - -ax.set_xlim(-0.5, len(tick_vals) - 0.5) - -ax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: str(int(x)))) +ax.set_xscale("log", base=10) +ax.set_xticks([50, 100, 200, 500, 1000, 2000, 5000, 10000]) +ax.set_xticklabels(["50", "100", "200", "500", "1K", "2K", "5K", "10K"]) +ax.set_xlim(46, 13000) +ax.set_ylim(0, 30) for label in ax.get_yticklabels(): label.set_fontweight('bold') label.set_fontsize(9) -ax.set_ylim(1, 64) +for label in ax.get_xticklabels(): + label.set_fontweight('bold') + label.set_fontsize(9) ax.set_xlabel("Number of Pedestrians", fontweight="bold", fontsize=9) ax.grid(color="#e9e9e9") ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) plt.title( - 'Performance [flops/cycle]\nCPU: AMD Ryzen AI 7 350 w/ Radeon 860M @ 2 GHz\nCompiler: g++ (Debian 15.2.0-17) 15.2.0\nFlags: -O3 -march=native -std=c++23 -Wall -Wextra -flto', + 'Performance (flops/cycle) [N = 3]\nCPU: AMD Ryzen AI 7 350 @ 2 GHz\nCompiler: g++ (Debian 15.2.0-17) 15.2.0\nFlags: -O3 -march=native -std=c++23 -Wall -Wextra -flto', loc='left', size=9, fontweight="bold") +ax.grid(color="#e9e9e9") plt.tight_layout() - -label_lines_above(ax, ends, fontsize=7.5, dy_frac=0.012) - +label_lines_above(ax, ends, fontsize=7.25, dy_frac=0.012) plt.savefig("analysis/fig_tracks.svg", bbox_inches="tight") -# plot 2: all te milestones + the symmetry one that got slower +# plot 2: all the milestones + the symmetry one that got slower bars = [ ("baseline", "variant_baseline"), ("AVX2 (double)", "variant_inlining_7_SoA_AVX2"), @@ -219,7 +207,7 @@ ax.grid(axis="y", visible=False) ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) plt.tight_layout() -plt.savefig("analysis/fig_ladder.png", dpi=170, bbox_inches="tight") +plt.savefig("analysis/fig_ladder.svg", bbox_inches="tight") # plot 3: roofline (n = 5000). same 7 variants as fig_tracks, same colors, inline labels. @@ -318,7 +306,7 @@ ax.set_xticklabels(ax.get_xticklabels(), fontweight="bold") ax.set_yticklabels(ax.get_yticklabels(), fontweight="bold") # ax.set_ylabel("Performance [flops/cycle]", fontweight="bold", fontsize=9) plt.title( - 'Performance [flops/cycle]\nCPU: AMD Ryzen AI 7 350 w/ Radeon 860M @ 2 GHz\nCompiler: g++ (Debian 15.2.0-17) 15.2.0\nFlags: -O3 -march=native -std=c++23 -Wall -Wextra -flto', + 'Performance [flops/cycle]\nCPU: AMD Ryzen AI 7 350 @ 2 GHz\nCompiler: g++ (Debian 15.2.0-17) 15.2.0\nFlags: -O3 -march=native -std=c++23 -Wall -Wextra -flto', loc='left', size=9, fontweight="bold") @@ -344,7 +332,7 @@ ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) ax.legend(frameon=False, fontsize=10) plt.tight_layout() -plt.savefig("analysis/fig_exp.png", dpi=170, bbox_inches="tight") +plt.savefig("analysis/fig_exp.svg", bbox_inches="tight") # plot 5: the milestone ladder as lines (this is plot0 + line1, line2, ... for the doc). # one line per relevant optimization step across n, baseline at the bottom. @@ -399,7 +387,7 @@ ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) plt.tight_layout() label_lines_above(ax, ends, fontsize=6.5, dy_frac=0.02, halo=0) -plt.savefig("analysis/fig_milestones.png", dpi=170, bbox_inches="tight") +plt.savefig("analysis/fig_milestones.svg", bbox_inches="tight") #plt.show() @@ -449,7 +437,8 @@ ax.set_xscale("log") ax.set_yscale("log") ax.set_xticks([50, 100, 200, 500, 1000, 2000, 5000]) -ax.set_xticklabels(["50", "100", "200", "500", "1k", "2k", "5k"], fontweight="bold") +ax.set_xticklabels(["50", "100", "200", "500", "1k", "2k", "5k"], + fontweight="bold") ax.set_yticks([1, 2, 5, 10, 20]) ax.set_yticklabels(["1x", "2x", "5x", "10x", "20x"], fontweight="bold") @@ -462,7 +451,7 @@ ax.set_ylim(0.8, 28) ax.set_xlabel("Number of Pedestrians", fontweight="bold", fontsize=9) # ax.set_ylabel("Speedup", fontweight="bold", fontsize=9) plt.title( - 'Runtime Speedup\nCPU: AMD Ryzen AI 7 350 w/ Radeon 860M @ 2 GHz\nCompiler: g++ (Debian 15.2.0-17) 15.2.0\nFlags: -O3 -march=native -std=c++23 -Wall -Wextra -flto', + 'Runtime Speedup\nCPU: AMD Ryzen AI 7 350 @ 2 GHz\nCompiler: g++ (Debian 15.2.0-17) 15.2.0\nFlags: -O3 -march=native -std=c++23 -Wall -Wextra -flto', loc='left', size=9, fontweight="bold") diff --git a/analysis/results_x86.csv b/analysis/results_x86.csv new file mode 100644 index 0000000..fc516ef --- /dev/null +++ b/analysis/results_x86.csv @@ -0,0 +1,316 @@ +variant,n,n_iter,W,Q_per_iter,I,cycles,flops_per_cycle +variant_AVX512_1,50,200,40690000,6512.0,31.24,12357341.2,3.293 +variant_AVX512_1,100,200,157220000,12912.0,60.88,42690045.0,3.683 +variant_AVX512_1,200,200,619080000,25712.0,120.39,149289520.0,4.147 +variant_AVX512_1,500,200,3826100000,64112.0,298.39,895752980.0,4.271 +variant_AVX512_1,1000,200,15255400000,128112.0,595.39,3427521146.7,4.451 +variant_AVX512_1,2000,200,60910800000,256112.0,1189.14,13524851386.7,4.504 +variant_AVX512_1,5000,200,380277000000,640112.0,2970.39,83786559000.0,4.539 +variant_AVX512_1,10000,200,1520554000000,1280112.0,5939.14,346902390090.0,4.383 +variant_baseline,50,200,36740000,6512.0,28.21,27452843.3,1.338 +variant_baseline,100,200,145480000,12912.0,56.34,108607106.7,1.34 +variant_baseline,200,200,578960000,25712.0,112.59,435167353.3,1.33 +variant_baseline,500,200,3607400000,64112.0,281.34,2807013360.0,1.285 +variant_baseline,1000,200,14414800000,128112.0,562.59,11381234466.7,1.267 +variant_baseline,2000,200,57629600000,256112.0,1125.09,46436426300.0,1.241 +variant_baseline,5000,200,360074000000,640112.0,2812.59,312703123826.7,1.151 +variant_branchless,50,200,37920000,6512.0,29.12,32528650.0,1.166 +variant_branchless,100,200,149840000,12912.0,58.02,130689786.7,1.147 +variant_branchless,200,200,595680000,25712.0,115.84,544820746.7,1.093 +variant_branchless,500,200,3709200000,64112.0,289.28,3560289460.0,1.042 +variant_branchless,1000,200,14818400000,128112.0,578.34,14393150806.7,1.03 +variant_branchless,2000,200,59236800000,256112.0,1156.46,59210476113.3,1.0 +variant_branchless,5000,200,370092000000,640112.0,2890.84,385464816373.3,0.96 +variant_branchless_sqrtfree,50,200,37920000,6512.0,29.12,31693365.0,1.196 +variant_branchless_sqrtfree,100,200,149840000,12912.0,58.02,124691126.7,1.202 +variant_branchless_sqrtfree,200,200,595680000,25712.0,115.84,494602046.7,1.204 +variant_branchless_sqrtfree,500,200,3709200000,64112.0,289.28,3078930946.7,1.205 +variant_branchless_sqrtfree,1000,200,14818400000,128112.0,578.34,12280066680.0,1.207 +variant_branchless_sqrtfree,2000,200,59236800000,256112.0,1156.46,49188518600.0,1.204 +variant_branchless_sqrtfree,5000,200,370092000000,640112.0,2890.84,308663505060.0,1.199 +variant_cutoff,50,200,36900000,6512.0,28.33,17730605.8,2.081 +variant_cutoff,100,200,145800000,12912.0,56.46,76248960.0,1.912 +variant_cutoff,200,200,579600000,25712.0,112.71,267962706.7,2.163 +variant_cutoff,500,200,3609000000,64112.0,281.46,1895310460.0,1.904 +variant_cutoff,1000,200,14418000000,128112.0,562.71,7727298866.7,1.866 +variant_cutoff,2000,200,57636000000,256112.0,1125.21,32411782673.3,1.778 +variant_cutoff,5000,200,360090000000,640112.0,2812.71,216604501193.3,1.662 +variant_cutoff_callsite,50,200,36900000,6512.0,28.33,16724109.2,2.206 +variant_cutoff_callsite,100,200,145800000,12912.0,56.46,71482436.7,2.04 +variant_cutoff_callsite,200,200,579600000,25712.0,112.71,254096566.7,2.281 +variant_cutoff_callsite,500,200,3609000000,64112.0,281.46,1819565306.7,1.983 +variant_cutoff_callsite,1000,200,14418000000,128112.0,562.71,7520631446.7,1.917 +variant_cutoff_callsite,2000,200,57636000000,256112.0,1125.21,31026276366.7,1.858 +variant_cutoff_callsite,5000,200,360090000000,640112.0,2812.71,211550331440.0,1.702 +variant_float_baseline,50,200,21950000,3256.0,33.71,24694491.3,0.889 +variant_float_baseline,100,200,86900000,6456.0,67.3,96417530.7,0.901 +variant_float_baseline,200,200,345800000,12856.0,134.49,383608277.3,0.901 +variant_float_baseline,500,200,2154500000,32056.0,336.05,2749099520.0,0.784 +variant_float_baseline,1000,200,8609000000,64056.0,671.99,11748012373.3,0.733 +variant_float_baseline,2000,200,34418000000,128056.0,1343.87,51879605589.3,0.663 +variant_float_baseline,5000,200,215045000000,320056.0,3359.49,369651034794.7,0.582 +variant_float_inlining,50,200,36750000,3256.0,56.43,20938618.7,1.755 +variant_float_inlining,100,200,145500000,6456.0,112.69,83407050.7,1.744 +variant_float_inlining,200,200,579000000,12856.0,225.19,338390005.3,1.711 +variant_float_inlining,500,200,3607500000,32056.0,562.69,2457371306.7,1.468 +variant_float_inlining,1000,200,14415000000,64056.0,1125.19,10667126784.0,1.351 +variant_float_inlining,2000,200,57630000000,128056.0,2250.19,47520261461.3,1.213 +variant_float_inlining,5000,200,360075000000,320056.0,5625.19,341753222485.3,1.054 +variant_float_inlining_SoA,50,200,36750000,3256.0,56.43,21048408.0,1.746 +variant_float_inlining_SoA,100,200,145500000,6456.0,112.69,84708077.3,1.718 +variant_float_inlining_SoA,200,200,579000000,12856.0,225.19,336827125.3,1.719 +variant_float_inlining_SoA,500,200,3607500000,32056.0,562.69,2477081600.0,1.456 +variant_float_inlining_SoA,1000,200,14415000000,64056.0,1125.19,10766873600.0,1.339 +variant_float_inlining_SoA,2000,200,57630000000,128056.0,2250.19,47988983125.3,1.201 +variant_float_inlining_SoA,5000,200,360075000000,320056.0,5625.19,345895436288.0,1.041 +variant_float_inlining_SoA_AVX2,50,200,42520000,3256.0,65.29,9238852.3,4.602 +variant_float_inlining_SoA_AVX2,100,200,155120000,6456.0,120.14,31443590.0,4.933 +variant_float_inlining_SoA_AVX2,200,200,590560000,12856.0,229.68,117080762.7,5.044 +variant_float_inlining_SoA_AVX2,500,200,3695600000,32056.0,576.43,715304192.0,5.166 +variant_float_inlining_SoA_AVX2,1000,200,14632800000,64056.0,1142.19,2914458538.7,5.021 +variant_float_inlining_SoA_AVX2,2000,200,58465600000,128056.0,2282.81,12570639018.7,4.651 +variant_float_inlining_SoA_AVX2,5000,200,365164000000,320056.0,5704.69,91014157653.3,4.012 +variant_float_inlining_SoA_AVX2,10000,200,1460328000000,640056.0,11407.81,410624917504.0,3.556 +variant_float_inlining_SoA_AVX2_polyexp,50,200,42520000,3256.0,65.29,5861893.7,7.254 +variant_float_inlining_SoA_AVX2_polyexp,100,200,155120000,6456.0,120.14,19739642.7,7.858 +variant_float_inlining_SoA_AVX2_polyexp,200,200,590560000,12856.0,229.68,71656157.3,8.242 +variant_float_inlining_SoA_AVX2_polyexp,500,200,3695600000,32056.0,576.43,435824629.3,8.48 +variant_float_inlining_SoA_AVX2_polyexp,1000,200,14632800000,64056.0,1142.19,1699931264.0,8.608 +variant_float_inlining_SoA_AVX2_polyexp,2000,200,58465600000,128056.0,2282.81,6739927722.7,8.675 +variant_float_inlining_SoA_AVX2_polyexp,5000,200,365164000000,320056.0,5704.69,42246128981.3,8.644 +variant_float_inlining_SoA_AVX2_polyexp,10000,200,1460328000000,640056.0,11407.81,167398135125.3,8.724 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt,50,200,44760000,3256.0,68.73,5092671.3,8.789 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt,100,200,163440000,6456.0,126.58,16935302.7,9.651 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt,200,200,622560000,12856.0,242.13,60867113.3,10.228 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt,500,200,3897200000,32056.0,607.87,368188341.3,10.585 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt,1000,200,15432800000,64056.0,1204.63,1433606229.3,10.765 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt,2000,200,61665600000,128056.0,2407.76,5675097941.3,10.866 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt,5000,200,385164000000,320056.0,6017.13,35604179626.7,10.818 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt,10000,200,1540328000000,640056.0,12032.76,142421229568.0,10.815 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt,50,200,44200000,3256.0,67.87,4881582.7,9.054 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt,100,200,161360000,6456.0,124.97,15658073.7,10.305 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt,200,200,614560000,12856.0,239.02,55530342.7,11.067 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt,500,200,3846800000,32056.0,600.01,332591989.3,11.566 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt,1000,200,15232800000,64056.0,1189.02,1292824832.0,11.783 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt,2000,200,60865600000,128056.0,2376.52,5134484992.0,11.854 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt,5000,200,380164000000,320056.0,5939.02,32074893312.0,11.852 +variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt,10000,200,1520328000000,640056.0,11876.52,128325312512.0,11.847 +variant_float_inlining_SoA_AVX512_polyexp,50,200,48510000,3256.0,74.49,4637544.0,10.46 +variant_float_inlining_SoA_AVX512_polyexp,100,200,167100000,6456.0,129.41,14045100.0,11.897 +variant_float_inlining_SoA_AVX512_polyexp,200,200,614520000,12856.0,239.0,47525244.0,12.93 +variant_float_inlining_SoA_AVX512_polyexp,500,200,3755500000,32056.0,585.77,272034645.3,13.805 +variant_float_inlining_SoA_AVX512_polyexp,1000,200,14752600000,64056.0,1151.54,1048487445.3,14.07 +variant_float_inlining_SoA_AVX512_polyexp,2000,200,58471600000,128056.0,2283.05,4093863424.0,14.283 +variant_float_inlining_SoA_AVX512_polyexp,5000,200,365763000000,320056.0,5714.05,25352017237.3,14.427 +variant_float_inlining_SoA_AVX512_polyexp,10000,200,1460358000000,640056.0,11408.05,100878147584.0,14.476 +variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt,50,200,48640000,3256.0,74.69,4900494.3,9.926 +variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt,100,200,167360000,6456.0,129.62,14697632.0,11.387 +variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt,200,200,615040000,12856.0,239.2,48755792.0,12.615 +variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt,500,200,3756800000,32056.0,585.97,275589760.0,13.632 +variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt,1000,200,14755200000,64056.0,1151.74,1056675861.3,13.964 +variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt,2000,200,58476800000,128056.0,2283.25,4125949952.0,14.173 +variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt,5000,200,365776000000,320056.0,5714.25,25582010368.0,14.298 +variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt,10000,200,1460384000000,640056.0,11408.25,101411954688.0,14.401 +variant_float_inlining_SoA_AVX512_polyexp_moreavx,50,200,48565600,3256.0,74.58,4427194.8,10.97 +variant_float_inlining_SoA_AVX512_polyexp_moreavx,100,200,167124800,6456.0,129.43,13805986.0,12.105 +variant_float_inlining_SoA_AVX512_polyexp_moreavx,200,200,614483200,12856.0,238.99,47360122.7,12.975 +variant_float_inlining_SoA_AVX512_polyexp_moreavx,500,200,3755364800,32056.0,585.75,275211349.3,13.645 +variant_float_inlining_SoA_AVX512_polyexp_moreavx,1000,200,14752243200,64056.0,1151.51,1064273109.3,13.861 +variant_float_inlining_SoA_AVX512_polyexp_moreavx,2000,200,58470800000,128056.0,2283.02,4162095445.3,14.048 +variant_float_inlining_SoA_AVX512_polyexp_moreavx,5000,200,365761043200,320056.0,5714.02,26013326677.3,14.061 +variant_float_inlining_SoA_AVX512_polyexp_moreavx,10000,200,1460354000000,640056.0,11408.02,103437175466.7,14.118 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2,50,200,48885600,3256.0,75.07,4118233.3,11.871 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2,100,200,191211200,6456.0,148.09,14171592.7,13.493 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2,200,200,662569600,12856.0,257.69,46241824.0,14.328 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2,500,200,3758564800,32056.0,586.25,250078272.0,15.03 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2,1000,200,14992329600,64056.0,1170.25,978147584.0,15.327 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2,2000,200,58950886400,128056.0,2301.76,3802809770.7,15.502 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2,5000,200,366961129600,320056.0,5732.76,23565800789.3,15.572 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2,10000,200,1462754086400,640056.0,11426.77,93711062357.3,15.609 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp,50,200,48885600,3256.0,75.07,4128367.9,11.841 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp,100,200,191211200,6456.0,148.09,14211595.0,13.455 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp,200,200,662569600,12856.0,257.69,46258992.0,14.323 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp,500,200,3758564800,32056.0,586.25,249568197.3,15.06 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp,1000,200,14992329600,64056.0,1170.25,975399104.0,15.37 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp,2000,200,58950886400,128056.0,2301.76,3793620480.0,15.539 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp,5000,200,366961129600,320056.0,5732.76,23545909930.7,15.585 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp,10000,200,1462754086400,640056.0,11426.77,93541878442.7,15.637 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt,50,200,48936800,3256.0,75.15,4115576.2,11.891 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt,100,200,191313600,6456.0,148.17,14176439.7,13.495 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt,200,200,662748800,12856.0,257.76,46456041.3,14.266 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt,500,200,3758974400,32056.0,586.31,250008314.7,15.035 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt,1000,200,14993148800,64056.0,1170.32,980856746.7,15.286 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt,2000,200,58952499200,128056.0,2301.82,3823654485.3,15.418 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt,5000,200,366965148800,320056.0,5732.83,23661115392.0,15.509 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt,10000,200,1462762099200,640056.0,11426.83,94207066112.0,15.527 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt,50,200,51458400,3256.0,79.02,3483682.3,14.771 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt,100,200,201476800,6456.0,156.04,11811947.3,17.057 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt,200,200,698454400,12856.0,271.65,38043610.7,18.359 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt,500,200,3963467200,32056.0,618.21,203642453.3,19.463 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt,1000,200,15811734400,64056.0,1234.21,812575168.0,19.459 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt,2000,200,62176889600,128056.0,2427.72,3099994965.3,20.057 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt,5000,200,387058134400,320056.0,6046.73,19214568789.3,20.144 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt,10000,200,1542884089600,640056.0,12052.73,76092566186.7,20.276 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved,50,200,51618400,3256.0,79.27,3425376.1,15.069 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved,100,200,201796800,6456.0,156.29,11372835.7,17.744 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved,200,200,699094400,12856.0,271.89,36467076.0,19.171 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved,500,200,3965067200,32056.0,618.46,195375242.7,20.295 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved,1000,200,15814934400,64056.0,1234.46,757288490.7,20.884 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved,2000,200,62183289600,128056.0,2427.97,2960859477.3,21.002 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved,5000,200,387074134400,320056.0,6046.98,18368346112.0,21.073 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved,10000,200,1542916089600,640056.0,12052.98,72963047424.0,21.147 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp,50,200,51238400,3256.0,78.68,2439821.7,21.001 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp,100,200,175907200,6456.0,136.24,8335320.3,21.104 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp,200,200,647004800,12856.0,251.64,30687906.7,21.083 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp,500,200,3958067200,32056.0,617.37,186111381.3,21.267 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp,1000,200,15554044800,64056.0,1214.1,732997781.3,21.22 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp,2000,200,61661200000,128056.0,2407.59,2924175018.7,21.087 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp,5000,200,385769244800,320056.0,6026.59,18349806250.7,21.023 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp,10000,200,1540306000000,640056.0,12032.59,73231152469.3,21.033 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4,50,200,49525600,3256.0,76.05,3890111.5,12.731 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4,100,200,192491200,6456.0,149.08,13501827.7,14.257 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4,200,200,758742400,12856.0,295.09,49975976.0,15.182 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4,500,200,3764964800,32056.0,587.25,239680656.0,15.708 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4,1000,200,15005129600,64056.0,1171.25,945644394.7,15.868 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4,2000,200,59911059200,128056.0,2339.25,3715754325.3,16.124 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4,5000,200,369361302400,320056.0,5770.26,22745182890.7,16.239 +variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4,10000,200,1467554259200,640056.0,11464.26,90144694272.0,16.28 +variant_float_inlining_SoA_symmetry,50,200,35280000,3256.0,54.18,20621257.3,1.711 +variant_float_inlining_SoA_symmetry,100,200,139560000,6456.0,108.09,81476053.3,1.713 +variant_float_inlining_SoA_symmetry,200,200,555120000,12856.0,215.9,333481098.7,1.665 +variant_float_inlining_SoA_symmetry,500,200,3457800000,32056.0,539.34,2568502357.3,1.346 +variant_float_inlining_SoA_symmetry,1000,200,13815600000,64056.0,1078.4,11066851669.3,1.248 +variant_float_inlining_SoA_symmetry,2000,200,55231200000,128056.0,2156.53,49006380373.3,1.127 +variant_float_inlining_SoA_symmetry,5000,200,345078000000,320056.0,5390.9,349641758037.3,0.987 +variant_float_inlining_SoA_symmetry_ILP_16,50,200,35920000,3256.0,55.16,17984374.0,1.997 +variant_float_inlining_SoA_symmetry_ILP_16,100,200,140840000,6456.0,109.08,64325713.3,2.189 +variant_float_inlining_SoA_symmetry_ILP_16,200,200,557680000,12856.0,216.89,252748229.3,2.206 +variant_float_inlining_SoA_symmetry_ILP_16,500,200,3464200000,32056.0,540.34,1863017770.7,1.859 +variant_float_inlining_SoA_symmetry_ILP_16,1000,200,13828400000,64056.0,1079.4,8033498282.7,1.721 +variant_float_inlining_SoA_symmetry_ILP_16,2000,200,55256800000,128056.0,2157.52,33723052032.0,1.639 +variant_float_inlining_SoA_symmetry_ILP_16,5000,200,345142000000,320056.0,5391.9,219469376170.7,1.573 +variant_float_inlining_SoA_symmetry_ILP_2,50,200,35360000,3256.0,54.3,18593934.7,1.902 +variant_float_inlining_SoA_symmetry_ILP_2,100,200,139720000,6456.0,108.21,73156496.0,1.91 +variant_float_inlining_SoA_symmetry_ILP_2,200,200,555440000,12856.0,216.02,293520213.3,1.892 +variant_float_inlining_SoA_symmetry_ILP_2,500,200,3458600000,32056.0,539.46,2178498048.0,1.588 +variant_float_inlining_SoA_symmetry_ILP_2,1000,200,13817200000,64056.0,1078.53,9321413973.3,1.482 +variant_float_inlining_SoA_symmetry_ILP_2,2000,200,55234400000,128056.0,2156.65,40795664384.0,1.354 +variant_float_inlining_SoA_symmetry_ILP_2,5000,200,345086000000,320056.0,5391.03,287744611669.3,1.199 +variant_float_inlining_SoA_symmetry_ILP_4,50,200,35440000,3256.0,54.42,17059464.0,2.077 +variant_float_inlining_SoA_symmetry_ILP_4,100,200,139880000,6456.0,108.33,65813640.0,2.125 +variant_float_inlining_SoA_symmetry_ILP_4,200,200,555760000,12856.0,216.15,266939221.3,2.082 +variant_float_inlining_SoA_symmetry_ILP_4,500,200,3459400000,32056.0,539.59,1956788906.7,1.768 +variant_float_inlining_SoA_symmetry_ILP_4,1000,200,13818800000,64056.0,1078.65,8363417088.0,1.652 +variant_float_inlining_SoA_symmetry_ILP_4,2000,200,55237600000,128056.0,2156.78,35347298986.7,1.563 +variant_float_inlining_SoA_symmetry_ILP_4,5000,200,345094000000,320056.0,5391.15,238227024554.7,1.449 +variant_float_inlining_SoA_symmetry_ILP_8,50,200,35600000,3256.0,54.67,15194553.3,2.343 +variant_float_inlining_SoA_symmetry_ILP_8,100,200,140200000,6456.0,108.58,56247389.3,2.493 +variant_float_inlining_SoA_symmetry_ILP_8,200,200,556400000,12856.0,216.4,226057952.0,2.461 +variant_float_inlining_SoA_symmetry_ILP_8,500,200,3461000000,32056.0,539.84,1703800277.3,2.031 +variant_float_inlining_SoA_symmetry_ILP_8,1000,200,13822000000,64056.0,1078.9,7384531456.0,1.872 +variant_float_inlining_SoA_symmetry_ILP_8,2000,200,55244000000,128056.0,2157.03,31106564096.0,1.776 +variant_float_inlining_SoA_symmetry_ILP_8,5000,200,345110000000,320056.0,5391.4,205839695872.0,1.677 +variant_inlining_7,50,200,36910000,6512.0,28.34,26121151.7,1.413 +variant_inlining_7,100,200,145820000,12912.0,56.47,102845253.3,1.418 +variant_inlining_7,200,200,579640000,25712.0,112.72,414933826.7,1.397 +variant_inlining_7,500,200,3609100000,64112.0,281.47,2696322800.0,1.339 +variant_inlining_7,1000,200,14418200000,128112.0,562.72,10910614700.0,1.321 +variant_inlining_7,2000,200,57636400000,256112.0,1125.22,44697241900.0,1.289 +variant_inlining_7,5000,200,360091000000,640112.0,2812.72,301883852420.0,1.193 +variant_inlining_7_SoA,50,200,36910000,6512.0,28.34,35321441.7,1.045 +variant_inlining_7_SoA,100,200,145820000,12912.0,56.47,135295173.3,1.078 +variant_inlining_7_SoA,200,200,579640000,25712.0,112.72,568212960.0,1.02 +variant_inlining_7_SoA,500,200,3609100000,64112.0,281.47,3522666700.0,1.025 +variant_inlining_7_SoA,1000,200,14418200000,128112.0,562.72,13818697526.7,1.043 +variant_inlining_7_SoA,2000,200,57636400000,256112.0,1125.22,55165644633.3,1.045 +variant_inlining_7_SoA,5000,200,360091000000,640112.0,2812.72,339567715040.0,1.06 +variant_inlining_7_SoA_AVX2,50,200,39660000,6512.0,30.45,16930248.3,2.343 +variant_inlining_7_SoA_AVX2,100,200,149400000,12912.0,57.85,57593386.7,2.594 +variant_inlining_7_SoA_AVX2,200,200,590800000,25712.0,114.89,221606086.7,2.666 +variant_inlining_7_SoA_AVX2,500,200,3667000000,64112.0,285.98,1355735366.7,2.705 +variant_inlining_7_SoA_AVX2,1000,200,14634000000,128112.0,571.14,5363998226.7,2.728 +variant_inlining_7_SoA_AVX2,2000,200,58468000000,256112.0,1141.45,21340824240.0,2.74 +variant_inlining_7_SoA_AVX2,5000,200,365170000000,640112.0,2852.39,118313683993.3,3.086 +variant_inlining_7_SoA_AVX2,10000,200,1460340000000,1280112.0,5703.95,483403608680.0,3.021 +variant_inlining_7_SoA_AVX2_polyexp,50,200,39660000,6512.0,30.45,11360558.3,3.491 +variant_inlining_7_SoA_AVX2_polyexp,100,200,149400000,12912.0,57.85,39974676.7,3.737 +variant_inlining_7_SoA_AVX2_polyexp,200,200,590800000,25712.0,114.89,152454126.7,3.875 +variant_inlining_7_SoA_AVX2_polyexp,500,200,3667000000,64112.0,285.98,924841613.3,3.965 +variant_inlining_7_SoA_AVX2_polyexp,1000,200,14634000000,128112.0,571.14,3677548486.7,3.979 +variant_inlining_7_SoA_AVX2_polyexp,2000,200,58468000000,256112.0,1141.45,14646151666.7,3.992 +variant_inlining_7_SoA_AVX2_polyexp,5000,200,365170000000,640112.0,2852.39,91635680486.7,3.985 +variant_inlining_7_SoA_AVX2_polyexp,10000,200,1460340000000,1280112.0,5703.95,362144000826.7,4.032 +variant_inlining_7_symmetry_1,50,200,35440000,6512.0,27.21,26691613.3,1.328 +variant_inlining_7_symmetry_1,100,200,139880000,12912.0,54.17,104115826.7,1.344 +variant_inlining_7_symmetry_1,200,200,555760000,25712.0,108.07,420221973.3,1.323 +variant_inlining_7_symmetry_1,500,200,3459400000,64112.0,269.79,2784195260.0,1.243 +variant_inlining_7_symmetry_1,1000,200,13818800000,128112.0,539.32,11440548280.0,1.208 +variant_inlining_7_symmetry_1,2000,200,55237600000,256112.0,1078.39,46724088026.7,1.182 +variant_inlining_7_symmetry_1,5000,200,345094000000,640112.0,2695.58,208238979073.3,1.657 +variant_inlining_7_symmetry_2,50,200,35440000,6512.0,27.21,26825500.0,1.321 +variant_inlining_7_symmetry_2,100,200,139880000,12912.0,54.17,104047720.0,1.344 +variant_inlining_7_symmetry_2,200,200,555760000,25712.0,108.07,418572093.3,1.328 +variant_inlining_7_symmetry_2,500,200,3459400000,64112.0,269.79,2796809913.3,1.237 +variant_inlining_7_symmetry_2,1000,200,13818800000,128112.0,539.32,11473883373.3,1.204 +variant_inlining_7_symmetry_2,2000,200,55237600000,256112.0,1078.39,47218873140.0,1.17 +variant_inlining_7_symmetry_2,5000,200,345094000000,640112.0,2695.58,212940538246.7,1.621 +variant_inlining_7_symmetry_2_ilp_1,50,200,35520000,6512.0,27.27,26576390.0,1.337 +variant_inlining_7_symmetry_2_ilp_1,100,200,140040000,12912.0,54.23,102705346.7,1.364 +variant_inlining_7_symmetry_2_ilp_1,200,200,556080000,25712.0,108.14,414853306.7,1.34 +variant_inlining_7_symmetry_2_ilp_1,500,200,3460200000,64112.0,269.86,2745536040.0,1.26 +variant_inlining_7_symmetry_2_ilp_1,1000,200,13820400000,128112.0,539.39,11346744286.7,1.218 +variant_inlining_7_symmetry_2_ilp_1,2000,200,55240800000,256112.0,1078.45,45827858580.0,1.205 +variant_inlining_7_symmetry_2_ilp_1,5000,200,345102000000,640112.0,2695.64,216145504520.0,1.597 +variant_inlining_7_symmetry_2_ilp_2,50,200,35600000,6512.0,27.33,26837608.3,1.326 +variant_inlining_7_symmetry_2_ilp_2,100,200,140200000,12912.0,54.29,104635440.0,1.34 +variant_inlining_7_symmetry_2_ilp_2,200,200,556400000,25712.0,108.2,421424786.7,1.32 +variant_inlining_7_symmetry_2_ilp_2,500,200,3461000000,64112.0,269.92,2800723726.7,1.236 +variant_inlining_7_symmetry_2_ilp_2,1000,200,13822000000,128112.0,539.45,11627472146.7,1.189 +variant_inlining_7_symmetry_2_ilp_2,2000,200,55244000000,256112.0,1078.51,47722643580.0,1.158 +variant_inlining_7_symmetry_2_ilp_2,5000,200,345110000000,640112.0,2695.7,316224530286.7,1.091 +variant_inlining_8,50,200,36750000,6512.0,28.22,25967831.7,1.415 +variant_inlining_8,100,200,145500000,12912.0,56.34,102344446.7,1.422 +variant_inlining_8,200,200,579000000,25712.0,112.59,412337820.0,1.404 +variant_inlining_8,500,200,3607500000,64112.0,281.34,2712489206.7,1.33 +variant_inlining_8,1000,200,14415000000,128112.0,562.59,10911941346.7,1.321 +variant_inlining_8,2000,200,57630000000,256112.0,1125.09,44617296533.3,1.292 +variant_inlining_8,5000,200,360075000000,640112.0,2812.59,302676303926.7,1.19 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt,50,200,42720000,6512.0,32.8,8373508.8,5.102 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt,100,200,155520000,12912.0,60.22,28637320.0,5.431 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt,200,200,591360000,25712.0,115.0,104580140.0,5.655 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt,500,200,3697600000,64112.0,288.37,637315293.3,5.802 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt,1000,200,14636800000,128112.0,571.25,2508058493.3,5.836 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt,2000,200,58473600000,256112.0,1141.56,9964964006.7,5.868 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt,5000,200,365184000000,640112.0,2852.5,61862883653.3,5.903 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt,10000,200,1460368000000,1280112.0,5704.06,246835017580.0,5.916 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr,50,200,42720000,6512.0,32.8,8324976.2,5.132 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr,100,200,155520000,12912.0,60.22,28461063.3,5.464 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr,200,200,591360000,25712.0,115.0,104197200.0,5.675 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr,500,200,3697600000,64112.0,288.37,634698826.7,5.826 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr,1000,200,14636800000,128112.0,571.25,2497438973.3,5.861 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr,2000,200,58473600000,256112.0,1141.56,9905258186.7,5.903 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr,5000,200,365184000000,640112.0,2852.5,61800905773.3,5.909 +variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr,10000,200,1460368000000,1280112.0,5704.06,246638909100.0,5.921 +variant_inlining_8_SoA_AVX512_polyexp_moreavx,50,200,42522400,6512.0,32.65,8002191.7,5.314 +variant_inlining_8_SoA_AVX512_polyexp_moreavx,100,200,155081600,12912.0,60.05,27743588.3,5.59 +variant_inlining_8_SoA_AVX512_polyexp_moreavx,200,200,590440000,25712.0,114.82,102487693.3,5.761 +variant_inlining_8_SoA_AVX512_polyexp_moreavx,500,200,3695321600,64112.0,288.19,632610206.7,5.841 +variant_inlining_8_SoA_AVX512_polyexp_moreavx,1000,200,14632200000,128112.0,571.07,2470303860.0,5.923 +variant_inlining_8_SoA_AVX512_polyexp_moreavx,2000,200,58464400000,256112.0,1141.38,9838694466.7,5.942 +variant_inlining_8_SoA_AVX512_polyexp_moreavx,5000,200,365161000000,640112.0,2852.32,61438052786.7,5.944 +variant_inlining_8_SoA_AVX512_polyexp_moreavx,10000,200,1460322000000,1280112.0,5703.88,245546828340.0,5.947 +variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2,50,200,48565600,6512.0,37.29,8523056.7,5.698 +variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2,100,200,167124800,12912.0,64.72,27731721.7,6.026 +variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2,200,200,614483200,25712.0,119.49,98272536.7,6.253 +variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2,500,200,3755364800,64112.0,292.88,588475846.7,6.382 +variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2,1000,200,14752243200,128112.0,575.76,2281054360.0,6.467 +variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2,2000,200,58470800000,256112.0,1141.51,8988870633.3,6.505 +variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2,5000,200,365761043200,640112.0,2857.01,56207738100.0,6.507 +variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2,10000,200,1460354000000,1280112.0,5704.01,224126171313.3,6.516 +variant_lut_exp,50,200,36900000,6512.0,28.33,28594623.3,1.29 +variant_lut_exp,100,200,145800000,12912.0,56.46,108870166.7,1.339 +variant_lut_exp,200,200,579600000,25712.0,112.71,435786306.7,1.33 +variant_lut_exp,500,200,3609000000,64112.0,281.46,2898102033.3,1.245 +variant_lut_exp,1000,200,14418000000,128112.0,562.71,11825768686.7,1.219 +variant_lut_exp,2000,200,57636000000,256112.0,1125.21,47853617373.3,1.204 +variant_lut_exp,5000,200,360090000000,640112.0,2812.71,305260944300.0,1.18 diff --git a/benchmarks/.gitkeep b/benchmarks/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/flopcounts/x86/variant_AVX512_1.py b/flopcounts/x86/variant_AVX512_1.py new file mode 100644 index 0000000..bbb40bb --- /dev/null +++ b/flopcounts/x86/variant_AVX512_1.py @@ -0,0 +1,100 @@ +# ADDs: n_iter * n_p * 4 + +# n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 / (ceil(((double) (n_p - 7)) / 8))) * 7 * 14 + +# n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 - 1 / (ceil(((double) (n_p - 7)) / 8))) * 224 + +# n_iter * n_p * (n_p - floor((double) n_p / 8) * 8) * 14 + +# n_iter * n_p * board.n_obstacles * 12 + +# n_iter * n_p * 7 + +# MULTs: n_iter * n_p * 4 + +# n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 / (ceil(((double) (n_p - 7)) / 8))) * 7 * 24 + +# n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 - 1 / (ceil(((double) (n_p - 7)) / 8))) * 352 + +# n_iter * n_p * (n_p - floor((double) n_p / 8) * 8) * 24 + +# n_iter * n_p * board.n_obstacles * 19 + +# n_iter * n_p * 17 + +# DIVs: n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 / (ceil(((double) (n_p - 7)) / 8))) * 7 + +# n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 - 1 / (ceil(((double) (n_p - 7)) / 8))) * 8 + +# n_iter * n_p * (n_p - floor((double) n_p / 8) * 8) + +# n_iter * n_p * board.n_obstacles + +# n_iter * n_p * 2 + +# SQRTs: n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 / (ceil(((double) (n_p - 7)) / 8))) * 7 * 3 + +# n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 - 1 / (ceil(((double) (n_p - 7)) / 8))) * 24 + +# n_iter * n_p * (n_p - floor((double) n_p / 8) * 8) * 3 + +# n_iter * n_p * board.n_obstacles + +# n_iter * n_p * 3 + +# EXPs: n_iter * n_p * ceil(((double) (n_p - 7)) / 8) * (1 / (ceil(((double) (n_p - 7)) / 8))) * 7 + +# n_iter * n_p * (n_p - floor((double) n_p / 8) * 8) + +# n_iter * n_p * board.n_obstacles + +from math import ceil + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + chunks = ceil((n_p - 7) / 8) if n_p > 7 else 0 + remainder = n_p - int(n_p // 8) * 8 + if chunks > 0: + term1 = n_iter * n_p * chunks * (1 / chunks) * 7 * 14 + term2 = n_iter * n_p * chunks * (1 - 1 / chunks) * 224 + else: + term1 = 0 + term2 = 0 + return int(n_iter * n_p * 4 + term1 + term2 + n_iter * n_p * remainder * 14 + + n_iter * n_p * n_obs * 12 + n_iter * n_p * 7) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + chunks = ceil((n_p - 7) / 8) if n_p > 7 else 0 + remainder = n_p - int(n_p // 8) * 8 + if chunks > 0: + term1 = n_iter * n_p * chunks * (1 / chunks) * 7 * 24 + term2 = n_iter * n_p * chunks * (1 - 1 / chunks) * 352 + else: + term1 = 0 + term2 = 0 + return int(n_iter * n_p * 4 + term1 + term2 + n_iter * n_p * remainder * 24 + + n_iter * n_p * n_obs * 19 + n_iter * n_p * 17) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + chunks = ceil((n_p - 7) / 8) if n_p > 7 else 0 + remainder = n_p - int(n_p // 8) * 8 + if chunks > 0: + term1 = n_iter * n_p * chunks * (1 / chunks) * 7 + term2 = n_iter * n_p * chunks * (1 - 1 / chunks) * 8 + else: + term1 = 0 + term2 = 0 + return int(n_iter * n_p * 4 + term1 + term2 + n_iter * n_p * remainder + + n_iter * n_p * n_obs + n_iter * n_p * 2) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + chunks = ceil((n_p - 7) / 8) if n_p > 7 else 0 + remainder = n_p - int(n_p // 8) * 8 + if chunks > 0: + term1 = n_iter * n_p * chunks * (1 / chunks) * 7 * 3 + term2 = n_iter * n_p * chunks * (1 - 1 / chunks) * 24 + else: + term1 = 0 + term2 = 0 + return int(n_iter * n_p * 4 + term1 + term2 + n_iter * n_p * remainder * 3 + + n_iter * n_p * n_obs + n_iter * n_p * 3) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + chunks = ceil((n_p - 7) / 8) if n_p > 7 else 0 + remainder = n_p - int(n_p // 8) * 8 + if chunks > 0: + term1 = n_iter * n_p * chunks * (1 / chunks) * 7 + else: + term1 = 0 + return int(n_iter * n_p * 4 + term1 + n_iter * n_p * remainder + + n_iter * n_p * n_obs + n_iter * n_p * 3) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_baseline.py b/flopcounts/x86/variant_baseline.py new file mode 100644 index 0000000..adccae7 --- /dev/null +++ b/flopcounts/x86/variant_baseline.py @@ -0,0 +1,58 @@ +# ADDs: n_iter * ( +# n_p * 4 + +# n_p * (n_p - 1) * 14 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 +# n_p * (n_p - 1) * 24 + +# n_p * board.n_obstacles * 13 + +# n_p * 16 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * (n_p - 1) * 3 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 14 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 24 + n_p * n_obs * 13 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_branchless.py b/flopcounts/x86/variant_branchless.py new file mode 100644 index 0000000..36f1ca3 --- /dev/null +++ b/flopcounts/x86/variant_branchless.py @@ -0,0 +1,27 @@ +# n_iter * [n_p ** 2 * (15 A + 24 M + 1 D + 4 S + 1 E) + n_p * (- 4 A - 4 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (13 A + 19 M + 1 D + 2 S + 1 E)] + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 15 + n_p * (-4) + n_obs * n_p * 13)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 24 + n_p * (-4) + n_obs * n_p * 19)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * 1 + n_obs * n_p * 1)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 4 + n_p * (-1) + n_obs * n_p * 2)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * (-1) + n_obs * n_p * 1)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_branchless_sqrtfree.py b/flopcounts/x86/variant_branchless_sqrtfree.py new file mode 100644 index 0000000..4c9158a --- /dev/null +++ b/flopcounts/x86/variant_branchless_sqrtfree.py @@ -0,0 +1,27 @@ +# n_iter * [n_p ** 2 * (15 A + 25 M + 1 D + 3 S + 1 E) + n_p * (- 4 A - 5 M + 1 D - 1 E) + n_obstacles * n_p * (13 A + 20 M + 1 D + 1 S + 1 E)] + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 15 + n_p * (-4) + n_obs * n_p * 13)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 25 + n_p * (-5) + n_obs * n_p * 20)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * 1 + n_obs * n_p * 1)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 3 + n_obs * n_p * 1)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * (-1) + n_obs * n_p * 1)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_cutoff.py b/flopcounts/x86/variant_cutoff.py new file mode 100644 index 0000000..cd3a418 --- /dev/null +++ b/flopcounts/x86/variant_cutoff.py @@ -0,0 +1,27 @@ +# n_iter * [ n_p ** 2 * (14 A + 23 M + 1 D + 4 S + 1 E) + n_p * (- 3 A - 3 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (12 A + 18 M + 1 D + 2 S + 1 E) ] + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 14 + n_p * (-3) + n_obs * n_p * 12)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 23 + n_p * (-3) + n_obs * n_p * 18)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * 1 + n_obs * n_p * 1)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 4 + n_p * (-1) + n_obs * n_p * 2)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * (-1) + n_obs * n_p * 1)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_cutoff_callsite.py b/flopcounts/x86/variant_cutoff_callsite.py new file mode 100644 index 0000000..cd3a418 --- /dev/null +++ b/flopcounts/x86/variant_cutoff_callsite.py @@ -0,0 +1,27 @@ +# n_iter * [ n_p ** 2 * (14 A + 23 M + 1 D + 4 S + 1 E) + n_p * (- 3 A - 3 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (12 A + 18 M + 1 D + 2 S + 1 E) ] + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 14 + n_p * (-3) + n_obs * n_p * 12)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 23 + n_p * (-3) + n_obs * n_p * 18)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * 1 + n_obs * n_p * 1)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 4 + n_p * (-1) + n_obs * n_p * 2)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * (-1) + n_obs * n_p * 1)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_baseline.py b/flopcounts/x86/variant_float_baseline.py new file mode 100644 index 0000000..c41efdf --- /dev/null +++ b/flopcounts/x86/variant_float_baseline.py @@ -0,0 +1,57 @@ +# ADDs: n_iter * ( +# n_p * 4 + +# n_p * (n_p - 1) * 14 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 +# n_p * (n_p - 1) * 24 + +# n_p * board.n_obstacles * 13 + +# n_p * 16 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * (n_p - 1) * 3 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 14 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 24 + n_p * n_obs * 13 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, n_obs) + exps(n_iter, n_p, n_obs) diff --git a/flopcounts/x86/variant_float_inlining.py b/flopcounts/x86/variant_float_inlining.py new file mode 100644 index 0000000..164f8b1 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining.py @@ -0,0 +1,58 @@ +# ADDs: n_iter * ( +# n_p * 4 + +# n_p * (n_p - 1) * 14 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 +# n_p * (n_p - 1) * 24 + +# n_p * board.n_obstacles * 13 + +# n_p * 17 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * (n_p - 1) * 3 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 14 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 24 + n_p * n_obs * 13 + n_p * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA.py b/flopcounts/x86/variant_float_inlining_SoA.py new file mode 100644 index 0000000..164f8b1 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA.py @@ -0,0 +1,58 @@ +# ADDs: n_iter * ( +# n_p * 4 + +# n_p * (n_p - 1) * 14 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 +# n_p * (n_p - 1) * 24 + +# n_p * board.n_obstacles * 13 + +# n_p * 17 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * (n_p - 1) * 3 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 14 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 24 + n_p * n_obs * 13 + n_p * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX2.py b/flopcounts/x86/variant_float_inlining_SoA_AVX2.py new file mode 100644 index 0000000..2cc0f8e --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX2.py @@ -0,0 +1,41 @@ +# const int n_p_pad = (n_p + 7) & ~7; + +# flops per iteration (one pass of the outer time loop): +# ADDs: n_p * 22 + n_p * n_p_pad * 15 + n_p * board.n_obstacles * 10 + n_p * 7 +# MULTs: n_p * 4 + n_p * n_p_pad * 24 + n_p * board.n_obstacles * 13 + n_p * 16 +# DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 +# SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 +# EXPs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 22 + n_p * n_p_pad * 15 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 24 + n_p * n_obs * 13 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 1 + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 1 + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp.py b/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp.py new file mode 100644 index 0000000..b6782b2 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp.py @@ -0,0 +1,41 @@ +# const int n_p_pad = (n_p + 7) & ~7; + +# flops per iteration (one pass of the outer time loop): +# ADDs: n_p * 22 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 10 + n_p * 7 +# MULTs: n_p * 4 + n_p * n_p_pad * 40 + n_p * board.n_obstacles * 13 + n_p * 16 +# DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 +# SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 +# EXPs: n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 22 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 13 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 1 + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.py b/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.py new file mode 100644 index 0000000..fc34b9f --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.py @@ -0,0 +1,41 @@ +# const int n_p_pad = (n_p + 7) & ~7; + +# flops per iteration (one pass of the outer time loop): +# ADDs: n_p * 22 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 10 + n_p * 7 +# MULTs: n_p * 4 + n_p * n_p_pad * 42 + n_p * board.n_obstacles * 13 + n_p * 16 +# DIVs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 2 +# SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 +# EXPs: n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 22 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 42 + n_p * n_obs * 13 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.py b/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.py new file mode 100644 index 0000000..d2f15d8 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.py @@ -0,0 +1,41 @@ +# const int n_p_pad = (n_p + 7) & ~7; + +# flops per iteration (one pass of the outer time loop): +# ADDs: n_p * 22 + n_p * n_p_pad * 28 + n_p * board.n_obstacles * 10 + n_p * 7 +# MULTs: n_p * 4 + n_p * n_p_pad * 42 + n_p * board.n_obstacles * 13 + n_p * 16 +# DIVs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 2 +# SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 +# EXPs: n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 22 + n_p * n_p_pad * 28 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 42 + n_p * n_obs * 13 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp.py new file mode 100644 index 0000000..7c7b970 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp.py @@ -0,0 +1,62 @@ +# const int n_p_pad = (n_p + 15) & ~15; + +# ADDs: n_iter * ( +# n_p * 36 + +# n_p * n_p_pad * 29 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) +# +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 40 + +# n_p * board.n_obstacles * 13 + +# n_p * 17 +# ) +# +# DIVs: n_iter * ( +# n_p * n_p_pad + +# n_p * board.n_obstacles + +# n_p * 2 +# ) +# +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) +# +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * + (n_p * 36 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 13 + n_p * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p * n_p_pad + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.py new file mode 100644 index 0000000..eadaa90 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.py @@ -0,0 +1,61 @@ +# const int n_p_pad = (n_p + 15) & ~15; + +# ADDs: n_iter * ( +# n_p * 36 + +# n_p * n_p_pad * 29 + +# n_p * board.n_obstacles * 10 + +# n_p * 11 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 40 + +# n_p * board.n_obstacles * 13 + +# n_p * 30 +# ) + +# DIVs: n_iter * ( +# n_p * n_p_pad + +# n_p * board.n_obstacles +# ) + +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p +# ) + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * + (n_p * 36 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p * 11)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 13 + n_p * 30)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p * n_p_pad + n_p * n_obs)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx.py new file mode 100644 index 0000000..5f99895 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx.py @@ -0,0 +1,62 @@ +# const int n_p_pad = (n_p + 15) & ~15; + +# ADDs: n_iter * ( +# n_p * 36 + +# n_p * n_p_pad * 29 + +# n_p * board.n_obstacles * 10 + +# n_p_pad * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 40 + +# n_p * board.n_obstacles * 13 + +# n_p_pad * 15 +# ) + +# DIVs: n_iter * ( +# n_p * n_p_pad + +# n_p * board.n_obstacles + +# n_p_pad * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p_pad * 3 +# ) + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * + (n_p * 36 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p_pad * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 13 + n_p_pad * 15)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p * n_p_pad + n_p * n_obs + n_p_pad * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.py new file mode 100644 index 0000000..5144d2f --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.py @@ -0,0 +1,62 @@ +# const int n_p_pad = (n_p + 31) & ~31; + +# ADDs: n_iter * ( +# n_p * 68 + +# n_p * n_p_pad * 29 + +# n_p * board.n_obstacles * 10 + +# n_p_pad * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 40 + +# n_p * board.n_obstacles * 13 + +# n_p_pad * 15 +# ) + +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p_pad * 3 +# ) + +# DIVs: n_iter * ( +# n_p * n_p_pad + +# n_p * board.n_obstacles + +# n_p_pad * 2 +# ) + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 68 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p_pad * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 13 + n_p_pad * 15)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad + n_p * n_obs + n_p_pad * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.py new file mode 100644 index 0000000..94937e5 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.py @@ -0,0 +1,62 @@ +# const int n_p_pad = (n_p + 31) & ~31; + +# ADDs: n_iter * ( +# n_p * 68 + +# n_p * n_p_pad * 29 + +# n_p * board.n_obstacles * 10 + +# n_p_pad * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 40 + +# n_p * board.n_obstacles * 13 + +# n_p_pad * 15 +# ) + +# DIVs: n_iter * ( +# n_p * n_p_pad + +# n_p * board.n_obstacles + +# n_p_pad * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p_pad * 3 +# ) + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 68 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p_pad * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 13 + n_p_pad * 15)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad + n_p * n_obs + n_p_pad * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.py new file mode 100644 index 0000000..e922446 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.py @@ -0,0 +1,62 @@ +# const int n_p_pad = (n_p + 31) & ~31; + +# ADDs: n_iter * ( +# n_p * 68 + +# n_p * n_p_pad * 29 +# n_p * board.n_obstacles * 10 +# n_p_pad * 9 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 40 +# n_p * board.n_obstacles * 13 +# n_p_pad * 17 +# ) + +# DIVs: n_iter * ( +# n_p * n_p_pad +# n_p * board.n_obstacles +# n_p_pad * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 +# n_p * board.n_obstacles +# n_p_pad * 3 +# ) + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 68 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p_pad * 9)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 13 + n_p_pad * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad + n_p * n_obs + n_p_pad * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.py new file mode 100644 index 0000000..6dc63c9 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.py @@ -0,0 +1,61 @@ +# const int n_p_pad = (n_p + 31) & ~31; + +# ADDs: n_iter * ( +# n_p * 68 + +# n_p * n_p_pad * 29 + +# n_p * board.n_obstacles * 10 + +# n_p_pad * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 42 + +# n_p * board.n_obstacles * 13 + +# n_p_pad * 16 +# ) + +# DIVs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p_pad * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p_pad * 3 +# ) + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 68 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p_pad * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 42 + n_p * n_obs * 13 + n_p_pad * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved.py new file mode 100644 index 0000000..363f7ee --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved.py @@ -0,0 +1,61 @@ +# const int n_p_pad = (n_p + 31) & ~31; + +# ADDs: n_iter * ( +# n_p * 84 + +# n_p * n_p_pad * 29 + +# n_p * board.n_obstacles * 10 + +# n_p_pad * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 42 + +# n_p * board.n_obstacles * 13 + +# n_p_pad * 16 +# ) + +# DIVs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p_pad * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p_pad * 3 +# ) + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 84 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p_pad * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 42 + n_p * n_obs * 13 + n_p_pad * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 31) & ~31 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp.py new file mode 100644 index 0000000..616d546 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp.py @@ -0,0 +1,62 @@ +# const int n_p_pad = (n_p + 15) & ~15; + +# ADDs: n_iter * ( +# n_p_pad * 7 + +# n_p_pad * n_p * 29 + +# n_p_pad * 2(=board.n_obstacles) * 24 + +# n_p_pad * 7 +# ) + +# MULTs: n_iter * ( +# n_p_pad * 4 + +# n_p_pad * n_p * 42 + +# n_p_pad * 2(=board.n_obstacles) * 31 + +# n_p_pad * 16 +# ) + +# DIVs: n_iter * ( +# n_p_pad * n_p * 3 + +# n_p_pad * 2(=board.n_obstacles) * 1 + +# n_p_pad * 2 +# ) + +# SQRTs: n_iter * ( +# n_p_pad * n_p * 3 + +# n_p_pad * 2(=board.n_obstacles) * 1 + +# n_p_pad * 3 +# ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int( + n_iter * + (n_p_pad * 7 + n_p_pad * n_p * 29 + n_p_pad * n_obs * 24 + n_p_pad * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int( + n_iter * + (n_p_pad * 4 + n_p_pad * n_p * 42 + n_p_pad * n_obs * 31 + n_p_pad * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p_pad * n_p * 3 + n_p_pad * n_obs * 1 + n_p_pad * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p_pad * n_p * 3 + n_p_pad * n_obs * 1 + n_p_pad * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(0) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.py b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.py new file mode 100644 index 0000000..b1c9dc6 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.py @@ -0,0 +1,62 @@ +# const int n_p_pad = (n_p + 63) & ~63; + +# ADDs: n_iter * ( +# n_p * 132 + +# n_p * n_p_pad * 29 + +# n_p * board.n_obstacles * 10 + +# n_p_pad * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * n_p_pad * 40 + +# n_p * board.n_obstacles * 13 + +# n_p_pad * 15 +# ) + +# DIVs: n_iter * ( +# n_p * n_p_pad + +# n_p * board.n_obstacles + +# n_p_pad * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * n_p_pad * 3 + +# n_p * board.n_obstacles + +# n_p_pad * 3 +# ) + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 63) & ~63 + return int(n_iter * + (n_p * 132 + n_p * n_p_pad * 29 + n_p * n_obs * 10 + n_p_pad * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 63) & ~63 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 13 + n_p_pad * 15)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 63) & ~63 + return int(n_iter * (n_p * n_p_pad + n_p * n_obs + n_p_pad * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 63) & ~63 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p_pad * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 63) & ~63 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_symmetry.py b/flopcounts/x86/variant_float_inlining_SoA_symmetry.py new file mode 100644 index 0000000..e85d49f --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_symmetry.py @@ -0,0 +1,69 @@ +# FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). +# section costs: overhead 4 ADD 4 MULT | distance pair 3 ADD 2 MULT 1 SQRT | +# ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | +# obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV + +# ADDs: n_iter * ( +# n_p * 4 + +# 0.5 * (n_p*n_p - n_p) * 3 + +# n_p * (n_p - 1) * 11 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# 0.5 * (n_p*n_p - n_p) * 2 + +# n_p * (n_p - 1) * 22 + +# n_p * board.n_obstacles * 13 + +# n_p * 17 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# 0.5 * (n_p*n_p - n_p) + +# n_p * (n_p - 1) * 2 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + +# total with EXP = 30: n_iter * ( 69 * n_p ** 2 - 32 * n_p + 55 * n_obstacles * n_p ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + 0.5 * (n_p * n_p - n_p) * 3 + n_p * + (n_p - 1) * 11 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + 0.5 * (n_p * n_p - n_p) * 2 + n_p * + (n_p - 1) * 22 + n_p * n_obs * 13 + n_p * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (0.5 * (n_p * n_p - n_p) + n_p * + (n_p - 1) * 2 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_16.py b/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_16.py new file mode 100644 index 0000000..d48f213 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_16.py @@ -0,0 +1,70 @@ +# FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). +# section costs: overhead 4 ADD 4 MULT (+ reductions 4*K = 64 ADD, K=16) | +# distance pair 3 ADD 2 MULT 1 SQRT | +# ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | +# obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV + +# ADDs: n_iter * ( +# n_p * 68 + +# 0.5 * (n_p*n_p - n_p) * 3 + +# n_p * (n_p - 1) * 11 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# 0.5 * (n_p*n_p - n_p) * 2 + +# n_p * (n_p - 1) * 22 + +# n_p * board.n_obstacles * 13 + +# n_p * 17 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# 0.5 * (n_p*n_p - n_p) + +# n_p * (n_p - 1) * 2 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + +# total with EXP = 30: n_iter * ( 69 * n_p ** 2 + 32 * n_p + 55 * n_obstacles * n_p ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 68 + 0.5 * (n_p * n_p - n_p) * 3 + n_p * + (n_p - 1) * 11 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + 0.5 * (n_p * n_p - n_p) * 2 + n_p * + (n_p - 1) * 22 + n_p * n_obs * 13 + n_p * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (0.5 * (n_p * n_p - n_p) + n_p * + (n_p - 1) * 2 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_2.py b/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_2.py new file mode 100644 index 0000000..d598bca --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_2.py @@ -0,0 +1,70 @@ +# FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). +# section costs: overhead 4 ADD 4 MULT (+ reductions 4*K = 8 ADD, K=2) | +# distance pair 3 ADD 2 MULT 1 SQRT | +# ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | +# obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV + +# ADDs: n_iter * ( +# n_p * 12 + +# 0.5 * (n_p*n_p - n_p) * 3 + +# n_p * (n_p - 1) * 11 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# 0.5 * (n_p*n_p - n_p) * 2 + +# n_p * (n_p - 1) * 22 + +# n_p * board.n_obstacles * 13 + +# n_p * 17 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# 0.5 * (n_p*n_p - n_p) + +# n_p * (n_p - 1) * 2 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + +# total with EXP = 30: n_iter * ( 69 * n_p ** 2 - 24 * n_p + 55 * n_obstacles * n_p ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 12 + 0.5 * (n_p * n_p - n_p) * 3 + n_p * + (n_p - 1) * 11 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + 0.5 * (n_p * n_p - n_p) * 2 + n_p * + (n_p - 1) * 22 + n_p * n_obs * 13 + n_p * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (0.5 * (n_p * n_p - n_p) + n_p * + (n_p - 1) * 2 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_4.py b/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_4.py new file mode 100644 index 0000000..dfefbb1 --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_4.py @@ -0,0 +1,70 @@ +# FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). +# section costs: overhead 4 ADD 4 MULT (+ reductions 4*K = 16 ADD, K=4) | +# distance pair 3 ADD 2 MULT 1 SQRT | +# ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | +# obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV + +# ADDs: n_iter * ( +# n_p * 20 + +# 0.5 * (n_p*n_p - n_p) * 3 + +# n_p * (n_p - 1) * 11 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# 0.5 * (n_p*n_p - n_p) * 2 + +# n_p * (n_p - 1) * 22 + +# n_p * board.n_obstacles * 13 + +# n_p * 17 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# 0.5 * (n_p*n_p - n_p) + +# n_p * (n_p - 1) * 2 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + +# total with EXP = 30: n_iter * ( 69 * n_p ** 2 - 16 * n_p + 55 * n_obstacles * n_p ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 20 + 0.5 * (n_p * n_p - n_p) * 3 + n_p * + (n_p - 1) * 11 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + 0.5 * (n_p * n_p - n_p) * 2 + n_p * + (n_p - 1) * 22 + n_p * n_obs * 13 + n_p * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (0.5 * (n_p * n_p - n_p) + n_p * + (n_p - 1) * 2 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_8.py b/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_8.py new file mode 100644 index 0000000..d70ae4d --- /dev/null +++ b/flopcounts/x86/variant_float_inlining_SoA_symmetry_ILP_8.py @@ -0,0 +1,70 @@ +# FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). +# section costs: overhead 4 ADD 4 MULT (+ reductions 4*K = 32 ADD, K=8) | +# distance pair 3 ADD 2 MULT 1 SQRT | +# ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | +# obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV + +# ADDs: n_iter * ( +# n_p * 36 + +# 0.5 * (n_p*n_p - n_p) * 3 + +# n_p * (n_p - 1) * 11 + +# n_p * board.n_obstacles * 10 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# 0.5 * (n_p*n_p - n_p) * 2 + +# n_p * (n_p - 1) * 22 + +# n_p * board.n_obstacles * 13 + +# n_p * 17 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# 0.5 * (n_p*n_p - n_p) + +# n_p * (n_p - 1) * 2 + +# n_p * board.n_obstacles + +# n_p * 3 +# ) + +# EXPs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + +# total with EXP = 30: n_iter * ( 69 * n_p ** 2 + 55 * n_obstacles * n_p ) linear term cancels + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 36 + 0.5 * (n_p * n_p - n_p) * 3 + n_p * + (n_p - 1) * 11 + n_p * n_obs * 10 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + 0.5 * (n_p * n_p - n_p) * 2 + n_p * + (n_p - 1) * 22 + n_p * n_obs * 13 + n_p * 17)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (0.5 * (n_p * n_p - n_p) + n_p * + (n_p - 1) * 2 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_7.py b/flopcounts/x86/variant_inlining_7.py new file mode 100644 index 0000000..c6c5948 --- /dev/null +++ b/flopcounts/x86/variant_inlining_7.py @@ -0,0 +1,44 @@ +# ADDs: n_iter * n_p * 11 + +# n_iter * n_p * (n_p - 1) * 14 + +# n_iter * n_p * board.n_obstacles * 12 + +# MULTs: n_iter * n_p * 21 + +# n_iter * n_p * (n_p - 1) * 24 + +# n_iter * n_p * board.n_obstacles * 19 + +# DIVs: n_iter * n_p * 2 + +# n_iter * n_p * (n_p - 1) + +# n_iter * n_p * board.n_obstacles + +# SQRTs: n_iter * n_p * 3 + +# n_iter * n_p * (n_p - 1) * 3 + +# n_iter * n_p * board.n_obstacles + +# EXPs: n_iter * n_p * (n_p - 1) + +# n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (11 + (n_p - 1) * 14 + n_obs * 12)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (21 + (n_p - 1) * 24 + n_obs * 19)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (2 + (n_p - 1) + n_obs)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (3 + (n_p - 1) * 3 + n_obs)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * ((n_p - 1) + n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_7_SoA.py b/flopcounts/x86/variant_inlining_7_SoA.py new file mode 100644 index 0000000..c6c5948 --- /dev/null +++ b/flopcounts/x86/variant_inlining_7_SoA.py @@ -0,0 +1,44 @@ +# ADDs: n_iter * n_p * 11 + +# n_iter * n_p * (n_p - 1) * 14 + +# n_iter * n_p * board.n_obstacles * 12 + +# MULTs: n_iter * n_p * 21 + +# n_iter * n_p * (n_p - 1) * 24 + +# n_iter * n_p * board.n_obstacles * 19 + +# DIVs: n_iter * n_p * 2 + +# n_iter * n_p * (n_p - 1) + +# n_iter * n_p * board.n_obstacles + +# SQRTs: n_iter * n_p * 3 + +# n_iter * n_p * (n_p - 1) * 3 + +# n_iter * n_p * board.n_obstacles + +# EXPs: n_iter * n_p * (n_p - 1) + +# n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (11 + (n_p - 1) * 14 + n_obs * 12)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (21 + (n_p - 1) * 24 + n_obs * 19)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (2 + (n_p - 1) + n_obs)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (3 + (n_p - 1) * 3 + n_obs)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * ((n_p - 1) + n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_7_SoA_AVX2.py b/flopcounts/x86/variant_inlining_7_SoA_AVX2.py new file mode 100644 index 0000000..8036261 --- /dev/null +++ b/flopcounts/x86/variant_inlining_7_SoA_AVX2.py @@ -0,0 +1,41 @@ +# const int n_p_pad = (n_p + 3) & ~3; + +# flops per iteration (one pass of the outer time loop): +# ADDs: n_p * 12 + n_p * n_p_pad * 15 + n_p * board.n_obstacles * 12 + n_p * 7 +# MULTs: n_p * 4 + n_p * n_p_pad * 24 + n_p * board.n_obstacles * 19 + n_p * 16 +# DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 +# SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 +# EXPs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * + (n_p * 12 + n_p * n_p_pad * 15 + n_p * n_obs * 12 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 24 + n_p * n_obs * 19 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * (n_p * n_p_pad * 1 + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * (n_p * n_p_pad * 1 + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_7_SoA_AVX2_polyexp.py b/flopcounts/x86/variant_inlining_7_SoA_AVX2_polyexp.py new file mode 100644 index 0000000..d68dc11 --- /dev/null +++ b/flopcounts/x86/variant_inlining_7_SoA_AVX2_polyexp.py @@ -0,0 +1,41 @@ +# const int n_p_pad = (n_p + 3) & ~3; + +# flops per iteration (one pass of the outer time loop): +# ADDs: n_p * 12 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 12 + n_p * 7 +# MULTs: n_p * 4 + n_p * n_p_pad * 40 + n_p * board.n_obstacles * 19 + n_p * 16 +# DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 +# SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 +# EXPs: n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * + (n_p * 12 + n_p * n_p_pad * 29 + n_p * n_obs * 12 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * + (n_p * 4 + n_p * n_p_pad * 40 + n_p * n_obs * 19 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * (n_p * n_p_pad * 1 + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * (n_p * n_p_pad * 3 + n_p * n_obs + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 3) & ~3 + return int(n_iter * (n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_7_symmetry_1.py b/flopcounts/x86/variant_inlining_7_symmetry_1.py new file mode 100644 index 0000000..cdafbef --- /dev/null +++ b/flopcounts/x86/variant_inlining_7_symmetry_1.py @@ -0,0 +1,27 @@ +# n_iter * [ n_p ** 2 * (12.5 ADD + 23 MULT + 1 DIV + 1 EXP + 2.5 SQRT) + n_p * (- 1.5 ADD + 0.5 SQRT - 2 MULT - 1 EXP + 1 DIV) + n_obstacles * n_p * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) ] + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 12.5 + n_p * (-1.5) + n_obs * n_p * 12)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 23 + n_p * (-2) + n_obs * n_p * 19)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * 1 + n_obs * n_p * 1)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 2.5 + n_p * 0.5 + n_obs * n_p * 1)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * (-1) + n_obs * n_p * 1)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_7_symmetry_2.py b/flopcounts/x86/variant_inlining_7_symmetry_2.py new file mode 100644 index 0000000..cdafbef --- /dev/null +++ b/flopcounts/x86/variant_inlining_7_symmetry_2.py @@ -0,0 +1,27 @@ +# n_iter * [ n_p ** 2 * (12.5 ADD + 23 MULT + 1 DIV + 1 EXP + 2.5 SQRT) + n_p * (- 1.5 ADD + 0.5 SQRT - 2 MULT - 1 EXP + 1 DIV) + n_obstacles * n_p * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) ] + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 12.5 + n_p * (-1.5) + n_obs * n_p * 12)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 23 + n_p * (-2) + n_obs * n_p * 19)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * 1 + n_obs * n_p * 1)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 2.5 + n_p * 0.5 + n_obs * n_p * 1)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * (-1) + n_obs * n_p * 1)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_7_symmetry_2_ilp_1.py b/flopcounts/x86/variant_inlining_7_symmetry_2_ilp_1.py new file mode 100644 index 0000000..9eb3c8e --- /dev/null +++ b/flopcounts/x86/variant_inlining_7_symmetry_2_ilp_1.py @@ -0,0 +1,27 @@ +# n_iter * [ n_p ** 2 * (12.5 A + 2.5 S + 23 M + 1 D + 1 E) + n_p * (6.5 A + 0.5 S - 2 M + 1 D - 1 E) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E)] + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 12.5 + n_p * 6.5 + n_obs * n_p * 12)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 23 + n_p * (-2) + n_obs * n_p * 19)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * 1 + n_obs * n_p * 1)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 2.5 + n_p * 0.5 + n_obs * n_p * 1)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * (-1) + n_obs * n_p * 1)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_7_symmetry_2_ilp_2.py b/flopcounts/x86/variant_inlining_7_symmetry_2_ilp_2.py new file mode 100644 index 0000000..e08a559 --- /dev/null +++ b/flopcounts/x86/variant_inlining_7_symmetry_2_ilp_2.py @@ -0,0 +1,27 @@ +# n_iter * [ n_p ** 2 * (12.5 A + 23 M + 2.5 S + 1 D + 1 E) + n_p * (14.5 A - 2 M + 0.5 S + 1 D - 1 E) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E) ] + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 12.5 + n_p * 14.5 + n_obs * n_p * 12)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 23 + n_p * (-2) + n_obs * n_p * 19)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * 1 + n_obs * n_p * 1)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 2.5 + n_p * 0.5 + n_obs * n_p * 1)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p**2 * 1 + n_p * (-1) + n_obs * n_p * 1)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_8.py b/flopcounts/x86/variant_inlining_8.py new file mode 100644 index 0000000..9955d52 --- /dev/null +++ b/flopcounts/x86/variant_inlining_8.py @@ -0,0 +1,44 @@ +# ADDs: n_iter * n_p * 11 + +# n_iter * n_p * (n_p - 1) * 14 + +# n_iter * n_p * board.n_obstacles * 10 + +# MULTs: n_iter * n_p * 21 + +# n_iter * n_p * (n_p - 1) * 24 + +# n_iter * n_p * board.n_obstacles * 13 + +# DIVs: n_iter * n_p * 2 + +# n_iter * n_p * (n_p - 1) + +# n_iter * n_p * board.n_obstacles + +# SQRTs: n_iter * n_p * 3 + +# n_iter * n_p * (n_p - 1) * 3 + +# n_iter * n_p * board.n_obstacles + +# EXPs: n_iter * n_p * (n_p - 1) + +# n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (11 + (n_p - 1) * 14 + n_obs * 10)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (21 + (n_p - 1) * 24 + n_obs * 13)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (2 + (n_p - 1) + n_obs)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * (3 + (n_p - 1) * 3 + n_obs)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * n_p * ((n_p - 1) + n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.py b/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.py new file mode 100644 index 0000000..c680193 --- /dev/null +++ b/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.py @@ -0,0 +1,49 @@ +# const int n_p_pad = (n_p + 7) & ~7; + +# ADDs: n_iter * n_p * 33 + +# n_iter * n_p * (n_p_pad / 8) * 232 + +# n_iter * n_p * board.n_obstacles * 10 + +# MULTs: n_iter * n_p * 40 + +# n_iter * n_p * (n_p_pad / 8) * 320 + +# n_iter * n_p * board.n_obstacles * 13 + +# DIVs: n_iter * n_p * (n_p_pad / 8) * 8 + +# n_iter * n_p * board.n_obstacles + +# SQRTs: n_iter * n_p + +# n_iter * n_p * (n_p_pad / 8) * 24 + +# n_iter * n_p * board.n_obstacles + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * (33 + (n_p_pad // 8) * 232 + n_obs * 10)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * (40 + (n_p_pad // 8) * 320 + n_obs * 13)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * ((n_p_pad // 8) * 8 + n_obs)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * (1 + (n_p_pad // 8) * 24 + n_obs)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.py b/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.py new file mode 100644 index 0000000..c680193 --- /dev/null +++ b/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.py @@ -0,0 +1,49 @@ +# const int n_p_pad = (n_p + 7) & ~7; + +# ADDs: n_iter * n_p * 33 + +# n_iter * n_p * (n_p_pad / 8) * 232 + +# n_iter * n_p * board.n_obstacles * 10 + +# MULTs: n_iter * n_p * 40 + +# n_iter * n_p * (n_p_pad / 8) * 320 + +# n_iter * n_p * board.n_obstacles * 13 + +# DIVs: n_iter * n_p * (n_p_pad / 8) * 8 + +# n_iter * n_p * board.n_obstacles + +# SQRTs: n_iter * n_p + +# n_iter * n_p * (n_p_pad / 8) * 24 + +# n_iter * n_p * board.n_obstacles + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * (33 + (n_p_pad // 8) * 232 + n_obs * 10)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * (40 + (n_p_pad // 8) * 320 + n_obs * 13)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * ((n_p_pad // 8) * 8 + n_obs)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * (1 + (n_p_pad // 8) * 24 + n_obs)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_moreavx.py b/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_moreavx.py new file mode 100644 index 0000000..c0d4372 --- /dev/null +++ b/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_moreavx.py @@ -0,0 +1,57 @@ +# const int n_p_pad = (n_p + 7) & ~7; + +# ADDs: n_iter * n_p * 20 + +# n_iter * n_p * (n_p_pad / 8) * 232 + +# n_iter * n_p * board.n_obstacles * 10 + +# n_iter * (n_p_pad / 8) * 56 + +# MULTs: n_iter * n_p * 4 + +# n_iter * n_p * (n_p_pad / 8) * 320 + +# n_iter * n_p * board.n_obstacles * 13 + +# n_iter * (n_p_pad / 8) * 120 + +# DIVs: n_iter * n_p * (n_p_pad / 8) * 8 + +# n_iter * n_p * board.n_obstacles + +# n_iter * (n_p_pad / 8) * 16 + +# SQRTs: n_iter * n_p * (n_p_pad / 8) * 24 + +# n_iter * n_p * board.n_obstacles + +# n_iter * (n_p_pad / 8) * 24 + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * + (n_p * 20 + n_p * (n_p_pad // 8) * 232 + n_p * n_obs * 10 + + (n_p_pad // 8) * 56)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * 4 + n_p * (n_p_pad // 8) * 320 + n_p * n_obs * 13 + + (n_p_pad // 8) * 120)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * (n_p_pad // 8) * 8 + n_p * n_obs + + (n_p_pad // 8) * 16)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * (n_p * (n_p_pad // 8) * 24 + n_p * n_obs + + (n_p_pad // 8) * 24)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 7) & ~7 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.py b/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.py new file mode 100644 index 0000000..d1be6bb --- /dev/null +++ b/flopcounts/x86/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.py @@ -0,0 +1,58 @@ +# const int n_p_pad = (n_p + 15) & ~15; + +# ADDs: n_iter * n_p * 36 + +# n_iter * n_p * (n_p_pad / 16) * 464 + +# n_iter * n_p * board.n_obstacles * 10 + +# n_iter * (n_p_pad / 8) * 56 + +# MULTs: n_iter * n_p * 4 + +# n_iter * n_p * (n_p_pad / 16) * 640 + +# n_iter * n_p * board.n_obstacles * 13 + +# n_iter * (n_p_pad / 8) * 120 + +# DIVs: n_iter * n_p * (n_p_pad / 16) * 16 + +# n_iter * n_p * board.n_obstacles + +# n_iter * (n_p_pad / 8) * 16 + +# SQRTs: n_iter * n_p * (n_p_pad / 16) * 48 + +# n_iter * n_p * board.n_obstacles + +# n_iter * (n_p_pad / 8) * 24 + +# EXPs: n_iter * n_p * board.n_obstacles + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * + (n_p * 36 + n_p * (n_p_pad // 16) * 464 + n_p * n_obs * 10 + + (n_p_pad // 8) * 56)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * + (n_p * 4 + n_p * (n_p_pad // 16) * 640 + n_p * n_obs * 13 + + (n_p_pad // 8) * 120)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p * (n_p_pad // 16) * 16 + n_p * n_obs + + (n_p_pad // 8) * 16)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * (n_p * (n_p_pad // 16) * 48 + n_p * n_obs + + (n_p_pad // 8) * 24)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + n_p_pad = (n_p + 15) & ~15 + return int(n_iter * n_p * n_obs) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/flopcounts/x86/variant_lut_exp.py b/flopcounts/x86/variant_lut_exp.py new file mode 100644 index 0000000..c52adea --- /dev/null +++ b/flopcounts/x86/variant_lut_exp.py @@ -0,0 +1,58 @@ +# ADDs: n_iter * ( +# n_p * 4 + +# n_p * (n_p - 1) * 14 + +# n_p * board.n_obstacles * 12 + +# n_p * 7 +# ) + +# MULTs: n_iter * ( +# n_p * 4 + +# n_p * (n_p - 1) * 23 + +# n_p * board.n_obstacles * 18 + +# n_p * 16 +# ) + +# DIVs: n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles + +# n_p * 2 +# ) + +# SQRTs: n_iter * ( +# n_p * (n_p - 1) * 4 + +# n_p * board.n_obstacles * 2 + +# n_p * 3 +# ) + +# EXPs (fast_exp, LUT): n_iter * ( +# n_p * (n_p - 1) + +# n_p * board.n_obstacles +# ) + + +def adds(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 14 + n_p * n_obs * 12 + n_p * 7)) + + +def mults(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * 4 + n_p * + (n_p - 1) * 23 + n_p * n_obs * 18 + n_p * 16)) + + +def divs(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs + n_p * 2)) + + +def sqrts(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) * 4 + n_p * n_obs * 2 + n_p * 3)) + + +def exps(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return int(n_iter * (n_p * (n_p - 1) + n_p * n_obs)) + + +def total(n_iter: int, n_p: int, n_obs: int = 2) -> int: + return adds(n_iter, n_p, n_obs) + mults(n_iter, n_p, n_obs) + divs( + n_iter, n_p, n_obs) + sqrts(n_iter, n_p, + n_obs) + exps(n_iter, n_p, n_obs) * 30 diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/1.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/1.log index 696cf09..6062c1a 100644 --- a/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp.cpp -o main_float_inlining_SoA_AVX2_polyexp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp_expfix.cpp -o main_float_inlining_SoA_AVX2_polyexp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 4538634.50 cycles -P(n): 8.09 flops/cycle +T(n): 5860437.00 cycles +P(n): 6.27 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 14836382.00 cycles -P(n): 9.81 flops/cycle +T(n): 19736818.00 cycles +P(n): 7.37 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 52866688.00 cycles -P(n): 10.95 flops/cycle +T(n): 71295200.00 cycles +P(n): 8.12 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 314877088.00 cycles -P(n): 11.46 flops/cycle +T(n): 434200736.00 cycles +P(n): 8.31 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 1228216448.00 cycles -P(n): 11.74 flops/cycle +T(n): 1690932352.00 cycles +P(n): 8.52 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 4888108544.00 cycles -P(n): 11.79 flops/cycle +T(n): 6726832128.00 cycles +P(n): 8.57 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 30235383808.00 cycles -P(n): 11.91 flops/cycle +T(n): 41595662336.00 cycles +P(n): 8.66 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 166348996608.00 cycles +P(n): 3.49 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/2.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/2.log index 13f9f97..652e0a6 100644 --- a/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp.cpp -o main_float_inlining_SoA_AVX2_polyexp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp_expfix.cpp -o main_float_inlining_SoA_AVX2_polyexp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 4573347.00 cycles -P(n): 8.03 flops/cycle +T(n): 5885449.00 cycles +P(n): 6.24 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 14972852.00 cycles -P(n): 9.72 flops/cycle +T(n): 19775262.00 cycles +P(n): 7.36 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 53246808.00 cycles -P(n): 10.87 flops/cycle +T(n): 71759640.00 cycles +P(n): 8.07 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 317441728.00 cycles -P(n): 11.36 flops/cycle +T(n): 442073856.00 cycles +P(n): 8.16 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 1237166592.00 cycles -P(n): 11.65 flops/cycle +T(n): 1698162944.00 cycles +P(n): 8.49 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 4899325440.00 cycles -P(n): 11.76 flops/cycle +T(n): 6719285248.00 cycles +P(n): 8.58 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 30502610944.00 cycles -P(n): 11.80 flops/cycle +T(n): 41795964928.00 cycles +P(n): 8.62 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 122120396800.00 cycles -P(n): 4.76 flops/cycle +T(n): 166340608000.00 cycles +P(n): 3.49 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/3.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/3.log new file mode 100644 index 0000000..543ba85 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp/x86/3.log @@ -0,0 +1,64 @@ +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 5839795.00 cycles +P(n): 6.29 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 19706848.00 cycles +P(n): 7.38 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 71913632.00 cycles +P(n): 8.05 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 431199296.00 cycles +P(n): 8.37 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 1710698496.00 cycles +P(n): 8.43 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 6773665792.00 cycles +P(n): 8.51 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 43346759680.00 cycles +P(n): 8.31 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 169504800768.00 cycles +P(n): 3.43 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/1.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/1.log index a34df7a..7e95d51 100644 --- a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.cpp -o main_float_inlining_SoA_AVX2_polyexp_rsqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_expfix.cpp -o main_float_inlining_SoA_AVX2_polyexp_rsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3922583.00 cycles -P(n): 9.37 flops/cycle +T(n): 5075402.00 cycles +P(n): 7.24 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 12656640.00 cycles -P(n): 11.49 flops/cycle +T(n): 16874942.00 cycles +P(n): 8.62 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 44724200.00 cycles -P(n): 12.95 flops/cycle +T(n): 60586048.00 cycles +P(n): 9.56 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 264998976.00 cycles -P(n): 13.61 flops/cycle +T(n): 363991840.00 cycles +P(n): 9.91 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 1051923712.00 cycles -P(n): 13.70 flops/cycle +T(n): 1424352768.00 cycles +P(n): 10.12 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 4077504256.00 cycles -P(n): 14.13 flops/cycle +T(n): 5638619648.00 cycles +P(n): 10.22 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 25317951488.00 cycles -P(n): 14.22 flops/cycle +T(n): 35081228288.00 cycles +P(n): 10.26 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 140734889984.00 cycles +P(n): 4.13 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/2.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/2.log index 67fb138..322109b 100644 --- a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/2.log @@ -1,66 +1,64 @@ -rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.cpp -o main_float_inlining_SoA_AVX2_polyexp_rsqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3968185.50 cycles -P(n): 9.26 flops/cycle +T(n): 5095124.00 cycles +P(n): 7.21 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 12830122.00 cycles -P(n): 11.34 flops/cycle +T(n): 16958378.00 cycles +P(n): 8.58 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 45279400.00 cycles -P(n): 12.79 flops/cycle +T(n): 60894740.00 cycles +P(n): 9.51 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 268426256.00 cycles -P(n): 13.44 flops/cycle +T(n): 364922464.00 cycles +P(n): 9.89 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 1064596800.00 cycles -P(n): 13.54 flops/cycle +T(n): 1432131456.00 cycles +P(n): 10.07 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 4126754304.00 cycles -P(n): 13.96 flops/cycle +T(n): 5669585408.00 cycles +P(n): 10.16 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 25623275520.00 cycles -P(n): 14.05 flops/cycle +T(n): 35706060800.00 cycles +P(n): 10.08 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 102223626240.00 cycles -P(n): 5.69 flops/cycle +T(n): 143150546944.00 cycles +P(n): 4.06 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/3.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/3.log new file mode 100644 index 0000000..a1c9040 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt/x86/3.log @@ -0,0 +1,64 @@ +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 5107488.00 cycles +P(n): 7.19 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 16972588.00 cycles +P(n): 8.57 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 61120552.00 cycles +P(n): 9.47 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 375650720.00 cycles +P(n): 9.60 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 1444334464.00 cycles +P(n): 9.98 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 5717088768.00 cycles +P(n): 10.08 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 36025249792.00 cycles +P(n): 10.00 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 143378251776.00 cycles +P(n): 4.05 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/1.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/1.log index 6fc604a..eab143b 100644 --- a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.cpp -o main_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt_expfix.cpp -o main_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3857942.00 cycles -P(n): 9.52 flops/cycle +T(n): 4870774.50 cycles +P(n): 7.54 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11939004.00 cycles -P(n): 12.19 flops/cycle +T(n): 15640118.00 cycles +P(n): 9.30 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 41343432.00 cycles -P(n): 14.00 flops/cycle +T(n): 55310808.00 cycles +P(n): 10.47 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 242607552.00 cycles -P(n): 14.87 flops/cycle +T(n): 329563872.00 cycles +P(n): 10.95 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 939774144.00 cycles -P(n): 15.34 flops/cycle +T(n): 1283268352.00 cycles +P(n): 11.23 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3710422272.00 cycles -P(n): 15.53 flops/cycle +T(n): 5083250688.00 cycles +P(n): 11.34 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 23019415552.00 cycles -P(n): 15.64 flops/cycle +T(n): 31586570240.00 cycles +P(n): 11.40 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 126882955264.00 cycles +P(n): 4.58 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/2.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/2.log index c4e45b2..e4dcd7a 100644 --- a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/2.log @@ -1,66 +1,64 @@ -rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.cpp -o main_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3896576.25 cycles -P(n): 9.43 flops/cycle +T(n): 4872185.50 cycles +P(n): 7.54 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 12081705.00 cycles -P(n): 12.04 flops/cycle +T(n): 15664138.00 cycles +P(n): 9.29 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 41848524.00 cycles -P(n): 13.83 flops/cycle +T(n): 55368740.00 cycles +P(n): 10.46 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 245348896.00 cycles -P(n): 14.70 flops/cycle +T(n): 329987008.00 cycles +P(n): 10.93 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 950120256.00 cycles -P(n): 15.17 flops/cycle +T(n): 1293044736.00 cycles +P(n): 11.15 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3760375296.00 cycles -P(n): 15.33 flops/cycle +T(n): 5130688512.00 cycles +P(n): 11.23 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 23338608640.00 cycles -P(n): 15.43 flops/cycle +T(n): 32060348416.00 cycles +P(n): 11.23 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 93044531200.00 cycles -P(n): 6.25 flops/cycle +T(n): 129057972224.00 cycles +P(n): 4.50 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/3.log b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/3.log new file mode 100644 index 0000000..da0c24a --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt/x86/3.log @@ -0,0 +1,64 @@ +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 4901788.00 cycles +P(n): 7.50 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 15669965.00 cycles +P(n): 9.28 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 55911480.00 cycles +P(n): 10.35 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 338225088.00 cycles +P(n): 10.67 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 1302161408.00 cycles +P(n): 11.07 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 5189515776.00 cycles +P(n): 11.11 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 32577761280.00 cycles +P(n): 11.05 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 129035010048.00 cycles +P(n): 4.50 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/1.log index 1d8658f..533d3a6 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp.cpp -o main_float_inlining_SoA_AVX512_polyexp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3952082.50 cycles -P(n): 9.30 flops/cycle +T(n): 4618692.00 cycles +P(n): 7.95 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11806225.00 cycles -P(n): 12.32 flops/cycle +T(n): 14020708.00 cycles +P(n): 10.38 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 39147728.00 cycles -P(n): 14.79 flops/cycle +T(n): 47426524.00 cycles +P(n): 12.21 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 220870336.00 cycles -P(n): 16.33 flops/cycle +T(n): 271672832.00 cycles +P(n): 13.28 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 843467072.00 cycles -P(n): 17.09 flops/cycle +T(n): 1041892096.00 cycles +P(n): 13.84 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3297570560.00 cycles -P(n): 17.48 flops/cycle +T(n): 4086357760.00 cycles +P(n): 14.10 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 20527921152.00 cycles -P(n): 17.54 flops/cycle +T(n): 25345890304.00 cycles +P(n): 14.21 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 100797374464.00 cycles +P(n): 5.77 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/2.log index 5f8aac9..1d0ae94 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp.cpp -o main_float_inlining_SoA_AVX512_polyexp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3995093.75 cycles -P(n): 9.20 flops/cycle +T(n): 4660804.50 cycles +P(n): 7.88 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11929089.00 cycles -P(n): 12.20 flops/cycle +T(n): 14015782.00 cycles +P(n): 10.38 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 39616448.00 cycles -P(n): 14.61 flops/cycle +T(n): 47630040.00 cycles +P(n): 12.16 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 223771808.00 cycles -P(n): 16.12 flops/cycle +T(n): 272007200.00 cycles +P(n): 13.26 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 854663616.00 cycles -P(n): 16.87 flops/cycle +T(n): 1042778816.00 cycles +P(n): 13.82 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3338979072.00 cycles -P(n): 17.26 flops/cycle +T(n): 4092400640.00 cycles +P(n): 14.08 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 20689987584.00 cycles -P(n): 17.40 flops/cycle +T(n): 25352386560.00 cycles +P(n): 14.20 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 82361786368.00 cycles -P(n): 7.06 flops/cycle +T(n): 101285814272.00 cycles +P(n): 5.74 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/3.log new file mode 100644 index 0000000..49a7e56 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 4633135.50 cycles +P(n): 7.93 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 14098810.00 cycles +P(n): 10.32 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 47519168.00 cycles +P(n): 12.18 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 272423904.00 cycles +P(n): 13.24 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 1060791424.00 cycles +P(n): 13.59 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 4102831872.00 cycles +P(n): 14.05 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 25357774848.00 cycles +P(n): 14.20 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 100551254016.00 cycles +P(n): 5.78 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/1.log index 7be9195..7cc9274 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.cpp -o main_float_inlining_SoA_AVX512_polyexp_fastinvsqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_fastinvsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 4235088.00 cycles -P(n): 8.68 flops/cycle +T(n): 4899351.00 cycles +P(n): 7.50 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 12301878.00 cycles -P(n): 11.83 flops/cycle +T(n): 14704840.00 cycles +P(n): 9.89 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 40178688.00 cycles -P(n): 14.41 flops/cycle +T(n): 48870456.00 cycles +P(n): 11.85 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 223423168.00 cycles -P(n): 16.15 flops/cycle +T(n): 274548320.00 cycles +P(n): 13.14 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 848105152.00 cycles -P(n): 17.00 flops/cycle +T(n): 1067638016.00 cycles +P(n): 13.50 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3303374080.00 cycles -P(n): 17.45 flops/cycle +T(n): 4133780736.00 cycles +P(n): 13.94 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 20445360128.00 cycles -P(n): 17.61 flops/cycle +T(n): 25536294912.00 cycles +P(n): 14.10 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 101531860992.00 cycles +P(n): 5.72 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/2.log index af94299..b038931 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.cpp -o main_float_inlining_SoA_AVX512_polyexp_fastinvsqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_fastinvsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 4261088.00 cycles -P(n): 8.62 flops/cycle +T(n): 4906050.00 cycles +P(n): 7.49 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 12455221.00 cycles -P(n): 11.68 flops/cycle +T(n): 14798538.00 cycles +P(n): 9.83 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 40661000.00 cycles -P(n): 14.24 flops/cycle +T(n): 48817384.00 cycles +P(n): 11.86 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 226296576.00 cycles -P(n): 15.94 flops/cycle +T(n): 276943584.00 cycles +P(n): 13.03 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 859257920.00 cycles -P(n): 16.78 flops/cycle +T(n): 1052295168.00 cycles +P(n): 13.70 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3344503808.00 cycles -P(n): 17.23 flops/cycle +T(n): 4133724672.00 cycles +P(n): 13.94 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 20797153280.00 cycles -P(n): 17.31 flops/cycle +T(n): 25570220032.00 cycles +P(n): 14.08 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 82334916608.00 cycles -P(n): 7.06 flops/cycle +T(n): 101490999296.00 cycles +P(n): 5.73 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/3.log new file mode 100644 index 0000000..ed48881 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_fastinvsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 4896082.00 cycles +P(n): 7.50 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 14589518.00 cycles +P(n): 9.97 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 48579536.00 cycles +P(n): 11.92 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 275277376.00 cycles +P(n): 13.10 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 1050094400.00 cycles +P(n): 13.73 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 4110344448.00 cycles +P(n): 14.02 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 25639516160.00 cycles +P(n): 14.04 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 101213003776.00 cycles +P(n): 5.74 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/1.log index 2c8917d..29aad47 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3667923.00 cycles -P(n): 10.02 flops/cycle +T(n): 4444855.00 cycles +P(n): 8.27 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11422578.00 cycles -P(n): 12.74 flops/cycle +T(n): 13834708.00 cycles +P(n): 10.52 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 37820328.00 cycles -P(n): 15.31 flops/cycle +T(n): 47410956.00 cycles +P(n): 12.21 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 216471376.00 cycles -P(n): 16.66 flops/cycle +T(n): 276381408.00 cycles +P(n): 13.05 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 832166400.00 cycles -P(n): 17.32 flops/cycle +T(n): 1067362496.00 cycles +P(n): 13.51 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3253410304.00 cycles -P(n): 17.71 flops/cycle +T(n): 4156325376.00 cycles +P(n): 13.87 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 20200312832.00 cycles -P(n): 17.83 flops/cycle +T(n): 26170245120.00 cycles +P(n): 13.76 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 104163885056.00 cycles +P(n): 5.58 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/2.log index d2df607..dcd470c 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3700145.00 cycles -P(n): 9.93 flops/cycle +T(n): 4415419.00 cycles +P(n): 8.32 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11292105.00 cycles -P(n): 12.88 flops/cycle +T(n): 13731945.00 cycles +P(n): 10.59 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 38161324.00 cycles -P(n): 15.17 flops/cycle +T(n): 47216112.00 cycles +P(n): 12.26 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 218794224.00 cycles -P(n): 16.49 flops/cycle +T(n): 273902208.00 cycles +P(n): 13.17 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 840880000.00 cycles -P(n): 17.14 flops/cycle +T(n): 1066031616.00 cycles +P(n): 13.52 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3292569344.00 cycles -P(n): 17.50 flops/cycle +T(n): 4168480512.00 cycles +P(n): 13.83 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 20442466304.00 cycles -P(n): 17.61 flops/cycle +T(n): 25959776256.00 cycles +P(n): 13.87 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 81447813120.00 cycles -P(n): 7.14 flops/cycle +T(n): 103256596480.00 cycles +P(n): 5.63 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/3.log new file mode 100644 index 0000000..a66e481 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 4421310.50 cycles +P(n): 8.31 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 13851305.00 cycles +P(n): 10.50 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 47453300.00 cycles +P(n): 12.20 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 275350432.00 cycles +P(n): 13.10 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 1059425216.00 cycles +P(n): 13.61 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 4161480448.00 cycles +P(n): 13.85 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 25909958656.00 cycles +P(n): 13.90 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 102891044864.00 cycles +P(n): 5.65 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/1.log index 42e40a3..8b8601d 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2 -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3477458.00 cycles -P(n): 10.57 flops/cycle +T(n): 4116743.00 cycles +P(n): 8.92 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11784472.00 cycles -P(n): 12.35 flops/cycle +T(n): 14193060.00 cycles +P(n): 10.25 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 38291872.00 cycles -P(n): 15.12 flops/cycle +T(n): 46275016.00 cycles +P(n): 12.51 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 202935328.00 cycles -P(n): 17.78 flops/cycle +T(n): 250446432.00 cycles +P(n): 14.40 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 792025088.00 cycles -P(n): 18.20 flops/cycle +T(n): 977844992.00 cycles +P(n): 14.74 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3083133952.00 cycles -P(n): 18.69 flops/cycle +T(n): 3811610624.00 cycles +P(n): 15.12 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 19033051136.00 cycles -P(n): 18.92 flops/cycle +T(n): 23616888832.00 cycles +P(n): 15.25 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 94000291840.00 cycles +P(n): 6.18 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/2.log index 52d6012..2a209b9 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2 -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3489274.50 cycles -P(n): 10.53 flops/cycle +T(n): 4130377.00 cycles +P(n): 8.90 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11797675.00 cycles -P(n): 12.33 flops/cycle +T(n): 14179700.00 cycles +P(n): 10.26 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 38045456.00 cycles -P(n): 15.22 flops/cycle +T(n): 46318220.00 cycles +P(n): 12.50 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 203912944.00 cycles -P(n): 17.69 flops/cycle +T(n): 250998336.00 cycles +P(n): 14.37 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 794322688.00 cycles -P(n): 18.15 flops/cycle +T(n): 982180672.00 cycles +P(n): 14.68 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3086995712.00 cycles -P(n): 18.67 flops/cycle +T(n): 3805426944.00 cycles +P(n): 15.14 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 19067494400.00 cycles -P(n): 18.88 flops/cycle +T(n): 23592302592.00 cycles +P(n): 15.26 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 75557011456.00 cycles -P(n): 7.69 flops/cycle +T(n): 93937680384.00 cycles +P(n): 6.19 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/3.log new file mode 100644 index 0000000..7f8cb13 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 4107580.00 cycles +P(n): 8.94 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 14142018.00 cycles +P(n): 10.29 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 46132236.00 cycles +P(n): 12.55 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 248790048.00 cycles +P(n): 14.50 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 974417088.00 cycles +P(n): 14.79 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 3791391744.00 cycles +P(n): 15.20 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 23488210944.00 cycles +P(n): 15.33 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 93195214848.00 cycles +P(n): 6.24 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/1.log index 6cdd3a9..3ea961d 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3500766.25 cycles -P(n): 10.49 flops/cycle +T(n): 4119329.50 cycles +P(n): 8.92 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11807499.00 cycles -P(n): 12.32 flops/cycle +T(n): 14140615.00 cycles +P(n): 10.29 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 38032600.00 cycles -P(n): 15.22 flops/cycle +T(n): 46104904.00 cycles +P(n): 12.56 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 204147360.00 cycles -P(n): 17.67 flops/cycle +T(n): 248777184.00 cycles +P(n): 14.50 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 791926400.00 cycles -P(n): 18.20 flops/cycle +T(n): 972575296.00 cycles +P(n): 14.82 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3074645504.00 cycles -P(n): 18.74 flops/cycle +T(n): 3793779712.00 cycles +P(n): 15.19 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 19024320512.00 cycles -P(n): 18.93 flops/cycle +T(n): 23662673920.00 cycles +P(n): 15.22 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 93648879616.00 cycles +P(n): 6.21 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/2.log index 73f3cc8..29375a1 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3498589.50 cycles -P(n): 10.50 flops/cycle +T(n): 4131873.00 cycles +P(n): 8.89 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11758110.00 cycles -P(n): 12.37 flops/cycle +T(n): 14207365.00 cycles +P(n): 10.24 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 37864020.00 cycles -P(n): 15.29 flops/cycle +T(n): 46157304.00 cycles +P(n): 12.54 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 202672640.00 cycles -P(n): 17.80 flops/cycle +T(n): 249512304.00 cycles +P(n): 14.46 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 790035264.00 cycles -P(n): 18.25 flops/cycle +T(n): 974145152.00 cycles +P(n): 14.80 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3070306304.00 cycles -P(n): 18.77 flops/cycle +T(n): 3796545280.00 cycles +P(n): 15.18 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 18978818048.00 cycles -P(n): 18.97 flops/cycle +T(n): 23539767296.00 cycles +P(n): 15.30 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 75496284160.00 cycles -P(n): 7.70 flops/cycle +T(n): 93385080832.00 cycles +P(n): 6.22 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/3.log new file mode 100644 index 0000000..d853e35 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 4133901.25 cycles +P(n): 8.89 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 14286805.00 cycles +P(n): 10.18 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 46514768.00 cycles +P(n): 12.45 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 250415104.00 cycles +P(n): 14.41 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 979476864.00 cycles +P(n): 14.72 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 3790536448.00 cycles +P(n): 15.20 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 23435288576.00 cycles +P(n): 15.36 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 93591674880.00 cycles +P(n): 6.21 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/1.log index a950e19..4022b05 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3466542.50 cycles -P(n): 10.60 flops/cycle +T(n): 4119765.00 cycles +P(n): 8.92 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11758505.00 cycles -P(n): 12.37 flops/cycle +T(n): 14158772.00 cycles +P(n): 10.27 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 38354536.00 cycles -P(n): 15.09 flops/cycle +T(n): 46276376.00 cycles +P(n): 12.51 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 203609408.00 cycles -P(n): 17.72 flops/cycle +T(n): 249878624.00 cycles +P(n): 14.44 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 792448704.00 cycles -P(n): 18.19 flops/cycle +T(n): 982863872.00 cycles +P(n): 14.67 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3076762624.00 cycles -P(n): 18.73 flops/cycle +T(n): 3826156544.00 cycles +P(n): 15.06 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 19086870528.00 cycles -P(n): 18.87 flops/cycle +T(n): 23693496320.00 cycles +P(n): 15.20 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 94222524416.00 cycles +P(n): 6.17 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/2.log index 967df04..4c1d8cf 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3470556.25 cycles -P(n): 10.59 flops/cycle +T(n): 4102695.50 cycles +P(n): 8.96 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 11748129.00 cycles -P(n): 12.38 flops/cycle +T(n): 14151872.00 cycles +P(n): 10.28 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 37961964.00 cycles -P(n): 15.25 flops/cycle +T(n): 46248272.00 cycles +P(n): 12.52 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 203245888.00 cycles -P(n): 17.75 flops/cycle +T(n): 249935680.00 cycles +P(n): 14.43 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 792723072.00 cycles -P(n): 18.18 flops/cycle +T(n): 977354240.00 cycles +P(n): 14.75 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3083602944.00 cycles -P(n): 18.69 flops/cycle +T(n): 3821584896.00 cycles +P(n): 15.08 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 19105830912.00 cycles -P(n): 18.85 flops/cycle +T(n): 23594674176.00 cycles +P(n): 15.26 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 75556601856.00 cycles -P(n): 7.69 flops/cycle +T(n): 94093844480.00 cycles +P(n): 6.18 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/3.log new file mode 100644 index 0000000..a80e32c --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 4124268.00 cycles +P(n): 8.91 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 14218675.00 cycles +P(n): 10.23 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 46843476.00 cycles +P(n): 12.36 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 250210640.00 cycles +P(n): 14.42 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 982352128.00 cycles +P(n): 14.67 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 3823222016.00 cycles +P(n): 15.07 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 23695175680.00 cycles +P(n): 15.20 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 94304829440.00 cycles +P(n): 6.16 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/1.log index cd711d9..fd8f66f 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 2966264.00 cycles -P(n): 12.39 flops/cycle +T(n): 3472917.50 cycles +P(n): 10.58 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 9680198.00 cycles -P(n): 15.03 flops/cycle +T(n): 11763622.00 cycles +P(n): 12.37 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 30652810.00 cycles -P(n): 18.89 flops/cycle +T(n): 38070640.00 cycles +P(n): 15.21 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 160992976.00 cycles -P(n): 22.41 flops/cycle +T(n): 204154320.00 cycles +P(n): 17.67 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 629138816.00 cycles -P(n): 22.91 flops/cycle +T(n): 798999808.00 cycles +P(n): 18.04 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 2423142656.00 cycles -P(n): 23.78 flops/cycle +T(n): 3114275584.00 cycles +P(n): 18.50 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 15022035968.00 cycles -P(n): 23.97 flops/cycle +T(n): 19146825728.00 cycles +P(n): 18.81 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 76207964160.00 cycles +P(n): 7.63 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/2.log index 179470e..0a1f7d4 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 2942563.50 cycles -P(n): 12.49 flops/cycle +T(n): 3503500.00 cycles +P(n): 10.49 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 9665785.00 cycles -P(n): 15.05 flops/cycle +T(n): 11802675.00 cycles +P(n): 12.33 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 30626330.00 cycles -P(n): 18.90 flops/cycle +T(n): 37947776.00 cycles +P(n): 15.26 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 161586752.00 cycles -P(n): 22.32 flops/cycle +T(n): 203323776.00 cycles +P(n): 17.74 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 626457472.00 cycles -P(n): 23.01 flops/cycle +T(n): 821986944.00 cycles +P(n): 17.54 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 2423129344.00 cycles -P(n): 23.78 flops/cycle +T(n): 3103710720.00 cycles +P(n): 18.57 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 14910323712.00 cycles -P(n): 24.15 flops/cycle +T(n): 19254915072.00 cycles +P(n): 18.70 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 59213176832.00 cycles -P(n): 9.81 flops/cycle +T(n): 76036898816.00 cycles +P(n): 7.64 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/3.log new file mode 100644 index 0000000..8fb271d --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 3474629.50 cycles +P(n): 10.57 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 11869545.00 cycles +P(n): 12.26 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 38112416.00 cycles +P(n): 15.19 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 203449264.00 cycles +P(n): 17.73 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 816738752.00 cycles +P(n): 17.65 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 3081998592.00 cycles +P(n): 18.70 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 19241965568.00 cycles +P(n): 18.71 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 76032835584.00 cycles +P(n): 7.64 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/1.log new file mode 100644 index 0000000..ed597a5 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/1.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 3443131.25 cycles +P(n): 10.67 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 11289992.00 cycles +P(n): 12.89 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 36293512.00 cycles +P(n): 15.95 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 193259072.00 cycles +P(n): 18.67 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 752713344.00 cycles +P(n): 19.15 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 2940765440.00 cycles +P(n): 19.60 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 18122862592.00 cycles +P(n): 19.87 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 71869390848.00 cycles +P(n): 8.09 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/2.log new file mode 100644 index 0000000..08bfc3e --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/2.log @@ -0,0 +1,64 @@ +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 3387230.00 cycles +P(n): 10.85 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 11326786.00 cycles +P(n): 12.84 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 36351516.00 cycles +P(n): 15.93 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 194490336.00 cycles +P(n): 18.55 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 753201792.00 cycles +P(n): 19.14 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 2954236416.00 cycles +P(n): 19.51 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 18356828160.00 cycles +P(n): 19.62 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 73678970880.00 cycles +P(n): 7.89 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/3.log new file mode 100644 index 0000000..70f3e89 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved/x86/3.log @@ -0,0 +1,64 @@ +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 3445767.00 cycles +P(n): 10.66 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 11501729.00 cycles +P(n): 12.65 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 36756200.00 cycles +P(n): 15.75 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 198376320.00 cycles +P(n): 18.18 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 765950336.00 cycles +P(n): 18.82 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 2987576576.00 cycles +P(n): 19.29 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 18625347584.00 cycles +P(n): 19.33 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 73340780544.00 cycles +P(n): 7.92 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/1.log new file mode 100644 index 0000000..5b20e8e --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/1.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved_moreilp.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved_moreilp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 2430659.75 cycles +P(n): 15.12 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 8285106.00 cycles +P(n): 17.56 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 30615050.00 cycles +P(n): 18.91 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 186199040.00 cycles +P(n): 19.37 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 728076288.00 cycles +P(n): 19.80 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 2895743232.00 cycles +P(n): 19.90 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 18180956160.00 cycles +P(n): 19.81 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 72372396032.00 cycles +P(n): 8.03 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/2.log new file mode 100644 index 0000000..66eac2b --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/2.log @@ -0,0 +1,64 @@ +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 2443465.25 cycles +P(n): 15.04 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 8370210.00 cycles +P(n): 17.38 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 30839560.00 cycles +P(n): 18.77 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 185338336.00 cycles +P(n): 19.46 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 737704256.00 cycles +P(n): 19.54 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 2915981056.00 cycles +P(n): 19.76 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 18362775552.00 cycles +P(n): 19.61 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 73707225088.00 cycles +P(n): 7.88 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/3.log new file mode 100644 index 0000000..a9c7550 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp/x86/3.log @@ -0,0 +1,64 @@ +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 2445340.00 cycles +P(n): 15.02 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 8350645.00 cycles +P(n): 17.42 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 30609110.00 cycles +P(n): 18.91 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 186796768.00 cycles +P(n): 19.31 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 733212800.00 cycles +P(n): 19.66 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 2960800768.00 cycles +P(n): 19.46 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 18505687040.00 cycles +P(n): 19.46 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 73613836288.00 cycles +P(n): 7.89 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/1.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/1.log index 6501532..9eac607 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/1.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4 -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3587017.50 cycles -P(n): 10.24 flops/cycle +T(n): 3897775.00 cycles +P(n): 9.43 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 12215910.00 cycles -P(n): 11.91 flops/cycle +T(n): 13522278.00 cycles +P(n): 10.76 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 44944608.00 cycles -P(n): 12.88 flops/cycle +T(n): 50063060.00 cycles +P(n): 11.56 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 214169936.00 cycles -P(n): 16.84 flops/cycle +T(n): 240297888.00 cycles +P(n): 15.01 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 828421248.00 cycles -P(n): 17.40 flops/cycle +T(n): 942706688.00 cycles +P(n): 15.29 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3268490496.00 cycles -P(n): 17.63 flops/cycle +T(n): 3722795008.00 cycles +P(n): 15.48 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 20012742656.00 cycles -P(n): 17.99 flops/cycle +T(n): 22749288448.00 cycles +P(n): 15.83 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 90110476288.00 cycles +P(n): 6.45 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/2.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/2.log index 03e3e9e..e61cf75 100644 --- a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/2.log +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4 -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 3256 bytes I(n): 56.42 flops/byte W(n): 36740000.00 flops -T(n): 3567542.00 cycles -P(n): 10.30 flops/cycle +T(n): 3907767.50 cycles +P(n): 9.40 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 6456 bytes I(n): 112.67 flops/byte W(n): 145480000.00 flops -T(n): 12180789.00 cycles -P(n): 11.94 flops/cycle +T(n): 13510660.00 cycles +P(n): 10.77 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 12856 bytes I(n): 225.17 flops/byte W(n): 578960000.00 flops -T(n): 44522004.00 cycles -P(n): 13.00 flops/cycle +T(n): 50107380.00 cycles +P(n): 11.55 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 32056 bytes I(n): 562.67 flops/byte W(n): 3607399936.00 flops -T(n): 212099104.00 cycles -P(n): 17.01 flops/cycle +T(n): 239951200.00 cycles +P(n): 15.03 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 64056 bytes I(n): 1125.17 flops/byte W(n): 14414799872.00 flops -T(n): 830612480.00 cycles -P(n): 17.35 flops/cycle +T(n): 940302272.00 cycles +P(n): 15.33 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 128056 bytes I(n): 2250.17 flops/byte W(n): 57629601792.00 flops -T(n): 3272956672.00 cycles -P(n): 17.61 flops/cycle +T(n): 3722942720.00 cycles +P(n): 15.48 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800369920.00 flops Q(n) per iteration: 320056 bytes I(n): 5625.17 flops/byte W(n): 360073986048.00 flops -T(n): 20105635840.00 cycles -P(n): 17.91 flops/cycle +T(n): 22711234560.00 cycles +P(n): 15.85 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772800.00 flops Q(n) per iteration: 640056 bytes I(n): 4539.87 flops/byte W(n): 581154570240.00 flops -T(n): 79669575680.00 cycles -P(n): 7.29 flops/cycle +T(n): 90064855040.00 cycles +P(n): 6.45 flops/cycle diff --git a/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/3.log b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/3.log new file mode 100644 index 0000000..98b4ec6 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main-float.cpp ./variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4_expfix.cpp -o main_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 3864792.00 cycles +P(n): 9.51 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 13472545.00 cycles +P(n): 10.80 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 49757488.00 cycles +P(n): 11.64 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 238792880.00 cycles +P(n): 15.11 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 953924224.00 cycles +P(n): 15.11 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 3701525248.00 cycles +P(n): 15.57 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 22775025664.00 cycles +P(n): 15.81 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772800.00 flops +Q(n) per iteration: 640056 bytes +I(n): 4539.87 flops/byte +W(n): 581154570240.00 flops +T(n): 90258751488.00 cycles +P(n): 6.44 flops/cycle + diff --git a/measurements/variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morersqrt/arm/1.log b/measurements/variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morersqrt/arm/1.log new file mode 100644 index 0000000..8f7a2a2 --- /dev/null +++ b/measurements/variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morersqrt/arm/1.log @@ -0,0 +1,58 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +sudo /usr/bin/g++ main-float.cpp ./variants/variant_float_inlining.cpp -o main_float_inlining -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 3256 bytes +I(n): 56.42 flops/byte +W(n): 36740000.00 flops +T(n): 10826171.00 cycles +P(n): 3.39 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 6456 bytes +I(n): 112.67 flops/byte +W(n): 145480000.00 flops +T(n): 48929320.00 cycles +P(n): 2.97 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 12856 bytes +I(n): 225.17 flops/byte +W(n): 578960000.00 flops +T(n): 190686384.00 cycles +P(n): 3.04 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 32056 bytes +I(n): 562.67 flops/byte +W(n): 3607399936.00 flops +T(n): 1427274624.00 cycles +P(n): 2.53 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 64056 bytes +I(n): 1125.17 flops/byte +W(n): 14414799872.00 flops +T(n): 6172825600.00 cycles +P(n): 2.34 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 128056 bytes +I(n): 2250.17 flops/byte +W(n): 57629601792.00 flops +T(n): 27468242944.00 cycles +P(n): 2.10 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800369920.00 flops +Q(n) per iteration: 320056 bytes +I(n): 5625.17 flops/byte +W(n): 360073986048.00 flops +T(n): 192917569536.00 cycles +P(n): 1.87 flops/cycle + diff --git a/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/1.log b/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/1.log index 8a21c6c..7ff1112 100644 --- a/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/1.log +++ b/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_7_SoA_AVX2_polyexp.cpp -o main_inlining_7_SoA_AVX2_polyexp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_7_SoA_AVX2_polyexp_expfix.cpp -o main_inlining_7_SoA_AVX2_polyexp_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 9225415.00 cycles -P(n): 3.98 flops/cycle +T(n): 11371033.75 cycles +P(n): 3.23 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 32320875.00 cycles -P(n): 4.50 flops/cycle +T(n): 39890030.00 cycles +P(n): 3.65 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 122392560.00 cycles -P(n): 4.73 flops/cycle +T(n): 153171560.00 cycles +P(n): 3.78 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 727457980.00 cycles -P(n): 4.96 flops/cycle +T(n): 926036960.00 cycles +P(n): 3.90 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2871839460.00 cycles -P(n): 5.02 flops/cycle +T(n): 3676943300.00 cycles +P(n): 3.92 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 11342093520.00 cycles -P(n): 5.08 flops/cycle +T(n): 14545721140.00 cycles +P(n): 3.96 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 70407692300.00 cycles -P(n): 5.11 flops/cycle +T(n): 90702048200.00 cycles +P(n): 3.97 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 362081083160.00 cycles +P(n): 1.61 flops/cycle diff --git a/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/2.log b/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/2.log index 033bc17..f46ef75 100644 --- a/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/2.log +++ b/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/2.log @@ -1,66 +1,64 @@ -rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_7_SoA_AVX2_polyexp.cpp -o main_inlining_7_SoA_AVX2_polyexp -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 9074662.50 cycles -P(n): 4.05 flops/cycle +T(n): 11368170.00 cycles +P(n): 3.23 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 31994315.00 cycles -P(n): 4.55 flops/cycle +T(n): 39909820.00 cycles +P(n): 3.65 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 120234740.00 cycles -P(n): 4.82 flops/cycle +T(n): 152279380.00 cycles +P(n): 3.80 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 719504200.00 cycles -P(n): 5.01 flops/cycle +T(n): 925130680.00 cycles +P(n): 3.90 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2844257700.00 cycles -P(n): 5.07 flops/cycle +T(n): 3668254420.00 cycles +P(n): 3.93 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 11300324680.00 cycles -P(n): 5.10 flops/cycle +T(n): 14660040260.00 cycles +P(n): 3.93 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 70554568640.00 cycles -P(n): 5.10 flops/cycle +T(n): 93340296780.00 cycles +P(n): 3.86 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772704.00 flops Q(n) per iteration: 1280112 bytes I(n): 2269.94 flops/byte W(n): 581154540800.00 flops -T(n): 281416807980.00 cycles -P(n): 2.07 flops/cycle +T(n): 362367747120.00 cycles +P(n): 1.60 flops/cycle diff --git a/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/3.log b/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/3.log new file mode 100644 index 0000000..25880a8 --- /dev/null +++ b/measurements/variant_inlining_7_SoA_AVX2_polyexp/x86/3.log @@ -0,0 +1,64 @@ +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 6512 bytes +I(n): 28.21 flops/byte +W(n): 36740000.00 flops +T(n): 11342471.25 cycles +P(n): 3.24 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 12912 bytes +I(n): 56.34 flops/byte +W(n): 145480000.00 flops +T(n): 40124180.00 cycles +P(n): 3.63 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 25712 bytes +I(n): 112.59 flops/byte +W(n): 578960000.00 flops +T(n): 151911440.00 cycles +P(n): 3.81 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 64112 bytes +I(n): 281.34 flops/byte +W(n): 3607400000.00 flops +T(n): 923357200.00 cycles +P(n): 3.91 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 128112 bytes +I(n): 562.59 flops/byte +W(n): 14414800000.00 flops +T(n): 3687447740.00 cycles +P(n): 3.91 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 256112 bytes +I(n): 1125.09 flops/byte +W(n): 57629600000.00 flops +T(n): 14732693600.00 cycles +P(n): 3.91 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800370000.00 flops +Q(n) per iteration: 640112 bytes +I(n): 2812.59 flops/byte +W(n): 360074000000.00 flops +T(n): 90864696480.00 cycles +P(n): 3.96 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 361983172200.00 cycles +P(n): 1.61 flops/cycle + diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/1.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/1.log index b6f19de..b741ee1 100644 --- a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/1.log +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 7287068.75 cycles -P(n): 5.04 flops/cycle +T(n): 8339301.25 cycles +P(n): 4.41 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 24479917.50 cycles -P(n): 5.94 flops/cycle +T(n): 28662660.00 cycles +P(n): 5.08 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 88304220.00 cycles -P(n): 6.56 flops/cycle +T(n): 104402900.00 cycles +P(n): 5.55 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 532537300.00 cycles -P(n): 6.77 flops/cycle +T(n): 636519040.00 cycles +P(n): 5.67 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2074603460.00 cycles -P(n): 6.95 flops/cycle +T(n): 2499027620.00 cycles +P(n): 5.77 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 8222911780.00 cycles -P(n): 7.01 flops/cycle +T(n): 9941452140.00 cycles +P(n): 5.80 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 51149290420.00 cycles -P(n): 7.04 flops/cycle +T(n): 61650558000.00 cycles +P(n): 5.84 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 246792591620.00 cycles +P(n): 2.35 flops/cycle diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/2.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/2.log index 1de959b..5762311 100644 --- a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/2.log +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 7237100.00 cycles -P(n): 5.08 flops/cycle +T(n): 8445907.50 cycles +P(n): 4.35 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 24227557.50 cycles -P(n): 6.00 flops/cycle +T(n): 28691310.00 cycles +P(n): 5.07 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 87321790.00 cycles -P(n): 6.63 flops/cycle +T(n): 104933160.00 cycles +P(n): 5.52 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 529210780.00 cycles -P(n): 6.82 flops/cycle +T(n): 639568720.00 cycles +P(n): 5.64 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2064752120.00 cycles -P(n): 6.98 flops/cycle +T(n): 2515964020.00 cycles +P(n): 5.73 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 8154495340.00 cycles -P(n): 7.07 flops/cycle +T(n): 9996713720.00 cycles +P(n): 5.76 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 50962729840.00 cycles -P(n): 7.07 flops/cycle +T(n): 62098746740.00 cycles +P(n): 5.80 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772704.00 flops Q(n) per iteration: 1280112 bytes I(n): 2269.94 flops/byte W(n): 581154540800.00 flops -T(n): 203476930280.00 cycles -P(n): 2.86 flops/cycle +T(n): 247013435860.00 cycles +P(n): 2.35 flops/cycle diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/3.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/3.log new file mode 100644 index 0000000..f95f05f --- /dev/null +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 6512 bytes +I(n): 28.21 flops/byte +W(n): 36740000.00 flops +T(n): 8335317.50 cycles +P(n): 4.41 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 12912 bytes +I(n): 56.34 flops/byte +W(n): 145480000.00 flops +T(n): 28557990.00 cycles +P(n): 5.09 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 25712 bytes +I(n): 112.59 flops/byte +W(n): 578960000.00 flops +T(n): 104404360.00 cycles +P(n): 5.55 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 64112 bytes +I(n): 281.34 flops/byte +W(n): 3607400000.00 flops +T(n): 635858120.00 cycles +P(n): 5.67 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 128112 bytes +I(n): 562.59 flops/byte +W(n): 14414800000.00 flops +T(n): 2509183840.00 cycles +P(n): 5.74 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 256112 bytes +I(n): 1125.09 flops/byte +W(n): 57629600000.00 flops +T(n): 9956726160.00 cycles +P(n): 5.79 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800370000.00 flops +Q(n) per iteration: 640112 bytes +I(n): 2812.59 flops/byte +W(n): 360074000000.00 flops +T(n): 61839346220.00 cycles +P(n): 5.82 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 246699025260.00 cycles +P(n): 2.36 flops/cycle + diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/1.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/1.log index d114728..18b6f5f 100644 --- a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/1.log +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 7143010.00 cycles -P(n): 5.14 flops/cycle +T(n): 8308587.50 cycles +P(n): 4.42 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 24630712.50 cycles -P(n): 5.91 flops/cycle +T(n): 28404470.00 cycles +P(n): 5.12 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 88030350.00 cycles -P(n): 6.58 flops/cycle +T(n): 103978120.00 cycles +P(n): 5.57 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 538550140.00 cycles -P(n): 6.70 flops/cycle +T(n): 633362280.00 cycles +P(n): 5.70 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2077772880.00 cycles -P(n): 6.94 flops/cycle +T(n): 2494262100.00 cycles +P(n): 5.78 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 8277950160.00 cycles -P(n): 6.96 flops/cycle +T(n): 9899453320.00 cycles +P(n): 5.82 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 51513976020.00 cycles -P(n): 6.99 flops/cycle +T(n): 61865956520.00 cycles +P(n): 5.82 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 246306063920.00 cycles +P(n): 2.36 flops/cycle diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/2.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/2.log index c0dc82b..1653c69 100644 --- a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/2.log +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 7224345.00 cycles -P(n): 5.09 flops/cycle +T(n): 8344811.25 cycles +P(n): 4.40 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 24323830.00 cycles -P(n): 5.98 flops/cycle +T(n): 28520365.00 cycles +P(n): 5.10 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 87541580.00 cycles -P(n): 6.61 flops/cycle +T(n): 104404120.00 cycles +P(n): 5.55 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 540497680.00 cycles -P(n): 6.67 flops/cycle +T(n): 636400560.00 cycles +P(n): 5.67 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2069077440.00 cycles -P(n): 6.97 flops/cycle +T(n): 2501258800.00 cycles +P(n): 5.76 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 8259242740.00 cycles -P(n): 6.98 flops/cycle +T(n): 9901533780.00 cycles +P(n): 5.82 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 51381444880.00 cycles -P(n): 7.01 flops/cycle +T(n): 61815669960.00 cycles +P(n): 5.82 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772704.00 flops Q(n) per iteration: 1280112 bytes I(n): 2269.94 flops/byte W(n): 581154540800.00 flops -T(n): 205008714840.00 cycles -P(n): 2.83 flops/cycle +T(n): 247091597180.00 cycles +P(n): 2.35 flops/cycle diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/3.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/3.log new file mode 100644 index 0000000..a60b0dc --- /dev/null +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 6512 bytes +I(n): 28.21 flops/byte +W(n): 36740000.00 flops +T(n): 8321530.00 cycles +P(n): 4.42 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 12912 bytes +I(n): 56.34 flops/byte +W(n): 145480000.00 flops +T(n): 28458355.00 cycles +P(n): 5.11 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 25712 bytes +I(n): 112.59 flops/byte +W(n): 578960000.00 flops +T(n): 104209360.00 cycles +P(n): 5.56 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 64112 bytes +I(n): 281.34 flops/byte +W(n): 3607400000.00 flops +T(n): 634333640.00 cycles +P(n): 5.69 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 128112 bytes +I(n): 562.59 flops/byte +W(n): 14414800000.00 flops +T(n): 2496796020.00 cycles +P(n): 5.77 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 256112 bytes +I(n): 1125.09 flops/byte +W(n): 57629600000.00 flops +T(n): 9914787460.00 cycles +P(n): 5.81 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800370000.00 flops +Q(n) per iteration: 640112 bytes +I(n): 2812.59 flops/byte +W(n): 360074000000.00 flops +T(n): 61721090840.00 cycles +P(n): 5.83 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 246519066200.00 cycles +P(n): 2.36 flops/cycle + diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/1.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/1.log index e886877..c33cf4f 100644 --- a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/1.log +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 6876013.75 cycles -P(n): 5.34 flops/cycle +T(n): 7985528.75 cycles +P(n): 4.60 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 23521667.50 cycles -P(n): 6.18 flops/cycle +T(n): 27703355.00 cycles +P(n): 5.25 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 85927760.00 cycles -P(n): 6.74 flops/cycle +T(n): 102292560.00 cycles +P(n): 5.66 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 524078060.00 cycles -P(n): 6.88 flops/cycle +T(n): 629804220.00 cycles +P(n): 5.73 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2052525500.00 cycles -P(n): 7.02 flops/cycle +T(n): 2472263180.00 cycles +P(n): 5.83 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 8210618700.00 cycles -P(n): 7.02 flops/cycle +T(n): 9851588140.00 cycles +P(n): 5.85 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 50972389900.00 cycles -P(n): 7.06 flops/cycle +T(n): 61479499820.00 cycles +P(n): 5.86 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 245617006060.00 cycles +P(n): 2.37 flops/cycle diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/2.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/2.log index 27bc43b..5ee8171 100644 --- a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/2.log +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 6880311.25 cycles -P(n): 5.34 flops/cycle +T(n): 8028216.25 cycles +P(n): 4.58 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 23463525.00 cycles -P(n): 6.20 flops/cycle +T(n): 27833730.00 cycles +P(n): 5.23 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 85995280.00 cycles -P(n): 6.73 flops/cycle +T(n): 102829680.00 cycles +P(n): 5.63 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 521810900.00 cycles -P(n): 6.91 flops/cycle +T(n): 629698060.00 cycles +P(n): 5.73 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2070674920.00 cycles -P(n): 6.96 flops/cycle +T(n): 2469782600.00 cycles +P(n): 5.84 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 8150338100.00 cycles -P(n): 7.07 flops/cycle +T(n): 9826240720.00 cycles +P(n): 5.86 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 50841614020.00 cycles -P(n): 7.08 flops/cycle +T(n): 61490138600.00 cycles +P(n): 5.86 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772704.00 flops Q(n) per iteration: 1280112 bytes I(n): 2269.94 flops/byte W(n): 581154540800.00 flops -T(n): 202898659340.00 cycles -P(n): 2.86 flops/cycle +T(n): 245848977420.00 cycles +P(n): 2.36 flops/cycle diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/3.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/3.log new file mode 100644 index 0000000..27a1fc5 --- /dev/null +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 6512 bytes +I(n): 28.21 flops/byte +W(n): 36740000.00 flops +T(n): 7992830.00 cycles +P(n): 4.60 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 12912 bytes +I(n): 56.34 flops/byte +W(n): 145480000.00 flops +T(n): 27693680.00 cycles +P(n): 5.25 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 25712 bytes +I(n): 112.59 flops/byte +W(n): 578960000.00 flops +T(n): 102340840.00 cycles +P(n): 5.66 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 64112 bytes +I(n): 281.34 flops/byte +W(n): 3607400000.00 flops +T(n): 638328340.00 cycles +P(n): 5.65 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 128112 bytes +I(n): 562.59 flops/byte +W(n): 14414800000.00 flops +T(n): 2468865800.00 cycles +P(n): 5.84 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 256112 bytes +I(n): 1125.09 flops/byte +W(n): 57629600000.00 flops +T(n): 9838254540.00 cycles +P(n): 5.86 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800370000.00 flops +Q(n) per iteration: 640112 bytes +I(n): 2812.59 flops/byte +W(n): 360074000000.00 flops +T(n): 61344519940.00 cycles +P(n): 5.87 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 245174501540.00 cycles +P(n): 2.37 flops/cycle + diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/1.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/1.log index 24e4936..7a9a201 100644 --- a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/1.log +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/1.log @@ -1,58 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2 -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 7796687.50 cycles -P(n): 4.71 flops/cycle +T(n): 8506121.25 cycles +P(n): 4.32 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 25453505.00 cycles -P(n): 5.72 flops/cycle +T(n): 27681145.00 cycles +P(n): 5.26 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 90370870.00 cycles -P(n): 6.41 flops/cycle +T(n): 98048030.00 cycles +P(n): 5.90 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 538274500.00 cycles -P(n): 6.70 flops/cycle +T(n): 594320700.00 cycles +P(n): 6.07 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2095953220.00 cycles -P(n): 6.88 flops/cycle +T(n): 2270890500.00 cycles +P(n): 6.35 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 8248614920.00 cycles -P(n): 6.99 flops/cycle +T(n): 8985906260.00 cycles +P(n): 6.41 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 51762509580.00 cycles -P(n): 6.96 flops/cycle +T(n): 56358587540.00 cycles +P(n): 6.39 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 224065475420.00 cycles +P(n): 2.59 flops/cycle diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/2.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/2.log index 658682f..e6443dc 100644 --- a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/2.log +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/2.log @@ -1,66 +1,66 @@ rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o -/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2 -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 === N_P=50 N_ITER=200 === W(n) per iteration: 183700.00 flops Q(n) per iteration: 6512 bytes I(n): 28.21 flops/byte W(n): 36740000.00 flops -T(n): 7778888.75 cycles -P(n): 4.72 flops/cycle +T(n): 8512763.75 cycles +P(n): 4.32 flops/cycle === N_P=100 N_ITER=200 === W(n) per iteration: 727400.00 flops Q(n) per iteration: 12912 bytes I(n): 56.34 flops/byte W(n): 145480000.00 flops -T(n): 25303990.00 cycles -P(n): 5.75 flops/cycle +T(n): 27679115.00 cycles +P(n): 5.26 flops/cycle === N_P=200 N_ITER=200 === W(n) per iteration: 2894800.00 flops Q(n) per iteration: 25712 bytes I(n): 112.59 flops/byte W(n): 578960000.00 flops -T(n): 89661540.00 cycles -P(n): 6.46 flops/cycle +T(n): 98109310.00 cycles +P(n): 5.90 flops/cycle === N_P=500 N_ITER=200 === W(n) per iteration: 18037000.00 flops Q(n) per iteration: 64112 bytes I(n): 281.34 flops/byte W(n): 3607400000.00 flops -T(n): 534011120.00 cycles -P(n): 6.76 flops/cycle +T(n): 583896260.00 cycles +P(n): 6.18 flops/cycle === N_P=1000 N_ITER=200 === W(n) per iteration: 72074000.00 flops Q(n) per iteration: 128112 bytes I(n): 562.59 flops/byte W(n): 14414800000.00 flops -T(n): 2077751560.00 cycles -P(n): 6.94 flops/cycle +T(n): 2292138700.00 cycles +P(n): 6.29 flops/cycle === N_P=2000 N_ITER=200 === W(n) per iteration: 288148000.00 flops Q(n) per iteration: 256112 bytes I(n): 1125.09 flops/byte W(n): 57629600000.00 flops -T(n): 8233407040.00 cycles -P(n): 7.00 flops/cycle +T(n): 9006037600.00 cycles +P(n): 6.40 flops/cycle === N_P=5000 N_ITER=200 === W(n) per iteration: 1800370000.00 flops Q(n) per iteration: 640112 bytes I(n): 2812.59 flops/byte W(n): 360074000000.00 flops -T(n): 51189403060.00 cycles -P(n): 7.03 flops/cycle +T(n): 56211721500.00 cycles +P(n): 6.41 flops/cycle === N_P=10000 N_ITER=200 === W(n) per iteration: 2905772704.00 flops Q(n) per iteration: 1280112 bytes I(n): 2269.94 flops/byte W(n): 581154540800.00 flops -T(n): 204461065180.00 cycles -P(n): 2.84 flops/cycle +T(n): 223879818840.00 cycles +P(n): 2.60 flops/cycle diff --git a/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/3.log b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/3.log new file mode 100644 index 0000000..c702109 --- /dev/null +++ b/measurements/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2/x86/3.log @@ -0,0 +1,66 @@ +rm -f main_* run_tests *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o +/usr/bin/g++ main.cpp ./variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2_expfix.cpp -o main_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2_expfix -O3 -march=native -std=c++23 -Wall -Wextra -flto -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0 -DBENCHMARK=1 +=== N_P=50 N_ITER=200 === +W(n) per iteration: 183700.00 flops +Q(n) per iteration: 6512 bytes +I(n): 28.21 flops/byte +W(n): 36740000.00 flops +T(n): 8550285.00 cycles +P(n): 4.30 flops/cycle + +=== N_P=100 N_ITER=200 === +W(n) per iteration: 727400.00 flops +Q(n) per iteration: 12912 bytes +I(n): 56.34 flops/byte +W(n): 145480000.00 flops +T(n): 27834905.00 cycles +P(n): 5.23 flops/cycle + +=== N_P=200 N_ITER=200 === +W(n) per iteration: 2894800.00 flops +Q(n) per iteration: 25712 bytes +I(n): 112.59 flops/byte +W(n): 578960000.00 flops +T(n): 98660270.00 cycles +P(n): 5.87 flops/cycle + +=== N_P=500 N_ITER=200 === +W(n) per iteration: 18037000.00 flops +Q(n) per iteration: 64112 bytes +I(n): 281.34 flops/byte +W(n): 3607400000.00 flops +T(n): 587210580.00 cycles +P(n): 6.14 flops/cycle + +=== N_P=1000 N_ITER=200 === +W(n) per iteration: 72074000.00 flops +Q(n) per iteration: 128112 bytes +I(n): 562.59 flops/byte +W(n): 14414800000.00 flops +T(n): 2280133880.00 cycles +P(n): 6.32 flops/cycle + +=== N_P=2000 N_ITER=200 === +W(n) per iteration: 288148000.00 flops +Q(n) per iteration: 256112 bytes +I(n): 1125.09 flops/byte +W(n): 57629600000.00 flops +T(n): 8974668040.00 cycles +P(n): 6.42 flops/cycle + +=== N_P=5000 N_ITER=200 === +W(n) per iteration: 1800370000.00 flops +Q(n) per iteration: 640112 bytes +I(n): 2812.59 flops/byte +W(n): 360074000000.00 flops +T(n): 56052905260.00 cycles +P(n): 6.42 flops/cycle + +=== N_P=10000 N_ITER=200 === +W(n) per iteration: 2905772704.00 flops +Q(n) per iteration: 1280112 bytes +I(n): 2269.94 flops/byte +W(n): 581154540800.00 flops +T(n): 224433219680.00 cycles +P(n): 2.59 flops/cycle + diff --git a/variants/variant_branchless_flopcount.cpp b/variants/variant_branchless_flopcount.cpp deleted file mode 100644 index d903659..0000000 --- a/variants/variant_branchless_flopcount.cpp +++ /dev/null @@ -1,426 +0,0 @@ -// This file is merged from the forces.cpp and social_forces_baseline.cpp -// The goal is that we from now on only have one file as our baseline -// This should make it more easy in the future to implement optimizations -// Our naming convention from now on is: variant_.cpp (Please make sure to keep that consistent, otherwise our Makefile wont work anymore) - -// # Changes: FLOPCOUNT -// # Predecessor: variant_branchless.cpp - -// n_iter * [n_p ** 2 * (15 A + 24 M + 1 D + 4 S + 1 E) + n_p * (- 4 A - 4 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (13 A + 19 M + 1 D + 2 S + 1 E)] - - - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/forces.hpp" -#include "../headers/pedestrian.hpp" -#include "../headers/social_force.hpp" -#include "../headers/vector2d.hpp" - -#include -#include -#include -#include -#include // keep — forces code uses unqualified sqrt/exp -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// forces.cpp - -// Flop count: 3 ADDs, 6 MULTs, 1 SQRT -double w(Vector2d e, Vector2d f) { - - // 1 ADD + 2 MULT - double dot = e.dot(f); - // 1 ADD + 2 MULT + 1 SQRT - double f_norm = f.norm(); - // 1 MULT - double cond = (double)(dot >= f_norm * cos_phi); // Should be 1 if in the field of view, else 0 - // 1 ADD + 1 MULT - return c_neg_times_dt + cond * (dt_neg - c_neg_times_dt); -} - -// Flop count: 2 ADDs, 4 MULTs -Vector2d acceleration_term_dt(const Pedestrian &alpha) { - - // # 2 MULTs - Vector2d desired_velocity = alpha.e * alpha.desired_v0; - - Vector2d actual_velocity = alpha.curr_vel; - - // # 2 ADDs, 2 MULTs - Vector2d f_alpha_0_dt = (desired_velocity - actual_velocity) * dt_times_tau_alpha_inv; - - return f_alpha_0_dt; -} - -// Flop count: 13 ADDs, 24 MULTs, 1 DIV, 4 SQRTs, 1 EXP -Vector2d pedestrian_repulsion_dt(const Pedestrian &alpha, const Pedestrian &beta) { - - // # 2 ADDs - // $\vec{r}_{\alpha\beta}$ - Vector2d r_ab = alpha.curr_pos - beta.curr_pos; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert$ - double r_ab_norm = r_ab.norm(); - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; // Avoid division by zero - - // $v_\beta \Delta t \vec{e}_\beta$ - Vector2d s_b_e = beta.s_b_e; - - // # 2 ADDs - // $\vec{r}_{\alpha\beta} - \vec{v}_{\beta}\varDelta_{t}\vec{e}_{\beta}$ - Vector2d r_ab2 = r_ab - s_b_e; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab2_norm = r_ab2.norm(); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; // Avoid division by zero - - // # 1 ADD - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULTs, 1 SQRT - // - // $2b = \sqrt{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)^2 - (v_\beta \Delta t)^2}$ - // - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - beta.s_b_sq; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; // Avoid sqrt of negative and avoid division by zero - double two_b = sqrt(two_b_sq); - - // # 5 MULTs, 1 DIV, 1 EXP - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}$ - // - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 2 ADDs, 4 MULTs - // - // $\vec{-f}_{\alpha \beta}(\vec{r}_{\alpha \beta}) = \frac{-V^0_{\alpha \beta}}{2\sigma}e^{-b / \sigma} 2\grad_{\vec{r}_{\alpha \beta}}b $ - // - // - // $= \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - - // v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - // - // $2\grad_{\vec{r}_{\alpha \beta}}b = \frac{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)(\frac{\vec{r}_{\alpha \beta}}{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert} + \frac{\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta}{\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert})}{2b}$ - // - // - // $= \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - Vector2d f_alpha_beta_neg = r_ab * scalar_mult_r_ab + r_ab2 * scalar_mult_r_ab2; - - // # 3 ADDs, 8 MULTs, 1 SQRT - // - // $\vec{F}_{\alpha\beta}(\vec{e}_{\alpha}, \vec{r}_{\alpha \beta}).dt = w(\vec{e}_{\alpha}, -\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta})).\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta}).dt$ - // - Vector2d f_alpha_beta_dt = f_alpha_beta_neg * w(alpha.e, f_alpha_beta_neg); - - return f_alpha_beta_dt; -} - -// Flop count: 11 ADDs, 19 MULTs, 1 DIVs, 2 SQRTs, 1 EXP -Vector2d obstacle_repulsion_dt(const Pedestrian &alpha, const Obstacle &B) { - - // # 7 ADDs, 5 MULTs - // $\vec{r}_{\alpha B}$ - Vector2d r_aB = alpha.curr_pos - B.find_nearest_obstacle_point(alpha.curr_pos); - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha B} \Arrowvert$ - double r_aB_norm = r_aB.norm(); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; // Avoid division by zero - - // # 2 MULTs, 1 DIV, 1 EXP - // - // $\frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R}$ - // - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - - // # 2 MULTs - // - // $-\vec{F}_{\alpha B}(\vec{r}_{\alpha B}) = \frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R} \frac{\vec{r}_{\alpha B}}{\Arrowvert \vec{r}_{\alpha B} \Arrowvert}$ - // - Vector2d f_alpha_B_neg = r_aB * scalar_mult; - - // # 3 ADDs, 8 MULTs, 1 SQRT - // - // $\vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha B}).dt = w(\vec{e}_{\alpha}, -\vec{F}_{\alpha B}(\vec{r}_{\alpha B})).\vec{F}_{\alpha B}(\vec{r}_{\alpha B}).dt$ - // - Vector2d f_alpha_B_dt = f_alpha_B_neg * w(alpha.e, f_alpha_B_neg); - - return f_alpha_B_dt; -} - -// social_force_baseline.cpp - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; // Platform-provided random seed source. - mt19937 gen(rd()); // Non-reproducible initialization. -#else - mt19937 gen(SEED); // Reproducible initialization. -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - // Group of pedestrians walking from left to right. - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(BOARD_LENGTH + 30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(WHITE, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - // Group of pedestrians walking from right to left. - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(-30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(BLACK, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - // Avoid compiler unused parameter warning - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - // Each sample stores: time step, pedestrian id, x, y, e.x, e.y, color. - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - // Store the initial state before the first simulation step. - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } - } -#endif - - // n_iter * [ {n_p * {[(n_p - 1) * (13 ADDs, 24 MULTs, 1 DIV, 4 SQRTs, 1 EXP + 2 ADDs)] + [2 ADD + 4 MULT + (n_obstacles * (11 ADDs, 19 MULTs, 1 DIV, 2 SQRTs, 1 EXP) + n_obstacles * 2 ADDs) + 2 ADD ]}} + {(7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p} ] - // n_iter * [ {n_p * {[(n_p - 1) * (13 A + 24 M + 1 D + 4 S + 1 E + 2 A)] + [2 A + 4 M + (n_obstacles * (11 A + 19 M + 1 D + 2 S + 1 E) + n_obstacles * 2 A) + 2 A ]}} + {n_p * (7 A + 16 M + 2 D + 3 S)} ] - // n_iter * [n_p ** 2 * (15 A + 24 M + 1 D + 4 S + 1 E) + n_p * (- 11 A - 20 M - 1 D - 4 S - 1 E) + n_obstacles * n_p * (13 A + 19 M + 1 D + 2 S + 1 E) + n_p * (7 A + 16 M + 2 D + 3 S)] - // n_iter * [n_p ** 2 * (15 A + 24 M + 1 D + 4 S + 1 E) + n_p * (- 11 A - 20 M - 1 D - 4 S - 1 E) + n_p * (7 A + 16 M + 2 D + 3 S) + n_obstacles * n_p * (13 A + 19 M + 1 D + 2 S + 1 E)] - // n_iter * [n_p ** 2 * (15 A + 24 M + 1 D + 4 S + 1 E) + n_p * (- 4 A - 4 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (13 A + 19 M + 1 D + 2 S + 1 E)] - - - for (int t = 0; t < n_iter; t++) { - // Pass 1: Compute all forces from the current state. - // n_p * {[(n_p - 1) * (13 ADDs, 24 MULTs, 1 DIV, 4 SQRTs, 1 EXP + 2 ADDs)] + [(2 ADD + 4 MULT + (n_obstacles * (11 ADDs, 19 MULTs, 1 DIV, 2 SQRTs, 1 EXP) + n_obstacles * 2 ADDs) + 2 ADD) ]} - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 2 ADDs, 4 MULTs - // - // $\vec{F}_\alpha(\vec{v}_\alpha, v^0_\alpha \vec{e}_\alpha) = \frac{1}{\tau_\alpha}(v^0_\alpha \vec{e}_\alpha - \vec{v}_\alpha)$ - // - Vector2d f_alpha_dt = acceleration_term_dt(p); - - // # (N-1) * (13 ADDs, 24 MULTs, 1 DIV, 4 SQRTs, 1 EXP) - // + (N-1) * 2 ADDs - // - // $\sum_{\beta} \vec{F}_{\alpha\beta}(\vec{e}_{\alpha},\vec{r}_{\alpha} - \vec{r}_{\beta})$ - // - // for (int j = 0; j < n_p; j++) { - // if (i == j) - // continue; - // - // f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]); - //} - for (int j = 0; j < i; j++) { - f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]); - } - for (int j = i + 1; j < n_p; j++) { - f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]); - } - // # board.n_obstacles * (11 ADDs, 19 MULTs, 1 DIV, 2 SQRTs, 1 EXP) - // + board.n_obstacles * 2 ADDs - // - // $\sum_{B} \vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha} - \vec{r}_{B})$ - // - for (int k = 0; k < board.n_obstacles; k++) { - f_alpha_dt += obstacle_repulsion_dt(p, board.obstacles[k]); - } - - // # 2 ADDs - // - // - // $\frac{d\vec{w}_\alpha}{dt} = \vec{F}_{\alpha}(t)$ - // - p.w += f_alpha_dt; - } - - // Pass 2: Update pedestrian states after all forces have been computed. - // # (7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - double w_norm = p.w.norm(); - - double w_norm2 = w_norm > EPS ? w_norm : EPS; // Avoid division by zero. - - // # 1 DIV - double correction_factor = std::min(1.0, p.v_max / w_norm2); - - // # 2 MULTs - p.curr_vel = p.w * correction_factor; - - // # 2 ADDs, 2 MULTs - // - // $\vec{r}_i^{t+1} = \vec{r}_i^t + \vec{v}_i^t dt$ - // - p.curr_pos = p.curr_pos + p.curr_vel * dt; - - // When a pedestrian reaches the end of the corridor, teleport them back - // to the opposite entrance while keeping the same y-coordinate. This - // maintains a constant pedestrian flow and makes lane formation easier to - // observe. - if (p.color == WHITE && p.curr_pos.x > (BOARD_LENGTH - 1.5)) { - p.curr_pos.x = 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(p.desired_v0, 0.0); - p.curr_vel = p.w; - } - if (p.color == BLACK && p.curr_pos.x < 1.5) { - p.curr_pos.x = BOARD_LENGTH - 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(-p.desired_v0, 0.0); - p.curr_vel = p.w; - } - - // Keep pedestrians inside the board boundaries. - p.curr_pos.y = clamp(p.curr_pos.y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 2 ADDs - Vector2d e = p.destination - p.curr_pos; - - // # 1 ADD, 2 MULTs, 1 DIV, 1 SQRT - double e_norm = e.norm(); - double e_norm2 = std::fmax(e_norm, EPS); // Avoid division by zero. - double e_norm_inv = 1.0 / e_norm2; - - // # 2 MULTs - p.e = e * e_norm_inv; - - // # 1 ADD, 2 MULTs - double velocity_norm_sq = p.curr_vel.dot(p.curr_vel); - - // # 1 SQRT - double velocity_norm = std::sqrt(velocity_norm_sq); - - // # 1 MULT - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - // # 3 MULTs - p.s_b_e = p.e * (velocity_norm * delta_t); - -#if !BENCHMARK - // Store the current pedestrian state for visualization. - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } -#endif - } - } - -#if !BENCHMARK - // Export recorded simulation states to CSV. - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} \ No newline at end of file diff --git a/variants/variant_branchless_sqrtfree_flopcount.cpp b/variants/variant_branchless_sqrtfree_flopcount.cpp deleted file mode 100644 index ed3ff6b..0000000 --- a/variants/variant_branchless_sqrtfree_flopcount.cpp +++ /dev/null @@ -1,428 +0,0 @@ -// This file is merged from the forces.cpp and social_forces_baseline.cpp -// The goal is that we from now on only have one file as our baseline -// This should make it more easy in the future to implement optimizations -// Our naming convention from now on is: variant_.cpp (Please make sure to keep that consistent, otherwise our Makefile wont work anymore) - -// # Changes: FLOPCOUNT -// # Predecessor: variant_branchless_sqrtfree.cpp -// n_iter * [n_p ** 2 * (15 A + 25 M + 1 D + 3 S + 1 E) + n_p * (- 4 A - 5 M + 1 D - 1 E) + n_obstacles * n_p * (13 A + 20 M + 1 D + 1 S + 1 E)] - - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/forces.hpp" -#include "../headers/pedestrian.hpp" -#include "../headers/social_force.hpp" -#include "../headers/vector2d.hpp" - -#include -#include -#include -#include -#include // keep — forces code uses unqualified sqrt/exp -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// forces.cpp - -// Flop count: 3 ADDs, 7 MULTs (no SQRT, no branch) -// Branchless + sqrt-free: cos_phi < 0 so f.norm()*cos_phi <= 0. -// - If dot >= 0: condition is trivially true. -// - If dot < 0: both sides non-positive; square them safely: -// dot^2 <= f.dot(f) * cos_phi^2. -// Bitwise | (not ||) keeps both sides evaluated and avoids a branch. - -// 3 ADD + 7 MULT -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; - 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); -} - -// Flop count: 2 ADDs, 4 MULTs -Vector2d acceleration_term_dt(const Pedestrian &alpha) { - - // # 2 MULTs - Vector2d desired_velocity = alpha.e * alpha.desired_v0; - - Vector2d actual_velocity = alpha.curr_vel; - - // # 2 ADDs, 2 MULTs - Vector2d f_alpha_0_dt = (desired_velocity - actual_velocity) * dt_times_tau_alpha_inv; - - return f_alpha_0_dt; -} - -// Flop count: 13 ADDs, 25 MULTs, 1 DIV, 3 SQRTs, 1 EXP -Vector2d pedestrian_repulsion_dt(const Pedestrian &alpha, const Pedestrian &beta) { - - // # 2 ADDs - // $\vec{r}_{\alpha\beta}$ - Vector2d r_ab = alpha.curr_pos - beta.curr_pos; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert$ - double r_ab_norm = r_ab.norm(); - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; // Avoid division by zero - - // $v_\beta \Delta t \vec{e}_\beta$ - Vector2d s_b_e = beta.s_b_e; - - // # 2 ADDs - // $\vec{r}_{\alpha\beta} - \vec{v}_{\beta}\varDelta_{t}\vec{e}_{\beta}$ - Vector2d r_ab2 = r_ab - s_b_e; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab2_norm = r_ab2.norm(); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; // Avoid division by zero - - // # 1 ADD - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULTs, 1 SQRT - // - // $2b = \sqrt{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)^2 - (v_\beta \Delta t)^2}$ - // - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - beta.s_b_sq; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; // Avoid sqrt of negative and avoid division by zero - double two_b = sqrt(two_b_sq); - - // # 5 MULTs, 1 DIV, 1 EXP - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}$ - // - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 2 ADDs, 4 MULTs - // - // $\vec{-f}_{\alpha \beta}(\vec{r}_{\alpha \beta}) = \frac{-V^0_{\alpha \beta}}{2\sigma}e^{-b / \sigma} 2\grad_{\vec{r}_{\alpha \beta}}b $ - // - // - // $= \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - - // v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - // - // $2\grad_{\vec{r}_{\alpha \beta}}b = \frac{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)(\frac{\vec{r}_{\alpha \beta}}{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert} + \frac{\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta}{\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert})}{2b}$ - // - // - // $= \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - Vector2d f_alpha_beta_neg = r_ab * scalar_mult_r_ab + r_ab2 * scalar_mult_r_ab2; - - // # 3 ADDs, 9 MULTs - // - // $\vec{F}_{\alpha\beta}(\vec{e}_{\alpha}, \vec{r}_{\alpha \beta}).dt = w(\vec{e}_{\alpha}, -\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta})).\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta}).dt$ - // - Vector2d f_alpha_beta_dt = f_alpha_beta_neg * w(alpha.e, f_alpha_beta_neg); - - return f_alpha_beta_dt; -} - -// Flop count: 11 ADDs, 20 MULTs, 1 DIVs, 1 SQRT, 1 EXP -Vector2d obstacle_repulsion_dt(const Pedestrian &alpha, const Obstacle &B) { - - // # 7 ADDs, 5 MULTs - // $\vec{r}_{\alpha B}$ - Vector2d r_aB = alpha.curr_pos - B.find_nearest_obstacle_point(alpha.curr_pos); - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha B} \Arrowvert$ - double r_aB_norm = r_aB.norm(); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; // Avoid division by zero - - // # 2 MULTs, 1 DIV, 1 EXP - // - // $\frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R}$ - // - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - - // # 2 MULTs - // - // $-\vec{F}_{\alpha B}(\vec{r}_{\alpha B}) = \frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R} \frac{\vec{r}_{\alpha B}}{\Arrowvert \vec{r}_{\alpha B} \Arrowvert}$ - // - Vector2d f_alpha_B_neg = r_aB * scalar_mult; - - // # 3 ADDs, 9 MULTs - // - // $\vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha B}).dt = w(\vec{e}_{\alpha}, -\vec{F}_{\alpha B}(\vec{r}_{\alpha B})).\vec{F}_{\alpha B}(\vec{r}_{\alpha B}).dt$ - // - Vector2d f_alpha_B_dt = f_alpha_B_neg * w(alpha.e, f_alpha_B_neg); - - return f_alpha_B_dt; -} - -// social_force_baseline.cpp - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; // Platform-provided random seed source. - mt19937 gen(rd()); // Non-reproducible initialization. -#else - mt19937 gen(SEED); // Reproducible initialization. -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - // Group of pedestrians walking from left to right. - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(BOARD_LENGTH + 30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(WHITE, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - // Group of pedestrians walking from right to left. - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(-30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(BLACK, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - // Avoid compiler unused parameter warning - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - // Each sample stores: time step, pedestrian id, x, y, e.x, e.y, color. - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - // Store the initial state before the first simulation step. - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } - } -#endif - - // n_iter * [{n_p * [(n_p - 1) * (13 ADDs, 25 MULTs, 1 DIV, 3 SQRTs, 1 EXP) + (n_p-1) * (2 ADDs) + n_obstacles * (11 ADDs, 20 MULTs, 1 DIV, 1 SQRTs, 1 EXP) + n_obstacles * (2 ADDs) + 4 ADDs + 4 MULTs]} + {(7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p}] - // n_iter * [{n_p * [(n_p - 1) * (13 A + 25 M + 1 D + 3 S + 1 E) + (n_p - 1) * (2 A) + n_obstacles * (11 A + 20 M + 1 D + 1 S + 1 E) + n_obstacles * (2 A) + 4 A + 4 M]} + {(7 A + 16 M + 2 D + 3 S) * n_p}] - // n_iter * [{n_p * [n_p * (13 A + 25 M + 1 D + 3 S + 1 E) - 1 * (13 A + 25 M + 1 D + 3 S + 1 E) + n_p * (2 A) - 1 * (2 A) + n_obstacles * (11 A + 20 M + 1 D + 1 S + 1 E) + n_obstacles * (2 A) + 4 A + 4 M]} + {n_p * (7 A + 16 M + 2 D + 3 S)}] - // n_iter * [{n_p * [n_p * (15 A + 25 M + 1 D + 3 S + 1 E) - 1 * (15 A + 25 M + 1 D + 3 S + 1 E - 4 A - 4 M) + n_obstacles * (13 A + 20 M + 1 D + 1 S + 1 E)]} + {n_p * (7 A + 16 M + 2 D + 3 S)}] - // n_iter * [n_p ** 2 * (15 A + 25 M + 1 D + 3 S + 1 E) - n_p * (11 A + 21 M + 1 D + 3 S + 1 E) + n_obstacles * n_p * (13 A + 20 M + 1 D + 1 S + 1 E) + n_p * (7 A + 16 M + 2 D + 3 S)] - // n_iter * [n_p ** 2 * (15 A + 25 M + 1 D + 3 S + 1 E) + n_p * (- 11 A - 21 M - 1 D - 3 S - 1 E) + n_p * (7 A + 16 M + 2 D + 3 S) + n_obstacles * n_p * (13 A + 20 M + 1 D + 1 S + 1 E)] - // n_iter * [n_p ** 2 * (15 A + 25 M + 1 D + 3 S + 1 E) + n_p * (- 4 A - 5 M + 1 D - 1 E) + n_obstacles * n_p * (13 A + 20 M + 1 D + 1 S + 1 E)] - for (int t = 0; t < n_iter; t++) { - // Pass 1: Compute all forces from the current state. - - // n_p * [(n_p - 1) * (13 ADDs, 24 MULTs, 1 DIV, 3 SQRTs, 1 EXP) + (n_p-1) * (2 ADDs) + n_obstacles * (11 ADDs, 19 MULTs, 1 DIV, 1 SQRT, 1 EXP) + n_obstacles * (2 ADDs) + 4 ADDs + 4 MULTs] - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 2 ADDs, 4 MULTs - // - // $\vec{F}_\alpha(\vec{v}_\alpha, v^0_\alpha \vec{e}_\alpha) = \frac{1}{\tau_\alpha}(v^0_\alpha \vec{e}_\alpha - \vec{v}_\alpha)$ - // - Vector2d f_alpha_dt = acceleration_term_dt(p); - - // # (N-1) * (13 ADDs, 25 MULTs, 1 DIV, 3 SQRT, 1 EXP) - // + (N-1) * 2 ADDs - // - // $\sum_{\beta} \vec{F}_{\alpha\beta}(\vec{e}_{\alpha},\vec{r}_{\alpha} - \vec{r}_{\beta})$ - // - // for (int j = 0; j < n_p; j++) { - // if (i == j) - // continue; - // - // f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]); - //} - for (int j = 0; j < i; j++) { - f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]); - } - for (int j = i + 1; j < n_p; j++) { - f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]); - } - // # board.n_obstacles * (11 ADDs, 20 MULTs, 1 DIV, 1 SQRT, 1 EXP) - // + board.n_obstacles * 2 ADDs - // - // $\sum_{B} \vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha} - \vec{r}_{B})$ - // - for (int k = 0; k < board.n_obstacles; k++) { - f_alpha_dt += obstacle_repulsion_dt(p, board.obstacles[k]); - } - - // # 2 ADDs - // - // - // $\frac{d\vec{w}_\alpha}{dt} = \vec{F}_{\alpha}(t)$ - // - p.w += f_alpha_dt; - } - - // Pass 2: Update pedestrian states after all forces have been computed. - // # (7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - double w_norm = p.w.norm(); - - double w_norm2 = w_norm > EPS ? w_norm : EPS; // Avoid division by zero. - - // # 1 DIV - double correction_factor = std::min(1.0, p.v_max / w_norm2); - - // # 2 MULTs - p.curr_vel = p.w * correction_factor; - - // # 2 ADDs, 2 MULTs - // - // $\vec{r}_i^{t+1} = \vec{r}_i^t + \vec{v}_i^t dt$ - // - p.curr_pos = p.curr_pos + p.curr_vel * dt; - - // When a pedestrian reaches the end of the corridor, teleport them back - // to the opposite entrance while keeping the same y-coordinate. This - // maintains a constant pedestrian flow and makes lane formation easier to - // observe. - if (p.color == WHITE && p.curr_pos.x > (BOARD_LENGTH - 1.5)) { - p.curr_pos.x = 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(p.desired_v0, 0.0); - p.curr_vel = p.w; - } - if (p.color == BLACK && p.curr_pos.x < 1.5) { - p.curr_pos.x = BOARD_LENGTH - 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(-p.desired_v0, 0.0); - p.curr_vel = p.w; - } - - // Keep pedestrians inside the board boundaries. - p.curr_pos.y = clamp(p.curr_pos.y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 2 ADDs - Vector2d e = p.destination - p.curr_pos; - - // # 1 ADD, 2 MULTs, 1 DIV, 1 SQRT - double e_norm = e.norm(); - double e_norm2 = std::fmax(e_norm, EPS); // Avoid division by zero. - double e_norm_inv = 1.0 / e_norm2; - - // # 2 MULTs - p.e = e * e_norm_inv; - - // # 1 ADD, 2 MULTs - double velocity_norm_sq = p.curr_vel.dot(p.curr_vel); - - // # 1 SQRT - double velocity_norm = std::sqrt(velocity_norm_sq); - - // # 1 MULT - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - // # 3 MULTs - p.s_b_e = p.e * (velocity_norm * delta_t); - -#if !BENCHMARK - // Store the current pedestrian state for visualization. - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } -#endif - } - } - -#if !BENCHMARK - // Export recorded simulation states to CSV. - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} \ No newline at end of file diff --git a/variants/variant_cutoff_callsite_flopcount.cpp b/variants/variant_cutoff_callsite_flopcount.cpp deleted file mode 100644 index 969f59c..0000000 --- a/variants/variant_cutoff_callsite_flopcount.cpp +++ /dev/null @@ -1,436 +0,0 @@ -// This file is copied from the baseline and modified -// Our goal is to only calculate pedestrian-pedestrian forces for pedestrian which are near -// Our tolerance is 1cm and our cutoff is a 5m radius. -// Since we have some tolerance and we put pedestrians back at the end of the hallway once they are on one end. We only test/verify on a smaller subsample. -// Otherwise we had huge gaps (because of those jumps back) -// In this version we do the cutoff before we call the pedestrian forces function - -// # Changes: FLOPCOUNT -// # Predecessor: variant_cutoff_callsite.cpp - -// // n_iter * [ n_p ** 2 * (14 A + 23 M + 1 D + 4 S + 1 E) + n_p * (- 3 A - 3 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (12 A + 18 M + 1 D + 2 S + 1 E) ] - - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian.hpp" -#include "../headers/vector2d.hpp" - -#include -#include -#include -#include -#include // keep — forces code uses unqualified sqrt/exp -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// forces.cpp - -// Flop count: 2 ADDs, 5 MULTs, 1 SQRT -double w(Vector2d e, Vector2d f) { - if (e.dot(f) >= f.norm() * cos_phi) - return dt_neg; - else - return c_neg_times_dt; -} - -// Flop count: 2 ADDs, 4 MULTs -Vector2d acceleration_term_dt(const Pedestrian &alpha) { - - // # 2 MULTs - Vector2d desired_velocity = alpha.e * alpha.desired_v0; - - Vector2d actual_velocity = alpha.curr_vel; - - // # 2 ADDs, 2 MULTs - Vector2d f_alpha_0_dt = (desired_velocity - actual_velocity) * dt_times_tau_alpha_inv; - - return f_alpha_0_dt; -} - -// Flop count: 12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP -Vector2d pedestrian_repulsion_dt(const Pedestrian &alpha, const Pedestrian &beta) { - - // # 2 ADDs - // $\vec{r}_{\alpha\beta}$ - Vector2d r_ab = alpha.curr_pos - beta.curr_pos; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert$ - double r_ab_norm = r_ab.norm(); - // Now we wanna check if it is a close pedestrian or wheter it is far away and can be ignored - // double r_ab_sq = r_ab.x * r_ab.x + r_ab.y * r_ab.y; // squared distance - // if (r_ab_sq > CUTOFF_SQ) - // return Vector2d(0.0, 0.0); // cheap reject - // double r_ab_norm = sqrt(r_ab_sq); // reuse r_ab_sq, no recompute - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; // Avoid division by zero - - // $v_\beta \Delta t \vec{e}_\beta$ - Vector2d s_b_e = beta.s_b_e; - - // # 2 ADDs - // $\vec{r}_{\alpha\beta} - \vec{v}_{\beta}\varDelta_{t}\vec{e}_{\beta}$ - Vector2d r_ab2 = r_ab - s_b_e; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab2_norm = r_ab2.norm(); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; // Avoid division by zero - - // # 1 ADD - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULTs, 1 SQRT - // - // $2b = \sqrt{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)^2 - (v_\beta \Delta t)^2}$ - // - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - beta.s_b_sq; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; // Avoid sqrt of negative and avoid division by zero - double two_b = sqrt(two_b_sq); - - // # 5 MULTs, 1 DIV, 1 EXP - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}$ - // - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 2 ADDs, 4 MULTs - // - // $\vec{-f}_{\alpha \beta}(\vec{r}_{\alpha \beta}) = \frac{-V^0_{\alpha \beta}}{2\sigma}e^{-b / \sigma} 2\grad_{\vec{r}_{\alpha \beta}}b $ - // - // - // $= \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - - // v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - // - // $2\grad_{\vec{r}_{\alpha \beta}}b = \frac{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)(\frac{\vec{r}_{\alpha \beta}}{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert} + \frac{\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta}{\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert})}{2b}$ - // - // - // $= \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - Vector2d f_alpha_beta_neg = r_ab * scalar_mult_r_ab + r_ab2 * scalar_mult_r_ab2; - - // # 2 ADDs, 7 MULTs, 1 SQRT - // - // $\vec{F}_{\alpha\beta}(\vec{e}_{\alpha}, \vec{r}_{\alpha \beta}).dt = w(\vec{e}_{\alpha}, -\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta})).\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta}).dt$ - // - Vector2d f_alpha_beta_dt = f_alpha_beta_neg * w(alpha.e, f_alpha_beta_neg); - - return f_alpha_beta_dt; -} - -// Flop count: 10 ADDs, 18 MULTs, 1 DIVs, 2 SQRTs, 1 EXP -Vector2d obstacle_repulsion_dt(const Pedestrian &alpha, const Obstacle &B) { - - // # 7 ADDs, 5 MULTs - // $\vec{r}_{\alpha B}$ - Vector2d r_aB = alpha.curr_pos - B.find_nearest_obstacle_point(alpha.curr_pos); - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha B} \Arrowvert$ - double r_aB_norm = r_aB.norm(); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; // Avoid division by zero - - // # 2 MULTs, 1 DIV, 1 EXP - // - // $\frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R}$ - // - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - - // # 2 MULTs - // - // $-\vec{F}_{\alpha B}(\vec{r}_{\alpha B}) = \frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R} \frac{\vec{r}_{\alpha B}}{\Arrowvert \vec{r}_{\alpha B} \Arrowvert}$ - // - Vector2d f_alpha_B_neg = r_aB * scalar_mult; - - // # 2 ADDs, 7 MULTs, 1 SQRT - // - // $\vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha B}).dt = w(\vec{e}_{\alpha}, -\vec{F}_{\alpha B}(\vec{r}_{\alpha B})).\vec{F}_{\alpha B}(\vec{r}_{\alpha B}).dt$ - // - Vector2d f_alpha_B_dt = f_alpha_B_neg * w(alpha.e, f_alpha_B_neg); - - return f_alpha_B_dt; -} - -// social_force_baseline.cpp - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; // Platform-provided random seed source. - mt19937 gen(rd()); // Non-reproducible initialization. -#else - mt19937 gen(SEED); // Reproducible initialization. -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - // Group of pedestrians walking from left to right. - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(BOARD_LENGTH + 30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(WHITE, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - // Group of pedestrians walking from right to left. - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(-30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(BLACK, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - // Avoid compiler unused parameter warning - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - // Each sample stores: time step, pedestrian id, x, y, e.x, e.y, color. - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - // Store the initial state before the first simulation step. - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } - } -#endif - - // n_iter * [ { n_p * (n_p - 1) * (12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP + 2 ADDs) + n_p * (4 ADD + 4 MULT + board.n_obstacles * (10 ADDs, 18 MULTs, 1 DIV, 2 SQRTs, 1 EXP + 2 ADD) ) } + {(7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p} ] - - // n_iter * [ { n_p * (n_p - 1) * (12 A + 23 M + 1 D + 4 S + 1 E + 2 A) + n_p * (4 A + 4 M + n_obstacles * (10 A + 18 M + 1 D + 2 S + 1 E + 2 A) ) } + {n_p * (7 A + 16 M + 2 D + 3 S)} ] - - // n_iter * [ { n_p ** 2 * (12 A + 23 M + 1 D + 4 S + 1 E + 2 A) - n_p * (12 A + 23 M + 1 D + 4 S + 1 E + 2 A) + n_p * (4 A + 4 M) + n_obstacles * n_p * (10 A + 18 M + 1 D + 2 S + 1 E + 2 A) } + {n_p * (7 A + 16 M + 2 D + 3 S)} ] - - - // n_iter * [ n_p ** 2 * (12 A + 23 M + 1 D + 4 S + 1 E + 2 A) + n_p * (- 8 A - 19 M - 1 D - 4 S - 1 E - 2 A) + n_obstacles * n_p * (10 A + 18 M + 1 D + 2 S + 1 E + 2 A) + n_p * (7 A + 16 M + 2 D + 3 S) ] - - - // n_iter * [ n_p ** 2 * (14 A + 23 M + 1 D + 4 S + 1 E) + n_p * (- 3 A - 3 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (12 A + 18 M + 1 D + 2 S + 1 E) ] - -// NOTE: this flop count charges every ped-ped pair the full cost. The cutoff -// (L80-81) early-rejects pairs farther than 5m, so the real ped-ped work is only -// a fraction of this (roughly 40-50% on a 10x50 hallway, depending on the layout). -// We keep it at 100% here as the upper-bound / fixed-W baseline, the exact fraction -// can be folded into the n_p**2 term later if we want the actual executed flops. - - for (int t = 0; t < n_iter; t++) { - // Pass 1: Compute all forces from the current state. - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 2 ADDs, 4 MULTs - // - // $\vec{F}_\alpha(\vec{v}_\alpha, v^0_\alpha \vec{e}_\alpha) = \frac{1}{\tau_\alpha}(v^0_\alpha \vec{e}_\alpha - \vec{v}_\alpha)$ - // - Vector2d f_alpha_dt = acceleration_term_dt(p); - - // # (N-1) * (12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP) - // + (N-1) * 2 ADDs - // - // $\sum_{\beta} \vec{F}_{\alpha\beta}(\vec{e}_{\alpha},\vec{r}_{\alpha} - \vec{r}_{\beta})$ - // - for (int j = 0; j < n_p; j++) { - if (i == j) - continue; - - // Cutoff check at the call site (so we dont have an additional call) - 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]); - } - - // # board.n_obstacles * (10 ADDs, 18 MULTs, 1 DIV, 2 SQRTs, 1 EXP) - // + board.n_obstacles * 2 ADDs - // - // $\sum_{B} \vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha} - \vec{r}_{B})$ - // - for (int k = 0; k < board.n_obstacles; k++) { - f_alpha_dt += obstacle_repulsion_dt(p, board.obstacles[k]); - } - - // # 2 ADDs - // - // - // $\frac{d\vec{w}_\alpha}{dt} = \vec{F}_{\alpha}(t)$ - // - p.w += f_alpha_dt; - } - - // Pass 2: Update pedestrian states after all forces have been computed. - // # (7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - double w_norm = p.w.norm(); - - double w_norm2 = w_norm > EPS ? w_norm : EPS; // Avoid division by zero. - - // # 1 DIV - double correction_factor = std::min(1.0, p.v_max / w_norm2); - - // # 2 MULTs - p.curr_vel = p.w * correction_factor; - - // # 2 ADDs, 2 MULTs - // - // $\vec{r}_i^{t+1} = \vec{r}_i^t + \vec{v}_i^t dt$ - // - p.curr_pos = p.curr_pos + p.curr_vel * dt; - - // When a pedestrian reaches the end of the corridor, teleport them back - // to the opposite entrance while keeping the same y-coordinate. This - // maintains a constant pedestrian flow and makes lane formation easier to - // observe. - if (p.color == WHITE && p.curr_pos.x > (BOARD_LENGTH - 1.5)) { - p.curr_pos.x = 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(p.desired_v0, 0.0); - p.curr_vel = p.w; - } - if (p.color == BLACK && p.curr_pos.x < 1.5) { - p.curr_pos.x = BOARD_LENGTH - 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(-p.desired_v0, 0.0); - p.curr_vel = p.w; - } - - // Keep pedestrians inside the board boundaries. - p.curr_pos.y = clamp(p.curr_pos.y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 2 ADDs - Vector2d e = p.destination - p.curr_pos; - - // # 1 ADD, 2 MULTs, 1 DIV, 1 SQRT - double e_norm = e.norm(); - double e_norm2 = e_norm > EPS ? e_norm : EPS; // Avoid division by zero. - double e_norm_inv = 1.0 / e_norm2; - - // # 2 MULTs - p.e = e * e_norm_inv; - - // # 1 ADD, 2 MULTs - double velocity_norm_sq = p.curr_vel.dot(p.curr_vel); - - // # 1 SQRT - double velocity_norm = std::sqrt(velocity_norm_sq); - - // # 1 MULT - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - // # 3 MULTs - p.s_b_e = p.e * (velocity_norm * delta_t); - -#if !BENCHMARK - // Store the current pedestrian state for visualization. - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } -#endif - } - } - -#if !BENCHMARK - // Export recorded simulation states to CSV. - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} \ No newline at end of file diff --git a/variants/variant_cutoff_flopcount.cpp b/variants/variant_cutoff_flopcount.cpp deleted file mode 100644 index bffd1a9..0000000 --- a/variants/variant_cutoff_flopcount.cpp +++ /dev/null @@ -1,434 +0,0 @@ -// This file is copied from the baseline and modified -// Our goal is to only calculate pedestrian-pedestrian forces for pedestrian which are near -// Our tolerance is 1cm and our cutoff is a 5m radius. -// Since we have some tolerance and we put pedestrians back at the end of the hallway once they are on one end. We only test/verify on a smaller subsample. -// Otherwise we had huge gaps (because of those jumps back) - -// # Changes: FLOPCOUNT -// # Predecessor: variant_cutoff.cpp - - -// n_iter * [ n_p ** 2 * (14 A + 23 M + 1 D + 4 S + 1 E) + n_p * (- 3 A - 3 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (12 A + 18 M + 1 D + 2 S + 1 E) ] - - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian.hpp" -#include "../headers/vector2d.hpp" - -#include -#include -#include -#include -#include // keep — forces code uses unqualified sqrt/exp -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// forces.cpp - -// Flop count: 2 ADDs, 5 MULTs, 1 SQRT -double w(Vector2d e, Vector2d f) { - if (e.dot(f) >= f.norm() * cos_phi) - return dt_neg; - else - return c_neg_times_dt; -} - -// Flop count: 2 ADDs, 4 MULTs -Vector2d acceleration_term_dt(const Pedestrian &alpha) { - - // # 2 MULTs - Vector2d desired_velocity = alpha.e * alpha.desired_v0; - - Vector2d actual_velocity = alpha.curr_vel; - - // # 2 ADDs, 2 MULTs - Vector2d f_alpha_0_dt = (desired_velocity - actual_velocity) * dt_times_tau_alpha_inv; - - return f_alpha_0_dt; -} - -// Flop count: 12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP -Vector2d pedestrian_repulsion_dt(const Pedestrian &alpha, const Pedestrian &beta) { - - // # 2 ADDs - // $\vec{r}_{\alpha\beta}$ - Vector2d r_ab = alpha.curr_pos - beta.curr_pos; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert$ - // double r_ab_norm = r_ab.norm(); - // Now we wanna check if it is a close pedestrian or wheter it is far away and can be ignored - double r_ab_sq = r_ab.x * r_ab.x + r_ab.y * r_ab.y; // squared distance - if (r_ab_sq > CUTOFF_SQ) - return Vector2d(0.0, 0.0); // cheap reject - double r_ab_norm = sqrt(r_ab_sq); // reuse r_ab_sq, no recompute - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; // Avoid division by zero - - // $v_\beta \Delta t \vec{e}_\beta$ - Vector2d s_b_e = beta.s_b_e; - - // # 2 ADDs - // $\vec{r}_{\alpha\beta} - \vec{v}_{\beta}\varDelta_{t}\vec{e}_{\beta}$ - Vector2d r_ab2 = r_ab - s_b_e; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab2_norm = r_ab2.norm(); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; // Avoid division by zero - - // # 1 ADD - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULTs, 1 SQRT - // - // $2b = \sqrt{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)^2 - (v_\beta \Delta t)^2}$ - // - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - beta.s_b_sq; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; // Avoid sqrt of negative and avoid division by zero - double two_b = sqrt(two_b_sq); - - // # 5 MULTs, 1 DIV, 1 EXP - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}$ - // - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 2 ADDs, 4 MULTs - // - // $\vec{-f}_{\alpha \beta}(\vec{r}_{\alpha \beta}) = \frac{-V^0_{\alpha \beta}}{2\sigma}e^{-b / \sigma} 2\grad_{\vec{r}_{\alpha \beta}}b $ - // - // - // $= \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - - // v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - // - // $2\grad_{\vec{r}_{\alpha \beta}}b = \frac{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)(\frac{\vec{r}_{\alpha \beta}}{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert} + \frac{\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta}{\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert})}{2b}$ - // - // - // $= \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - Vector2d f_alpha_beta_neg = r_ab * scalar_mult_r_ab + r_ab2 * scalar_mult_r_ab2; - - // # 2 ADDs, 7 MULTs, 1 SQRT - // - // $\vec{F}_{\alpha\beta}(\vec{e}_{\alpha}, \vec{r}_{\alpha \beta}).dt = w(\vec{e}_{\alpha}, -\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta})).\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta}).dt$ - // - Vector2d f_alpha_beta_dt = f_alpha_beta_neg * w(alpha.e, f_alpha_beta_neg); - - return f_alpha_beta_dt; -} - -// Flop count: 10 ADDs, 18 MULTs, 1 DIVs, 2 SQRTs, 1 EXP -Vector2d obstacle_repulsion_dt(const Pedestrian &alpha, const Obstacle &B) { - - // # 7 ADDs, 5 MULTs - // $\vec{r}_{\alpha B}$ - Vector2d r_aB = alpha.curr_pos - B.find_nearest_obstacle_point(alpha.curr_pos); - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha B} \Arrowvert$ - double r_aB_norm = r_aB.norm(); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; // Avoid division by zero - - // # 2 MULTs, 1 DIV, 1 EXP - // - // $\frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R}$ - // - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - - // # 2 MULTs - // - // $-\vec{F}_{\alpha B}(\vec{r}_{\alpha B}) = \frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R} \frac{\vec{r}_{\alpha B}}{\Arrowvert \vec{r}_{\alpha B} \Arrowvert}$ - // - Vector2d f_alpha_B_neg = r_aB * scalar_mult; - - // # 2 ADDs, 7 MULTs, 1 SQRT - // - // $\vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha B}).dt = w(\vec{e}_{\alpha}, -\vec{F}_{\alpha B}(\vec{r}_{\alpha B})).\vec{F}_{\alpha B}(\vec{r}_{\alpha B}).dt$ - // - Vector2d f_alpha_B_dt = f_alpha_B_neg * w(alpha.e, f_alpha_B_neg); - - return f_alpha_B_dt; -} - -// social_force_baseline.cpp - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; // Platform-provided random seed source. - mt19937 gen(rd()); // Non-reproducible initialization. -#else - mt19937 gen(SEED); // Reproducible initialization. -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - // Group of pedestrians walking from left to right. - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(BOARD_LENGTH + 30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(WHITE, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - // Group of pedestrians walking from right to left. - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(-30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(BLACK, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - // Avoid compiler unused parameter warning - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - // Each sample stores: time step, pedestrian id, x, y, e.x, e.y, color. - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - // Store the initial state before the first simulation step. - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } - } -#endif - - // n_iter * [{ n_p * { [(n_p - 1) * (12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP + 2 ADDs)] + [n_obstacles * (10 ADDs, 18 MULTs, 1 DIV, 2 SQRTs, 1 EXP + 2 ADDs) + 4 ADDs + 4 MULTs] } } + { (7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p }] - - - // n_iter * [{ n_p * { [(n_p - 1) * (12 A + 23 M + 1 D + 4 S + 1 E + 2 A)] + [n_obstacles * (10 A + 18 M + 1 D + 2 S + 1 E + 2 A) + 4 A + 4 M] } } + { n_p * (7 A + 16 M + 2 D + 3 S) }] - - // n_iter * [{ n_p * { n_p * (14 A + 23 M + 1 D + 4 S + 1 E) - 1 * (10 A + 19 M + 1 D + 4 S + 1 E) + n_obstacles * (12 A + 18 M + 1 D + 2 S + 1 E) }} + { n_p * (7 A + 16 M + 2 D + 3 S) }] - - - // n_iter * [ n_p ** 2 * (14 A + 23 M + 1 D + 4 S + 1 E) - n_p * (10 A + 19 M + 1 D + 4 S + 1 E) + n_obstacles * n_p * (12 A + 18 M + 1 D + 2 S + 1 E) + n_p * (7 A + 16 M + 2 D + 3 S) ] - - - // n_iter * [ n_p ** 2 * (14 A + 23 M + 1 D + 4 S + 1 E) + n_p * (- 3 A - 3 M + 1 D - 1 S - 1 E) + n_obstacles * n_p * (12 A + 18 M + 1 D + 2 S + 1 E) ] - -// NOTE: this flop count charges every ped-ped pair the full cost. The cutoff -// (L80-81) early-rejects pairs farther than 5m, so the real ped-ped work is only -// a fraction of this (roughly 40-50% on a 10x50 hallway, depending on the layout). -// We keep it at 100% here as the upper-bound / fixed-W baseline, the exact fraction -// can be folded into the n_p**2 term later if we want the actual executed flops. - - - for (int t = 0; t < n_iter; t++) { - // Pass 1: Compute all forces from the current state. - // n_p * { [(n_p - 1) * (12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP + 2 ADDs)] + [n_obstacles * (10 ADDs, 18 MULTs, 1 DIV, 2 SQRTs, 1 EXP + 2 ADDs) + 4 ADDs + 4 MULTs] } - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 2 ADDs, 4 MULTs - // - // $\vec{F}_\alpha(\vec{v}_\alpha, v^0_\alpha \vec{e}_\alpha) = \frac{1}{\tau_\alpha}(v^0_\alpha \vec{e}_\alpha - \vec{v}_\alpha)$ - // - Vector2d f_alpha_dt = acceleration_term_dt(p); - - // # (N-1) * (12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP) - // + (N-1) * 2 ADDs - // - // $\sum_{\beta} \vec{F}_{\alpha\beta}(\vec{e}_{\alpha},\vec{r}_{\alpha} - \vec{r}_{\beta})$ - // - for (int j = 0; j < n_p; j++) { - if (i == j) - continue; - - f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]); - } - - // # board.n_obstacles * (10 ADDs, 18 MULTs, 1 DIV, 2 SQRTs, 1 EXP) - // + board.n_obstacles * 2 ADDs - // - // $\sum_{B} \vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha} - \vec{r}_{B})$ - // - for (int k = 0; k < board.n_obstacles; k++) { - f_alpha_dt += obstacle_repulsion_dt(p, board.obstacles[k]); - } - - // # 2 ADDs - // - // - // $\frac{d\vec{w}_\alpha}{dt} = \vec{F}_{\alpha}(t)$ - // - p.w += f_alpha_dt; - } - - // Pass 2: Update pedestrian states after all forces have been computed. - // # (7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - double w_norm = p.w.norm(); - - double w_norm2 = w_norm > EPS ? w_norm : EPS; // Avoid division by zero. - - // # 1 DIV - double correction_factor = std::min(1.0, p.v_max / w_norm2); - - // # 2 MULTs - p.curr_vel = p.w * correction_factor; - - // # 2 ADDs, 2 MULTs - // - // $\vec{r}_i^{t+1} = \vec{r}_i^t + \vec{v}_i^t dt$ - // - p.curr_pos = p.curr_pos + p.curr_vel * dt; - - // When a pedestrian reaches the end of the corridor, teleport them back - // to the opposite entrance while keeping the same y-coordinate. This - // maintains a constant pedestrian flow and makes lane formation easier to - // observe. - if (p.color == WHITE && p.curr_pos.x > (BOARD_LENGTH - 1.5)) { - p.curr_pos.x = 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(p.desired_v0, 0.0); - p.curr_vel = p.w; - } - if (p.color == BLACK && p.curr_pos.x < 1.5) { - p.curr_pos.x = BOARD_LENGTH - 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(-p.desired_v0, 0.0); - p.curr_vel = p.w; - } - - // Keep pedestrians inside the board boundaries. - p.curr_pos.y = clamp(p.curr_pos.y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 2 ADDs - Vector2d e = p.destination - p.curr_pos; - - // # 1 ADD, 2 MULTs, 1 DIV, 1 SQRT - double e_norm = e.norm(); - double e_norm2 = e_norm > EPS ? e_norm : EPS; // Avoid division by zero. - double e_norm_inv = 1.0 / e_norm2; - - // # 2 MULTs - p.e = e * e_norm_inv; - - // # 1 ADD, 2 MULTs - double velocity_norm_sq = p.curr_vel.dot(p.curr_vel); - - // # 1 SQRT - double velocity_norm = std::sqrt(velocity_norm_sq); - - // # 1 MULT - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - // # 3 MULTs - p.s_b_e = p.e * (velocity_norm * delta_t); - -#if !BENCHMARK - // Store the current pedestrian state for visualization. - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } -#endif - } - } - -#if !BENCHMARK - // Export recorded simulation states to CSV. - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} \ No newline at end of file diff --git a/variants/variant_float_inlining_SoA_AVX2_flopcount.cpp b/variants/variant_float_inlining_SoA_AVX2_flopcount.cpp deleted file mode 100644 index bbc98d7..0000000 --- a/variants/variant_float_inlining_SoA_AVX2_flopcount.cpp +++ /dev/null @@ -1,449 +0,0 @@ -// # Changes: AVX2 vectorization of the ped-ped inner loop (8 floats per __m256, exp still scalar) -// # Predecessor: variant_float_inlining_SoA.cpp - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include // avx2 - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// same SoA arrays as the predecessor, but we pad to multiple of 8 so the avx loop is clean -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - const int n_p_pad = (n_p + 7) & ~7; // round up to a multiple of 8 for avx float lanes - - pos_x.resize(n_p_pad); - pos_y.resize(n_p_pad); - sb_sq.resize(n_p_pad); - sb_e_x.resize(n_p_pad); - sb_e_y.resize(n_p_pad); - e_x.resize(n_p_pad); - e_y.resize(n_p_pad); - curr_vel_x.resize(n_p_pad); - curr_vel_y.resize(n_p_pad); - w_x.resize(n_p_pad); - w_y.resize(n_p_pad); - desired_v0.resize(n_p_pad); - v_max.resize(n_p_pad); - destination_x.resize(n_p_pad); - destination_y.resize(n_p_pad); - color.resize(n_p_pad); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; // renamed to avoid shadowing destination_x[] - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - // padding lanes: park them far away, zero everything else. - // exp(very negative) is roughly 0 and the active mask zeroes their contribution anyway. - for (int j = n_p; j < n_p_pad; j++) { - pos_x[j] = 1e9f; - pos_y[j] = 1e9f; - sb_sq[j] = 0.0f; - sb_e_x[j] = 0.0f; - sb_e_y[j] = 0.0f; - } - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - const int n_p_pad = (n_p + 7) & ~7; - - // {0,1,2,3,4,5,6,7} so we can build per-iter j indices as (j + lane_off) - alignas(32) const float lane_offset_arr[8] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; - const __m256 lane_off = _mm256_load_ps(lane_offset_arr); - - // constants broadcast to all 8 lanes once - const __m256 v_neg_v0_div_2s = _mm256_set1_ps(NEG_V0_AB_div_TWO_SIGMA); - const __m256 v_neg_2s_inv = _mm256_set1_ps(NEG_TWO_SIGMA_INV); - const __m256 v_cos_phi_sq = _mm256_set1_ps(cos_phi_sq); - const __m256 v_dt_neg = _mm256_set1_ps(dt_neg); - const __m256 v_c_neg_dt = _mm256_set1_ps(c_neg_times_dt); - const __m256 v_zero = _mm256_setzero_ps(); - const __m256 v_one = _mm256_set1_ps(1.0f); - const __m256 v_eps = _mm256_set1_ps(EPS); - const __m256 v_eps_sq = _mm256_set1_ps(EPS_SQ); - const __m256 v_np = _mm256_set1_ps((float)n_p); - - // # flops per iteration (one pass of the outer time loop): - // # ADDs: n_p * 22 + n_p * n_p_pad * 15 + n_p * board.n_obstacles * 10 + n_p * 7 - // # MULTs: n_p * 4 + n_p * n_p_pad * 24 + n_p * board.n_obstacles * 13 + n_p * 16 - // # DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 - // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 - // # EXPs: n_p * n_p_pad * 1 + n_p * board.n_obstacles - for (int t = 0; t < n_iter; t++) { - - for (int i = 0; i < n_p; i++) { - - float desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - - // i is fixed for the inner loops, broadcast its fields once - const __m256 pix = _mm256_set1_ps(pos_x[i]); - const __m256 piy = _mm256_set1_ps(pos_y[i]); - const __m256 eix = _mm256_set1_ps(e_x[i]); - const __m256 eiy = _mm256_set1_ps(e_y[i]); - const __m256 v_i = _mm256_set1_ps((float)i); - - __m256 acc_x = _mm256_setzero_ps(); - __m256 acc_y = _mm256_setzero_ps(); - - for (int j = 0; j < n_p_pad; j += 8) { - // 8 lanes at once, lane k maps to pedestrian j+k - __m256 j_idx = _mm256_add_ps(_mm256_set1_ps((float)j), lane_off); // # 8 ADDs - - // active = (j+k != i) AND (j+k < n_p), zero in lanes we wanna skip - __m256 m_neq_i = _mm256_cmp_ps(j_idx, v_i, _CMP_NEQ_OQ); - __m256 m_in_range = _mm256_cmp_ps(j_idx, v_np, _CMP_LT_OQ); - __m256 active = _mm256_and_ps(m_neq_i, m_in_range); - - // j-side loads, SoA is contiguous. loadu because std::vector only guarantees 16B align - __m256 pjx = _mm256_loadu_ps(&pos_x[j]); - __m256 pjy = _mm256_loadu_ps(&pos_y[j]); - __m256 sbe_jx = _mm256_loadu_ps(&sb_e_x[j]); - __m256 sbe_jy = _mm256_loadu_ps(&sb_e_y[j]); - __m256 sb_jsq = _mm256_loadu_ps(&sb_sq[j]); - - // r_ab = pos_i - pos_j ; |r_ab| with the EPS clamp the scalar version does - __m256 r_ab_x = _mm256_sub_ps(pix, pjx); // # 8 ADDs - __m256 r_ab_y = _mm256_sub_ps(piy, pjy); // # 8 ADDs - __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); // # 16 MULTs, 8 ADDs - __m256 r_ab_n = _mm256_sqrt_ps(r_ab_n2); // # 8 SQRTs - r_ab_n = _mm256_max_ps(r_ab_n, v_eps); - - // r_ab2 = r_ab - sb_e_j ; |r_ab2| also clamped - __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); // # 8 ADDs - __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); // # 8 ADDs - __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); // # 16 MULTs, 8 ADDs - __m256 r_ab2_n = _mm256_sqrt_ps(r_ab2_n2); // # 8 SQRTs - r_ab2_n = _mm256_max_ps(r_ab2_n, v_eps); - - // 2b = sqrt((|r_ab| + |r_ab2|)^2 - sb_sq_j) ; clamp the inside with EPS_SQ - __m256 r_sum = _mm256_add_ps(r_ab_n, r_ab2_n); // # 8 ADDs - __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); // # 8 MULTs, 8 ADDs - two_b_sq = _mm256_max_ps(two_b_sq, v_eps_sq); - __m256 two_b = _mm256_sqrt_ps(two_b_sq); // # 8 SQRTs - - // exp scalar fallback. manually unrolled, otherwise gcc keeps it as a loop with - // one expf call and serialises all 8 (the double avx2 with 4 iters does get unrolled, the 8 here doesn't). - __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); // # 8 MULTs - alignas(32) float tmp_arg[8], tmp_val[8]; - _mm256_store_ps(tmp_arg, e_arg); - tmp_val[0] = expf(tmp_arg[0]); - tmp_val[1] = expf(tmp_arg[1]); - tmp_val[2] = expf(tmp_arg[2]); - tmp_val[3] = expf(tmp_arg[3]); - tmp_val[4] = expf(tmp_arg[4]); - tmp_val[5] = expf(tmp_arg[5]); - tmp_val[6] = expf(tmp_arg[6]); - tmp_val[7] = expf(tmp_arg[7]); // # 8 EXPs (1 per lane, scalar) - __m256 e_val = _mm256_load_ps(tmp_val); - - // smc = (r_sum * NEG_V0_AB_div_TWO_SIGMA * e_val) / (two_b * r_ab_n * r_ab2_n) - // i==j and padding lanes collapse the denom, so we swap in 1.0 there to keep smc finite. - // the AND with active below zeroes those lanes anyway. - __m256 numer = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), e_val); // # 16 MULTs - __m256 denom = _mm256_mul_ps(_mm256_mul_ps(two_b, r_ab_n), r_ab2_n); // # 16 MULTs - __m256 denom_safe = _mm256_blendv_ps(v_one, denom, active); // bitwise, no flop - __m256 smc = _mm256_div_ps(numer, denom_safe); // # 8 DIVs - - __m256 sm_r_ab = _mm256_mul_ps(smc, r_ab2_n); // # 8 MULTs - __m256 sm_r_ab2 = _mm256_mul_ps(smc, r_ab_n); // # 8 MULTs - - // scalar: f_x = r_ab_x * sm_r_ab + r_ab2_x * sm_r_ab2 - __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); // # 16 MULTs, 8 ADDs - __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); // # 16 MULTs, 8 ADDs - - // float w_val: default dt_neg, switch to c_neg_times_dt when - //(e_dot_f < 0 AND e_dot_f_sq > f_norm_sq * cos_phi_sq). - // this is the DeMorgan of the scalar's if (e_dot_f >= 0 || e_dot_f_sq <= rhs) - __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); // # 16 MULTs, 8 ADDs - __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); // # 8 MULTs - __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); // # 16 MULTs, 8 ADDs - __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); // # 8 MULTs - - __m256 c1 = _mm256_cmp_ps(e_dot_f, v_zero, _CMP_LT_OQ); - __m256 c2 = _mm256_cmp_ps(e_dot_f_sq, rhs, _CMP_GT_OQ); - __m256 cond = _mm256_and_ps(c1, c2); - __m256 w_val = _mm256_blendv_ps(v_dt_neg, v_c_neg_dt, cond); - - // mask out i==j lane and padding before accumulating. - // AND with all-zeros = 0, AND with all-ones passes through, cheaper than blendv here. - __m256 f_x_safe = _mm256_and_ps(f_x, active); // bitwise, no flop - __m256 f_y_safe = _mm256_and_ps(f_y, active); // bitwise, no flop - acc_x = _mm256_fmadd_ps(f_x_safe, w_val, acc_x); // # 8 MULTs, 8 ADDs - acc_y = _mm256_fmadd_ps(f_y_safe, w_val, acc_y); // # 8 MULTs, 8 ADDs - } - - // horizontal sum of acc_x and acc_y at the same time - __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); // # 8 ADDs (x and y interleaved) - __m128 hsum_lo = _mm256_castps256_ps128(hsum); - __m128 hsum_hi = _mm256_extractf128_ps(hsum, 1); - __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); // # 4 ADDs - sum4 = _mm_hadd_ps(sum4, sum4); // # 4 ADDs (total hadd: 16 ADDs for x+y) - alignas(16) float sum_out[4]; - _mm_store_ps(sum_out, sum4); - f_alpha_dt_x += sum_out[0]; // # 1 ADD - f_alpha_dt_y += sum_out[1]; // # 1 ADD - - // obstacle loop kept scalar, board.n_obstacles is 0 in this setup anyway - for (int k = 0; k < board.n_obstacles; k++) { - - float v1_x = pos_x[i] - board.obstacles[k].start_x; // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; // # 1 ADD - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); // # 1 MULT, 2 ADDs - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); // # 1 MULT, 2 ADDs - - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV - float f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT - - float w_val; - - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - - f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD - } - - w_x[i] += f_alpha_dt_x; // # 1 ADD - w_y[i] += f_alpha_dt_y; // # 1 ADD - } - - // position update loop stays scalar, same as the double AVX2 variant does - for (int i = 0; i < n_p; i++) { - - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT - float w_norm2 = w_norm > EPS ? w_norm : EPS; - float correction_factor = min(1.0f, v_max[i] / w_norm2); // # 1 DIV - - curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - float ex = destination_x[i] - pos_x[i]; // # 1 ADD - float ey = destination_y[i] - pos_y[i]; // # 1 ADD - - float e_norm = sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT - float e_norm2 = e_norm > EPS ? e_norm : EPS; - float e_norm_inv = 1.0 / e_norm2; // # 1 DIV - - e_x[i] = ex * e_norm_inv; // # 1 MULT - e_y[i] = ey * e_norm_inv; // # 1 MULT - - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD - float velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - - sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_AVX2_polyexp.cpp b/variants/variant_float_inlining_SoA_AVX2_polyexp.cpp index bff9db4..f0a8056 100644 --- a/variants/variant_float_inlining_SoA_AVX2_polyexp.cpp +++ b/variants/variant_float_inlining_SoA_AVX2_polyexp.cpp @@ -170,46 +170,55 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } -// vectorized expf for a __m256 (8 lanes), no libm call. -// same trick as the double polyexp: exp(x) = 2^n * exp(r) where n = round(x / ln2), r = x - n*ln2. -// after range reduce r is in [-ln2/2, ln2/2] ~= [-0.347, 0.347]. -// degree-5 taylor is enough for float (worst-case abs error ~1e-5, way under 1cm verify tolerance). -// 2^n is free via the schraudolph bit trick on the ieee 754 exponent slot. -// float layout: sign(1) | exp(8, bias 127) | mantissa(23), so we shift the biased exponent by 23. +// simd expf, degree-12 taylor on the [-ln2/2, ln2/2] reduced range, +// schraudolph 2^n bit trick on the exponent slot. +// # 128 MULTs, 112 ADDs static inline __m256 exp256_ps(__m256 x) { - // n = round(x * log2(e)) const __m256 log2e = _mm256_set1_ps(1.4426950408889634f); - __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), - _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); + __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); // # 8 MULTs - // r = x - n * ln(2) const __m256 ln2 = _mm256_set1_ps(0.6931471805599453f); - __m256 r = _mm256_fnmadd_ps(nf, ln2, x); + __m256 r = _mm256_fnmadd_ps(nf, ln2, x); // # 8 MULTs, 8 ADDs - // poly(r) ~ exp(r), degree-5 horner from highest coeff downwards - const __m256 c5 = _mm256_set1_ps(1.0f / 120.0f); - const __m256 c4 = _mm256_set1_ps(1.0f / 24.0f); - const __m256 c3 = _mm256_set1_ps(1.0f / 6.0f); - const __m256 c2 = _mm256_set1_ps(0.5f); - const __m256 c1 = _mm256_set1_ps(1.0f); - const __m256 c0 = _mm256_set1_ps(1.0f); + const __m256 c13 = _mm256_set1_ps(c13_scalar); + const __m256 c12 = _mm256_set1_ps(c12_scalar); + const __m256 c11 = _mm256_set1_ps(c11_scalar); + const __m256 c10 = _mm256_set1_ps(c10_scalar); + const __m256 c9 = _mm256_set1_ps(c9_scalar); + const __m256 c8 = _mm256_set1_ps(c8_scalar); + const __m256 c7 = _mm256_set1_ps(c7_scalar); + const __m256 c6 = _mm256_set1_ps(c6_scalar); + const __m256 c5 = _mm256_set1_ps(c5_scalar); + const __m256 c4 = _mm256_set1_ps(c4_scalar); + const __m256 c3 = _mm256_set1_ps(c3_scalar); + const __m256 c2 = _mm256_set1_ps(c2_scalar); + const __m256 c1 = _mm256_set1_ps(c1_scalar); + const __m256 c0 = _mm256_set1_ps(c0_scalar); - __m256 p = c5; + __m256 p = c13; + + // # 13 * (8 MULTs, 8 ADDs) + // # = 104 MULTs, 104 ADDs + p = _mm256_fmadd_ps(p, r, c12); + p = _mm256_fmadd_ps(p, r, c11); + p = _mm256_fmadd_ps(p, r, c10); + p = _mm256_fmadd_ps(p, r, c9); + p = _mm256_fmadd_ps(p, r, c8); + p = _mm256_fmadd_ps(p, r, c7); + p = _mm256_fmadd_ps(p, r, c6); + p = _mm256_fmadd_ps(p, r, c5); p = _mm256_fmadd_ps(p, r, c4); p = _mm256_fmadd_ps(p, r, c3); p = _mm256_fmadd_ps(p, r, c2); p = _mm256_fmadd_ps(p, r, c1); p = _mm256_fmadd_ps(p, r, c0); - // 2^n via bit trick: float bias 127, exponent slot at bit 23. - // padding lanes (sb_e=0, pos=1e9) overflow this, but the active mask zeroes them out - // before accumulation so the garbage doesnt matter. - __m256i ni = _mm256_cvtps_epi32(nf); + __m256i ni = _mm256_cvtps_epi32(nf); __m256i biased = _mm256_add_epi32(ni, _mm256_set1_epi32(127)); __m256i scaled = _mm256_slli_epi32(biased, 23); - __m256 pow2n = _mm256_castsi256_ps(scaled); + __m256 pow2n = _mm256_castsi256_ps(scaled); - return _mm256_mul_ps(p, pow2n); + return _mm256_mul_ps(p, pow2n); // # 8 MULTs } int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { @@ -249,15 +258,21 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m256 v_np = _mm256_set1_ps((float)n_p); const __m256 v_exp_min = _mm256_set1_ps(-87.0f); // clamp for polyexp, below this exp(x) ~ 0 anyway + // # flops per iteration (one pass of the outer time loop): + // # ADDs: n_p * 22 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 10 + n_p * 7 + // # MULTs: n_p * 4 + n_p * n_p_pad * 40 + n_p * board.n_obstacles * 13 + n_p * 16 + // # DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 + // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 + // # EXPs: n_p * board.n_obstacles for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { - float desired_velocity_x = e_x[i] * desired_v0[i]; - float desired_velocity_y = e_y[i] * desired_v0[i]; + float desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT + float desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; + float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD + float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD const __m256 pix = _mm256_set1_ps(pos_x[i]); const __m256 piy = _mm256_set1_ps(pos_y[i]); @@ -269,7 +284,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m256 acc_y = _mm256_setzero_ps(); for (int j = 0; j < n_p_pad; j += 8) { - __m256 j_idx = _mm256_add_ps(_mm256_set1_ps((float)j), lane_off); + __m256 j_idx = _mm256_add_ps(_mm256_set1_ps((float)j), lane_off); // # 8 ADDs __m256 m_neq_i = _mm256_cmp_ps(j_idx, v_i, _CMP_NEQ_OQ); __m256 m_in_range = _mm256_cmp_ps(j_idx, v_np, _CMP_LT_OQ); @@ -281,90 +296,90 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m256 sbe_jy = _mm256_loadu_ps(&sb_e_y[j]); __m256 sb_jsq = _mm256_loadu_ps(&sb_sq[j]); - __m256 r_ab_x = _mm256_sub_ps(pix, pjx); - __m256 r_ab_y = _mm256_sub_ps(piy, pjy); - __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); - __m256 r_ab_n = _mm256_sqrt_ps(r_ab_n2); + __m256 r_ab_x = _mm256_sub_ps(pix, pjx); // # 8 ADDs + __m256 r_ab_y = _mm256_sub_ps(piy, pjy); // # 8 ADDs + __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); // # 16 MULTs, 8 ADDs + __m256 r_ab_n = _mm256_sqrt_ps(r_ab_n2); // # 8 SQRTs r_ab_n = _mm256_max_ps(r_ab_n, v_eps); - __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); - __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); - __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); - __m256 r_ab2_n = _mm256_sqrt_ps(r_ab2_n2); + __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); // # 8 ADDs + __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); // # 8 ADDs + __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); // # 16 MULTs, 8 ADDs + __m256 r_ab2_n = _mm256_sqrt_ps(r_ab2_n2); // # 8 SQRTs r_ab2_n = _mm256_max_ps(r_ab2_n, v_eps); - __m256 r_sum = _mm256_add_ps(r_ab_n, r_ab2_n); - __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); + __m256 r_sum = _mm256_add_ps(r_ab_n, r_ab2_n); // # 8 ADDs + __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); // # 8 MULTs, 8 ADDs two_b_sq = _mm256_max_ps(two_b_sq, v_eps_sq); - __m256 two_b = _mm256_sqrt_ps(two_b_sq); + __m256 two_b = _mm256_sqrt_ps(two_b_sq); // # 8 SQRTs // simd polynomial expf, no libm call. replaces the 8-iter scalar loop that gcc refused to unroll. // clamp e_arg >= -87 first: exp(-87) ~ 1.6e-38 is already basically 0, and the schraudolph 2^n // bit trick blows up if (nf + 127) goes negative (which happens for padding lanes where e_arg ~ -4.7e9). - __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); + __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); // # 8 MULTs e_arg = _mm256_max_ps(e_arg, v_exp_min); - __m256 e_val = exp256_ps(e_arg); + __m256 e_val = exp256_ps(e_arg); // # 128 MULTs, 112 ADDs - __m256 numer = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), e_val); - __m256 denom = _mm256_mul_ps(_mm256_mul_ps(two_b, r_ab_n), r_ab2_n); - __m256 denom_safe = _mm256_blendv_ps(v_one, denom, active); - __m256 smc = _mm256_div_ps(numer, denom_safe); + __m256 numer = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), e_val); // # 16 MULTs + __m256 denom = _mm256_mul_ps(_mm256_mul_ps(two_b, r_ab_n), r_ab2_n); // # 16 MULTs + __m256 denom_safe = _mm256_blendv_ps(v_one, denom, active); // bitwise, no flop + __m256 smc = _mm256_div_ps(numer, denom_safe); // # 8 DIVs - __m256 sm_r_ab = _mm256_mul_ps(smc, r_ab2_n); - __m256 sm_r_ab2 = _mm256_mul_ps(smc, r_ab_n); + __m256 sm_r_ab = _mm256_mul_ps(smc, r_ab2_n); // # 8 MULTs + __m256 sm_r_ab2 = _mm256_mul_ps(smc, r_ab_n); // # 8 MULTs - __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); - __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); + __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); // # 16 MULTs, 8 ADDs + __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); // # 16 MULTs, 8 ADDs - __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); - __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); - __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); - __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); + __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); // # 16 MULTs, 8 ADDs + __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); // # 8 MULTs + __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); // # 16 MULTs, 8 ADDs + __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); // # 8 MULTs __m256 c1 = _mm256_cmp_ps(e_dot_f, v_zero, _CMP_LT_OQ); __m256 c2 = _mm256_cmp_ps(e_dot_f_sq, rhs, _CMP_GT_OQ); __m256 cond = _mm256_and_ps(c1, c2); __m256 w_val = _mm256_blendv_ps(v_dt_neg, v_c_neg_dt, cond); - __m256 f_x_safe = _mm256_and_ps(f_x, active); - __m256 f_y_safe = _mm256_and_ps(f_y, active); - acc_x = _mm256_fmadd_ps(f_x_safe, w_val, acc_x); - acc_y = _mm256_fmadd_ps(f_y_safe, w_val, acc_y); + __m256 f_x_safe = _mm256_and_ps(f_x, active); // bitwise, no flop + __m256 f_y_safe = _mm256_and_ps(f_y, active); // bitwise, no flop + acc_x = _mm256_fmadd_ps(f_x_safe, w_val, acc_x); // # 8 MULTs, 8 ADDs + acc_y = _mm256_fmadd_ps(f_y_safe, w_val, acc_y); // # 8 MULTs, 8 ADDs } - __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); + __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); // # 8 ADDs __m128 hsum_lo = _mm256_castps256_ps128(hsum); __m128 hsum_hi = _mm256_extractf128_ps(hsum, 1); - __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); - sum4 = _mm_hadd_ps(sum4, sum4); + __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); // # 4 ADDs + sum4 = _mm_hadd_ps(sum4, sum4); // # 4 ADDs alignas(16) float sum_out[4]; _mm_store_ps(sum_out, sum4); - f_alpha_dt_x += sum_out[0]; - f_alpha_dt_y += sum_out[1]; + f_alpha_dt_x += sum_out[0]; // # 1 ADD + f_alpha_dt_y += sum_out[1]; // # 1 ADD for (int k = 0; k < board.n_obstacles; k++) { - float v1_x = pos_x[i] - board.obstacles[k].start_x; - float v1_y = pos_y[i] - board.obstacles[k].start_y; + float v1_x = pos_x[i] - board.obstacles[k].start_x; // # 1 ADD + float v1_y = pos_y[i] - board.obstacles[k].start_y; // # 1 ADD float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; + float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD if (projection < 0.0) projection = 0.0; else if (projection > 1.0) projection = 1.0; - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); // # 1 MULT, 2 ADDs + float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); // # 1 MULT, 2 ADDs - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); + float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - float f_alpha_B_neg_y = r_aB_y * scalar_mult; + float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV + float f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT + float f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT float w_val; @@ -375,28 +390,28 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else w_val = c_neg_times_dt; - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT + float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - f_alpha_dt_x += f_alpha_B_dt_x; - f_alpha_dt_y += f_alpha_B_dt_y; + f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD + f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD } - w_x[i] += f_alpha_dt_x; - w_y[i] += f_alpha_dt_y; + w_x[i] += f_alpha_dt_x; // # 1 ADD + w_y[i] += f_alpha_dt_y; // # 1 ADD } for (int i = 0; i < n_p; i++) { - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); + float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT float w_norm2 = w_norm > EPS ? w_norm : EPS; - float correction_factor = min(1.0f, v_max[i] / w_norm2); + float correction_factor = min(1.0f, v_max[i] / w_norm2); // # 1 DIV - curr_vel_x[i] = w_x[i] * correction_factor; - curr_vel_y[i] = w_y[i] * correction_factor; + curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT + curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; + pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD + pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { pos_x[i] = 1.0; @@ -417,22 +432,22 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - float ex = destination_x[i] - pos_x[i]; - float ey = destination_y[i] - pos_y[i]; + float ex = destination_x[i] - pos_x[i]; // # 1 ADD + float ey = destination_y[i] - pos_y[i]; // # 1 ADD - float e_norm = sqrt(ex * ex + ey * ey); + float e_norm = sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT float e_norm2 = e_norm > EPS ? e_norm : EPS; - float e_norm_inv = 1.0 / e_norm2; + float e_norm_inv = 1.0 / e_norm2; // # 1 DIV - e_x[i] = ex * e_norm_inv; - e_y[i] = ey * e_norm_inv; + e_x[i] = ex * e_norm_inv; // # 1 MULT + e_y[i] = ey * e_norm_inv; // # 1 MULT - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; - float velocity_norm = sqrt(velocity_norm_sq); + float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD + float velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - sb_sq[i] = velocity_norm_sq * delta_t_sq; - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); + sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT + sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) + sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT #if !BENCHMARK if (dump_path && (t % dump_interval == 0)) { diff --git a/variants/variant_float_inlining_SoA_AVX2_polyexp_expfix.cpp b/variants/variant_float_inlining_SoA_AVX2_polyexp_expfix.cpp deleted file mode 100644 index 572a2bc..0000000 --- a/variants/variant_float_inlining_SoA_AVX2_polyexp_expfix.cpp +++ /dev/null @@ -1,472 +0,0 @@ -// # Changes: simd polynomial expf, replaces the 8 scalar expf calls in the j loop -// # Predecessor: variant_float_inlining_SoA_AVX2.cpp - -// the for-loop of 8 scalar expf calls was serialised by gcc (it didnt unroll), big perf hit. -// replace with one inlined simd polynomial approx, no libm call at all. - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include // avx2 - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - const int n_p_pad = (n_p + 7) & ~7; - - pos_x.resize(n_p_pad); - pos_y.resize(n_p_pad); - sb_sq.resize(n_p_pad); - sb_e_x.resize(n_p_pad); - sb_e_y.resize(n_p_pad); - e_x.resize(n_p_pad); - e_y.resize(n_p_pad); - curr_vel_x.resize(n_p_pad); - curr_vel_y.resize(n_p_pad); - w_x.resize(n_p_pad); - w_y.resize(n_p_pad); - desired_v0.resize(n_p_pad); - v_max.resize(n_p_pad); - destination_x.resize(n_p_pad); - destination_y.resize(n_p_pad); - color.resize(n_p_pad); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int j = n_p; j < n_p_pad; j++) { - pos_x[j] = 1e9f; - pos_y[j] = 1e9f; - sb_sq[j] = 0.0f; - sb_e_x[j] = 0.0f; - sb_e_y[j] = 0.0f; - } - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -// simd expf, degree-12 taylor on the [-ln2/2, ln2/2] reduced range, -// schraudolph 2^n bit trick on the exponent slot. -// # 128 MULTs, 112 ADDs -static inline __m256 exp256_ps(__m256 x) { - const __m256 log2e = _mm256_set1_ps(1.4426950408889634f); - __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); // # 8 MULTs - - const __m256 ln2 = _mm256_set1_ps(0.6931471805599453f); - __m256 r = _mm256_fnmadd_ps(nf, ln2, x); // # 8 MULTs, 8 ADDs - - const __m256 c13 = _mm256_set1_ps(c13_scalar); - const __m256 c12 = _mm256_set1_ps(c12_scalar); - const __m256 c11 = _mm256_set1_ps(c11_scalar); - const __m256 c10 = _mm256_set1_ps(c10_scalar); - const __m256 c9 = _mm256_set1_ps(c9_scalar); - const __m256 c8 = _mm256_set1_ps(c8_scalar); - const __m256 c7 = _mm256_set1_ps(c7_scalar); - const __m256 c6 = _mm256_set1_ps(c6_scalar); - const __m256 c5 = _mm256_set1_ps(c5_scalar); - const __m256 c4 = _mm256_set1_ps(c4_scalar); - const __m256 c3 = _mm256_set1_ps(c3_scalar); - const __m256 c2 = _mm256_set1_ps(c2_scalar); - const __m256 c1 = _mm256_set1_ps(c1_scalar); - const __m256 c0 = _mm256_set1_ps(c0_scalar); - - __m256 p = c13; - - // # 13 * (8 MULTs, 8 ADDs) - // # = 104 MULTs, 104 ADDs - p = _mm256_fmadd_ps(p, r, c12); - p = _mm256_fmadd_ps(p, r, c11); - p = _mm256_fmadd_ps(p, r, c10); - p = _mm256_fmadd_ps(p, r, c9); - p = _mm256_fmadd_ps(p, r, c8); - p = _mm256_fmadd_ps(p, r, c7); - p = _mm256_fmadd_ps(p, r, c6); - p = _mm256_fmadd_ps(p, r, c5); - p = _mm256_fmadd_ps(p, r, c4); - p = _mm256_fmadd_ps(p, r, c3); - p = _mm256_fmadd_ps(p, r, c2); - p = _mm256_fmadd_ps(p, r, c1); - p = _mm256_fmadd_ps(p, r, c0); - - __m256i ni = _mm256_cvtps_epi32(nf); - __m256i biased = _mm256_add_epi32(ni, _mm256_set1_epi32(127)); - __m256i scaled = _mm256_slli_epi32(biased, 23); - __m256 pow2n = _mm256_castsi256_ps(scaled); - - return _mm256_mul_ps(p, pow2n); // # 8 MULTs -} - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - const int n_p_pad = (n_p + 7) & ~7; - - alignas(32) const float lane_offset_arr[8] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; - const __m256 lane_off = _mm256_load_ps(lane_offset_arr); - - const __m256 v_neg_v0_div_2s = _mm256_set1_ps(NEG_V0_AB_div_TWO_SIGMA); - const __m256 v_neg_2s_inv = _mm256_set1_ps(NEG_TWO_SIGMA_INV); - const __m256 v_cos_phi_sq = _mm256_set1_ps(cos_phi_sq); - const __m256 v_dt_neg = _mm256_set1_ps(dt_neg); - const __m256 v_c_neg_dt = _mm256_set1_ps(c_neg_times_dt); - const __m256 v_zero = _mm256_setzero_ps(); - const __m256 v_one = _mm256_set1_ps(1.0f); - const __m256 v_eps = _mm256_set1_ps(EPS); - const __m256 v_eps_sq = _mm256_set1_ps(EPS_SQ); - const __m256 v_np = _mm256_set1_ps((float)n_p); - const __m256 v_exp_min = _mm256_set1_ps(-87.0f); // clamp for polyexp, below this exp(x) ~ 0 anyway - - // # flops per iteration (one pass of the outer time loop): - // # ADDs: n_p * 22 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 10 + n_p * 7 - // # MULTs: n_p * 4 + n_p * n_p_pad * 40 + n_p * board.n_obstacles * 13 + n_p * 16 - // # DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 - // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 - // # EXPs: n_p * board.n_obstacles - for (int t = 0; t < n_iter; t++) { - - for (int i = 0; i < n_p; i++) { - - float desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - - const __m256 pix = _mm256_set1_ps(pos_x[i]); - const __m256 piy = _mm256_set1_ps(pos_y[i]); - const __m256 eix = _mm256_set1_ps(e_x[i]); - const __m256 eiy = _mm256_set1_ps(e_y[i]); - const __m256 v_i = _mm256_set1_ps((float)i); - - __m256 acc_x = _mm256_setzero_ps(); - __m256 acc_y = _mm256_setzero_ps(); - - for (int j = 0; j < n_p_pad; j += 8) { - __m256 j_idx = _mm256_add_ps(_mm256_set1_ps((float)j), lane_off); // # 8 ADDs - - __m256 m_neq_i = _mm256_cmp_ps(j_idx, v_i, _CMP_NEQ_OQ); - __m256 m_in_range = _mm256_cmp_ps(j_idx, v_np, _CMP_LT_OQ); - __m256 active = _mm256_and_ps(m_neq_i, m_in_range); - - __m256 pjx = _mm256_loadu_ps(&pos_x[j]); - __m256 pjy = _mm256_loadu_ps(&pos_y[j]); - __m256 sbe_jx = _mm256_loadu_ps(&sb_e_x[j]); - __m256 sbe_jy = _mm256_loadu_ps(&sb_e_y[j]); - __m256 sb_jsq = _mm256_loadu_ps(&sb_sq[j]); - - __m256 r_ab_x = _mm256_sub_ps(pix, pjx); // # 8 ADDs - __m256 r_ab_y = _mm256_sub_ps(piy, pjy); // # 8 ADDs - __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); // # 16 MULTs, 8 ADDs - __m256 r_ab_n = _mm256_sqrt_ps(r_ab_n2); // # 8 SQRTs - r_ab_n = _mm256_max_ps(r_ab_n, v_eps); - - __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); // # 8 ADDs - __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); // # 8 ADDs - __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); // # 16 MULTs, 8 ADDs - __m256 r_ab2_n = _mm256_sqrt_ps(r_ab2_n2); // # 8 SQRTs - r_ab2_n = _mm256_max_ps(r_ab2_n, v_eps); - - __m256 r_sum = _mm256_add_ps(r_ab_n, r_ab2_n); // # 8 ADDs - __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); // # 8 MULTs, 8 ADDs - two_b_sq = _mm256_max_ps(two_b_sq, v_eps_sq); - __m256 two_b = _mm256_sqrt_ps(two_b_sq); // # 8 SQRTs - - // simd polynomial expf, no libm call. replaces the 8-iter scalar loop that gcc refused to unroll. - // clamp e_arg >= -87 first: exp(-87) ~ 1.6e-38 is already basically 0, and the schraudolph 2^n - // bit trick blows up if (nf + 127) goes negative (which happens for padding lanes where e_arg ~ -4.7e9). - __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); // # 8 MULTs - e_arg = _mm256_max_ps(e_arg, v_exp_min); - __m256 e_val = exp256_ps(e_arg); // # 128 MULTs, 112 ADDs - - __m256 numer = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), e_val); // # 16 MULTs - __m256 denom = _mm256_mul_ps(_mm256_mul_ps(two_b, r_ab_n), r_ab2_n); // # 16 MULTs - __m256 denom_safe = _mm256_blendv_ps(v_one, denom, active); // bitwise, no flop - __m256 smc = _mm256_div_ps(numer, denom_safe); // # 8 DIVs - - __m256 sm_r_ab = _mm256_mul_ps(smc, r_ab2_n); // # 8 MULTs - __m256 sm_r_ab2 = _mm256_mul_ps(smc, r_ab_n); // # 8 MULTs - - __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); // # 16 MULTs, 8 ADDs - __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); // # 16 MULTs, 8 ADDs - - __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); // # 16 MULTs, 8 ADDs - __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); // # 8 MULTs - __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); // # 16 MULTs, 8 ADDs - __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); // # 8 MULTs - - __m256 c1 = _mm256_cmp_ps(e_dot_f, v_zero, _CMP_LT_OQ); - __m256 c2 = _mm256_cmp_ps(e_dot_f_sq, rhs, _CMP_GT_OQ); - __m256 cond = _mm256_and_ps(c1, c2); - __m256 w_val = _mm256_blendv_ps(v_dt_neg, v_c_neg_dt, cond); - - __m256 f_x_safe = _mm256_and_ps(f_x, active); // bitwise, no flop - __m256 f_y_safe = _mm256_and_ps(f_y, active); // bitwise, no flop - acc_x = _mm256_fmadd_ps(f_x_safe, w_val, acc_x); // # 8 MULTs, 8 ADDs - acc_y = _mm256_fmadd_ps(f_y_safe, w_val, acc_y); // # 8 MULTs, 8 ADDs - } - - __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); // # 8 ADDs - __m128 hsum_lo = _mm256_castps256_ps128(hsum); - __m128 hsum_hi = _mm256_extractf128_ps(hsum, 1); - __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); // # 4 ADDs - sum4 = _mm_hadd_ps(sum4, sum4); // # 4 ADDs - alignas(16) float sum_out[4]; - _mm_store_ps(sum_out, sum4); - f_alpha_dt_x += sum_out[0]; // # 1 ADD - f_alpha_dt_y += sum_out[1]; // # 1 ADD - - for (int k = 0; k < board.n_obstacles; k++) { - - float v1_x = pos_x[i] - board.obstacles[k].start_x; // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; // # 1 ADD - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); // # 1 MULT, 2 ADDs - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); // # 1 MULT, 2 ADDs - - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV - float f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT - - float w_val; - - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - - f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD - } - - w_x[i] += f_alpha_dt_x; // # 1 ADD - w_y[i] += f_alpha_dt_y; // # 1 ADD - } - - for (int i = 0; i < n_p; i++) { - - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT - float w_norm2 = w_norm > EPS ? w_norm : EPS; - float correction_factor = min(1.0f, v_max[i] / w_norm2); // # 1 DIV - - curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - float ex = destination_x[i] - pos_x[i]; // # 1 ADD - float ey = destination_y[i] - pos_y[i]; // # 1 ADD - - float e_norm = sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT - float e_norm2 = e_norm > EPS ? e_norm : EPS; - float e_norm_inv = 1.0 / e_norm2; // # 1 DIV - - e_x[i] = ex * e_norm_inv; // # 1 MULT - e_y[i] = ey * e_norm_inv; // # 1 MULT - - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD - float velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - - sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.cpp b/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.cpp index b75d74b..c42d59d 100644 --- a/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.cpp +++ b/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt.cpp @@ -187,23 +187,43 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } -// simd expf, same as the predecessor. degree-5 taylor on the [-ln2/2, ln2/2] reduced range, +// simd expf, degree-12 taylor on the [-ln2/2, ln2/2] reduced range, // schraudolph 2^n bit trick on the exponent slot. +// # 128 MULTs, 112 ADDs static inline __m256 exp256_ps(__m256 x) { const __m256 log2e = _mm256_set1_ps(1.4426950408889634f); - __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); + __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); // # 8 MULTs const __m256 ln2 = _mm256_set1_ps(0.6931471805599453f); - __m256 r = _mm256_fnmadd_ps(nf, ln2, x); + __m256 r = _mm256_fnmadd_ps(nf, ln2, x); // # 8 MULTs, 8 ADDs - const __m256 c5 = _mm256_set1_ps(1.0f / 120.0f); - const __m256 c4 = _mm256_set1_ps(1.0f / 24.0f); - const __m256 c3 = _mm256_set1_ps(1.0f / 6.0f); - const __m256 c2 = _mm256_set1_ps(0.5f); - const __m256 c1 = _mm256_set1_ps(1.0f); - const __m256 c0 = _mm256_set1_ps(1.0f); + const __m256 c13 = _mm256_set1_ps(c13_scalar); + const __m256 c12 = _mm256_set1_ps(c12_scalar); + const __m256 c11 = _mm256_set1_ps(c11_scalar); + const __m256 c10 = _mm256_set1_ps(c10_scalar); + const __m256 c9 = _mm256_set1_ps(c9_scalar); + const __m256 c8 = _mm256_set1_ps(c8_scalar); + const __m256 c7 = _mm256_set1_ps(c7_scalar); + const __m256 c6 = _mm256_set1_ps(c6_scalar); + const __m256 c5 = _mm256_set1_ps(c5_scalar); + const __m256 c4 = _mm256_set1_ps(c4_scalar); + const __m256 c3 = _mm256_set1_ps(c3_scalar); + const __m256 c2 = _mm256_set1_ps(c2_scalar); + const __m256 c1 = _mm256_set1_ps(c1_scalar); + const __m256 c0 = _mm256_set1_ps(c0_scalar); - __m256 p = c5; + __m256 p = c13; + + // # 13 * (8 MULTs, 8 ADDs) + // # = 104 MULTs, 104 ADDs + p = _mm256_fmadd_ps(p, r, c12); + p = _mm256_fmadd_ps(p, r, c11); + p = _mm256_fmadd_ps(p, r, c10); + p = _mm256_fmadd_ps(p, r, c9); + p = _mm256_fmadd_ps(p, r, c8); + p = _mm256_fmadd_ps(p, r, c7); + p = _mm256_fmadd_ps(p, r, c6); + p = _mm256_fmadd_ps(p, r, c5); p = _mm256_fmadd_ps(p, r, c4); p = _mm256_fmadd_ps(p, r, c3); p = _mm256_fmadd_ps(p, r, c2); @@ -215,7 +235,7 @@ static inline __m256 exp256_ps(__m256 x) { __m256i scaled = _mm256_slli_epi32(biased, 23); __m256 pow2n = _mm256_castsi256_ps(scaled); - return _mm256_mul_ps(p, pow2n); + return _mm256_mul_ps(p, pow2n); // # 8 MULTs } int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { @@ -253,15 +273,21 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m256 v_np = _mm256_set1_ps((float)n_p); const __m256 v_exp_min = _mm256_set1_ps(-87.0f); + // # flops per iteration (one pass of the outer time loop): + // # ADDs: n_p * 22 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 10 + n_p * 7 + // # MULTs: n_p * 4 + n_p * n_p_pad * 42 + n_p * board.n_obstacles * 13 + n_p * 16 + // # DIVs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 2 + // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 + // # EXPs: n_p * board.n_obstacles for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { - float desired_velocity_x = e_x[i] * desired_v0[i]; - float desired_velocity_y = e_y[i] * desired_v0[i]; + float desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT + float desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; + float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD + float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD const __m256 pix = _mm256_set1_ps(pos_x[i]); const __m256 piy = _mm256_set1_ps(pos_y[i]); @@ -273,7 +299,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m256 acc_y = _mm256_setzero_ps(); for (int j = 0; j < n_p_pad; j += 8) { - __m256 j_idx = _mm256_add_ps(_mm256_set1_ps((float)j), lane_off); + __m256 j_idx = _mm256_add_ps(_mm256_set1_ps((float)j), lane_off); // # 8 ADDs __m256 m_neq_i = _mm256_cmp_ps(j_idx, v_i, _CMP_NEQ_OQ); __m256 m_in_range = _mm256_cmp_ps(j_idx, v_np, _CMP_LT_OQ); @@ -285,93 +311,93 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m256 sbe_jy = _mm256_loadu_ps(&sb_e_y[j]); __m256 sb_jsq = _mm256_loadu_ps(&sb_sq[j]); - __m256 r_ab_x = _mm256_sub_ps(pix, pjx); - __m256 r_ab_y = _mm256_sub_ps(piy, pjy); - __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); + __m256 r_ab_x = _mm256_sub_ps(pix, pjx); // # 8 ADDs + __m256 r_ab_y = _mm256_sub_ps(piy, pjy); // # 8 ADDs + __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); // # 16 MULTs, 8 ADDs // clamp before rsqrt: rsqrt(0) = inf, then x*inf = 0*inf = NaN downstream. EPS_SQ // means the inv_sqrt tops out at 1/EPS and r_ab_n = EPS_SQ/EPS = EPS, matches the predecessor. r_ab_n2 = _mm256_max_ps(r_ab_n2, v_eps_sq); - __m256 inv_r_ab_n = _mm256_rsqrt_ps(r_ab_n2); - __m256 r_ab_n = _mm256_mul_ps(r_ab_n2, inv_r_ab_n); + __m256 inv_r_ab_n = _mm256_rsqrt_ps(r_ab_n2); // # 8 SQRTs, 8 DIVs + __m256 r_ab_n = _mm256_mul_ps(r_ab_n2, inv_r_ab_n); // # 8 MULTs - __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); - __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); - __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); + __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); // # 8 ADDs + __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); // # 8 ADDs + __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); // # 16 MULTs, 8 ADDs r_ab2_n2 = _mm256_max_ps(r_ab2_n2, v_eps_sq); - __m256 inv_r_ab2_n = _mm256_rsqrt_ps(r_ab2_n2); - __m256 r_ab2_n = _mm256_mul_ps(r_ab2_n2, inv_r_ab2_n); + __m256 inv_r_ab2_n = _mm256_rsqrt_ps(r_ab2_n2); // # 8 SQRTs, 8 DIVs + __m256 r_ab2_n = _mm256_mul_ps(r_ab2_n2, inv_r_ab2_n); // # 8 MULTs - __m256 r_sum = _mm256_add_ps(r_ab_n, r_ab2_n); - __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); + __m256 r_sum = _mm256_add_ps(r_ab_n, r_ab2_n); // # 8 ADDs + __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); // # 8 MULTs, 8 ADDs two_b_sq = _mm256_max_ps(two_b_sq, v_eps_sq); - __m256 inv_two_b = _mm256_rsqrt_ps(two_b_sq); - __m256 two_b = _mm256_mul_ps(two_b_sq, inv_two_b); + __m256 inv_two_b = _mm256_rsqrt_ps(two_b_sq); // # 8 SQRTs, 8 DIVs + __m256 two_b = _mm256_mul_ps(two_b_sq, inv_two_b); // # 8 MULTs - __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); + __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); // # 8 MULTs e_arg = _mm256_max_ps(e_arg, v_exp_min); - __m256 e_val = exp256_ps(e_arg); + __m256 e_val = exp256_ps(e_arg); // # 128 MULTs, 112 ADDs // smc = numer/denom in the predecessor. with inv-sqrts in hand the divide is free: // sm_r_ab = smc * r_ab2_n = numer / (two_b * r_ab_n) = numer * inv_two_b * inv_r_ab_n // sm_r_ab2 = smc * r_ab_n = numer / (two_b * r_ab2_n) = numer * inv_two_b * inv_r_ab2_n // for the i==j and padding lanes inv_two_b*inv_r_ab_n is huge, but the active-mask AND on // f_x/f_y below zeros the lane out before accumulation so it cant pollute anything. - __m256 numer_inv_two_b = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), _mm256_mul_ps(e_val, inv_two_b)); - __m256 sm_r_ab = _mm256_mul_ps(numer_inv_two_b, inv_r_ab_n); - __m256 sm_r_ab2 = _mm256_mul_ps(numer_inv_two_b, inv_r_ab2_n); + __m256 numer_inv_two_b = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), _mm256_mul_ps(e_val, inv_two_b)); // # 24 MULTs + __m256 sm_r_ab = _mm256_mul_ps(numer_inv_two_b, inv_r_ab_n); // # 8 MULTs + __m256 sm_r_ab2 = _mm256_mul_ps(numer_inv_two_b, inv_r_ab2_n); // # 8 MULTs - __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); - __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); + __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); // # 16 MULTs, 8 ADDs + __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); // # 16 MULTs, 8 ADDs - __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); - __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); - __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); - __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); + __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); // # 16 MULTs, 8 ADDs + __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); // # 8 MULTs + __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); // # 16 MULTs, 8 ADDs + __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); // # 8 MULTs __m256 c1 = _mm256_cmp_ps(e_dot_f, v_zero, _CMP_LT_OQ); __m256 c2 = _mm256_cmp_ps(e_dot_f_sq, rhs, _CMP_GT_OQ); __m256 cond = _mm256_and_ps(c1, c2); __m256 w_val = _mm256_blendv_ps(v_dt_neg, v_c_neg_dt, cond); - __m256 f_x_safe = _mm256_and_ps(f_x, active); - __m256 f_y_safe = _mm256_and_ps(f_y, active); - acc_x = _mm256_fmadd_ps(f_x_safe, w_val, acc_x); - acc_y = _mm256_fmadd_ps(f_y_safe, w_val, acc_y); + __m256 f_x_safe = _mm256_and_ps(f_x, active); // bitwise, no flop + __m256 f_y_safe = _mm256_and_ps(f_y, active); // bitwise, no flop + acc_x = _mm256_fmadd_ps(f_x_safe, w_val, acc_x); // # 8 MULTs, 8 ADDs + acc_y = _mm256_fmadd_ps(f_y_safe, w_val, acc_y); // # 8 MULTs, 8 ADDs } - __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); + __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); // # 8 ADDs (x and y interleaved) __m128 hsum_lo = _mm256_castps256_ps128(hsum); __m128 hsum_hi = _mm256_extractf128_ps(hsum, 1); - __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); - sum4 = _mm_hadd_ps(sum4, sum4); + __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); // # 4 ADDs + sum4 = _mm_hadd_ps(sum4, sum4); // # 4 ADDs (total hadd: 16 ADDs for x+y) alignas(16) float sum_out[4]; _mm_store_ps(sum_out, sum4); - f_alpha_dt_x += sum_out[0]; - f_alpha_dt_y += sum_out[1]; + f_alpha_dt_x += sum_out[0]; // # 1 ADD + f_alpha_dt_y += sum_out[1]; // # 1 ADD for (int k = 0; k < board.n_obstacles; k++) { - float v1_x = pos_x[i] - board.obstacles[k].start_x; - float v1_y = pos_y[i] - board.obstacles[k].start_y; + float v1_x = pos_x[i] - board.obstacles[k].start_x; // # 1 ADD + float v1_y = pos_y[i] - board.obstacles[k].start_y; // # 1 ADD float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; + float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD if (projection < 0.0) projection = 0.0; else if (projection > 1.0) projection = 1.0; - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); // # 1 MULT, 2 ADDs + float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); // # 1 MULT, 2 ADDs - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); + float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - float f_alpha_B_neg_y = r_aB_y * scalar_mult; + float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV + float f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT + float f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT float w_val; @@ -382,28 +408,28 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else w_val = c_neg_times_dt; - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT + float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - f_alpha_dt_x += f_alpha_B_dt_x; - f_alpha_dt_y += f_alpha_B_dt_y; + f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD + f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD } - w_x[i] += f_alpha_dt_x; - w_y[i] += f_alpha_dt_y; + w_x[i] += f_alpha_dt_x; // # 1 ADD + w_y[i] += f_alpha_dt_y; // # 1 ADD } for (int i = 0; i < n_p; i++) { - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); + float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT float w_norm2 = w_norm > EPS ? w_norm : EPS; - float correction_factor = min(1.0f, v_max[i] / w_norm2); + float correction_factor = min(1.0f, v_max[i] / w_norm2); // # 1 DIV - curr_vel_x[i] = w_x[i] * correction_factor; - curr_vel_y[i] = w_y[i] * correction_factor; + curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT + curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; + pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD + pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { pos_x[i] = 1.0; @@ -424,22 +450,22 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - float ex = destination_x[i] - pos_x[i]; - float ey = destination_y[i] - pos_y[i]; + float ex = destination_x[i] - pos_x[i]; // # 1 ADD + float ey = destination_y[i] - pos_y[i]; // # 1 ADD - float e_norm = sqrt(ex * ex + ey * ey); + float e_norm = sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT float e_norm2 = e_norm > EPS ? e_norm : EPS; - float e_norm_inv = 1.0 / e_norm2; + float e_norm_inv = 1.0 / e_norm2; // # 1 DIV - e_x[i] = ex * e_norm_inv; - e_y[i] = ey * e_norm_inv; + e_x[i] = ex * e_norm_inv; // # 1 MULT + e_y[i] = ey * e_norm_inv; // # 1 MULT - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; - float velocity_norm = sqrt(velocity_norm_sq); + float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD + float velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - sb_sq[i] = velocity_norm_sq * delta_t_sq; - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); + sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT + sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) + sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT #if !BENCHMARK if (dump_path && (t % dump_interval == 0)) { diff --git a/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_expfix.cpp b/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_expfix.cpp deleted file mode 100644 index 0de7907..0000000 --- a/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_expfix.cpp +++ /dev/null @@ -1,490 +0,0 @@ -// # Changes: pure vrsqrtps -// # Predecessor: variant_float_inlining_SoA_AVX2_polyexp.cpp - -// -// We first tried rsqrt + 1 Newton step to keep full sqrt precision, but it -// turned out slower than just using vsqrtps. The Newton step needs 2 muls -// and an fnmadd on top of rsqrt, so the whole recovered sqrt ends up at -// ~20 cycles of latency, while vsqrtps is only 12. Since the OoO core was -// already running multiple sqrts in parallel, sqrt throughput wasn't really -// the bottleneck. All the Newton step did was make the dependency chain -// longer, which hurt more than the faster rsqrt helped. -// -// So we drop the Newton step. rsqrt + 1 mul gives us a sqrt in 8 cycles -// with about 12-bit precision, which is well within the 1cm verify tolerance. - -// The main structural win is killing the vdivps. rsqrt produces 1/sqrt(x) -// directly, so we keep the inverses around (inv_r_ab_n, inv_r_ab2_n, -// inv_two_b) and rewrite the force scaling. The original had -// "numer / (two_b * r_ab_n * r_ab2_n)" which needs a vdivps; now it's -// "numer * inv_two_b * inv_r_ab_n * inv_r_ab2_n", trading one divide for -// two muls. Divide was the long-pole op on port 0, so this is the bigger -// of the two changes. - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include // avx2 - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - const int n_p_pad = (n_p + 7) & ~7; - - pos_x.resize(n_p_pad); - pos_y.resize(n_p_pad); - sb_sq.resize(n_p_pad); - sb_e_x.resize(n_p_pad); - sb_e_y.resize(n_p_pad); - e_x.resize(n_p_pad); - e_y.resize(n_p_pad); - curr_vel_x.resize(n_p_pad); - curr_vel_y.resize(n_p_pad); - w_x.resize(n_p_pad); - w_y.resize(n_p_pad); - desired_v0.resize(n_p_pad); - v_max.resize(n_p_pad); - destination_x.resize(n_p_pad); - destination_y.resize(n_p_pad); - color.resize(n_p_pad); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int j = n_p; j < n_p_pad; j++) { - pos_x[j] = 1e9f; - pos_y[j] = 1e9f; - sb_sq[j] = 0.0f; - sb_e_x[j] = 0.0f; - sb_e_y[j] = 0.0f; - } - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -// simd expf, degree-12 taylor on the [-ln2/2, ln2/2] reduced range, -// schraudolph 2^n bit trick on the exponent slot. -// # 128 MULTs, 112 ADDs -static inline __m256 exp256_ps(__m256 x) { - const __m256 log2e = _mm256_set1_ps(1.4426950408889634f); - __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); // # 8 MULTs - - const __m256 ln2 = _mm256_set1_ps(0.6931471805599453f); - __m256 r = _mm256_fnmadd_ps(nf, ln2, x); // # 8 MULTs, 8 ADDs - - const __m256 c13 = _mm256_set1_ps(c13_scalar); - const __m256 c12 = _mm256_set1_ps(c12_scalar); - const __m256 c11 = _mm256_set1_ps(c11_scalar); - const __m256 c10 = _mm256_set1_ps(c10_scalar); - const __m256 c9 = _mm256_set1_ps(c9_scalar); - const __m256 c8 = _mm256_set1_ps(c8_scalar); - const __m256 c7 = _mm256_set1_ps(c7_scalar); - const __m256 c6 = _mm256_set1_ps(c6_scalar); - const __m256 c5 = _mm256_set1_ps(c5_scalar); - const __m256 c4 = _mm256_set1_ps(c4_scalar); - const __m256 c3 = _mm256_set1_ps(c3_scalar); - const __m256 c2 = _mm256_set1_ps(c2_scalar); - const __m256 c1 = _mm256_set1_ps(c1_scalar); - const __m256 c0 = _mm256_set1_ps(c0_scalar); - - __m256 p = c13; - - // # 13 * (8 MULTs, 8 ADDs) - // # = 104 MULTs, 104 ADDs - p = _mm256_fmadd_ps(p, r, c12); - p = _mm256_fmadd_ps(p, r, c11); - p = _mm256_fmadd_ps(p, r, c10); - p = _mm256_fmadd_ps(p, r, c9); - p = _mm256_fmadd_ps(p, r, c8); - p = _mm256_fmadd_ps(p, r, c7); - p = _mm256_fmadd_ps(p, r, c6); - p = _mm256_fmadd_ps(p, r, c5); - p = _mm256_fmadd_ps(p, r, c4); - p = _mm256_fmadd_ps(p, r, c3); - p = _mm256_fmadd_ps(p, r, c2); - p = _mm256_fmadd_ps(p, r, c1); - p = _mm256_fmadd_ps(p, r, c0); - - __m256i ni = _mm256_cvtps_epi32(nf); - __m256i biased = _mm256_add_epi32(ni, _mm256_set1_epi32(127)); - __m256i scaled = _mm256_slli_epi32(biased, 23); - __m256 pow2n = _mm256_castsi256_ps(scaled); - - return _mm256_mul_ps(p, pow2n); // # 8 MULTs -} - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - const int n_p_pad = (n_p + 7) & ~7; - - alignas(32) const float lane_offset_arr[8] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; - const __m256 lane_off = _mm256_load_ps(lane_offset_arr); - - const __m256 v_neg_v0_div_2s = _mm256_set1_ps(NEG_V0_AB_div_TWO_SIGMA); - const __m256 v_neg_2s_inv = _mm256_set1_ps(NEG_TWO_SIGMA_INV); - const __m256 v_cos_phi_sq = _mm256_set1_ps(cos_phi_sq); - const __m256 v_dt_neg = _mm256_set1_ps(dt_neg); - const __m256 v_c_neg_dt = _mm256_set1_ps(c_neg_times_dt); - const __m256 v_zero = _mm256_setzero_ps(); - const __m256 v_eps_sq = _mm256_set1_ps(EPS_SQ); - const __m256 v_np = _mm256_set1_ps((float)n_p); - const __m256 v_exp_min = _mm256_set1_ps(-87.0f); - - // # flops per iteration (one pass of the outer time loop): - // # ADDs: n_p * 22 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 10 + n_p * 7 - // # MULTs: n_p * 4 + n_p * n_p_pad * 42 + n_p * board.n_obstacles * 13 + n_p * 16 - // # DIVs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 2 - // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 - // # EXPs: n_p * board.n_obstacles - for (int t = 0; t < n_iter; t++) { - - for (int i = 0; i < n_p; i++) { - - float desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - - const __m256 pix = _mm256_set1_ps(pos_x[i]); - const __m256 piy = _mm256_set1_ps(pos_y[i]); - const __m256 eix = _mm256_set1_ps(e_x[i]); - const __m256 eiy = _mm256_set1_ps(e_y[i]); - const __m256 v_i = _mm256_set1_ps((float)i); - - __m256 acc_x = _mm256_setzero_ps(); - __m256 acc_y = _mm256_setzero_ps(); - - for (int j = 0; j < n_p_pad; j += 8) { - __m256 j_idx = _mm256_add_ps(_mm256_set1_ps((float)j), lane_off); // # 8 ADDs - - __m256 m_neq_i = _mm256_cmp_ps(j_idx, v_i, _CMP_NEQ_OQ); - __m256 m_in_range = _mm256_cmp_ps(j_idx, v_np, _CMP_LT_OQ); - __m256 active = _mm256_and_ps(m_neq_i, m_in_range); - - __m256 pjx = _mm256_loadu_ps(&pos_x[j]); - __m256 pjy = _mm256_loadu_ps(&pos_y[j]); - __m256 sbe_jx = _mm256_loadu_ps(&sb_e_x[j]); - __m256 sbe_jy = _mm256_loadu_ps(&sb_e_y[j]); - __m256 sb_jsq = _mm256_loadu_ps(&sb_sq[j]); - - __m256 r_ab_x = _mm256_sub_ps(pix, pjx); // # 8 ADDs - __m256 r_ab_y = _mm256_sub_ps(piy, pjy); // # 8 ADDs - __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); // # 16 MULTs, 8 ADDs - // clamp before rsqrt: rsqrt(0) = inf, then x*inf = 0*inf = NaN downstream. EPS_SQ - // means the inv_sqrt tops out at 1/EPS and r_ab_n = EPS_SQ/EPS = EPS, matches the predecessor. - r_ab_n2 = _mm256_max_ps(r_ab_n2, v_eps_sq); - __m256 inv_r_ab_n = _mm256_rsqrt_ps(r_ab_n2); // # 8 SQRTs, 8 DIVs - __m256 r_ab_n = _mm256_mul_ps(r_ab_n2, inv_r_ab_n); // # 8 MULTs - - __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); // # 8 ADDs - __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); // # 8 ADDs - __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); // # 16 MULTs, 8 ADDs - r_ab2_n2 = _mm256_max_ps(r_ab2_n2, v_eps_sq); - __m256 inv_r_ab2_n = _mm256_rsqrt_ps(r_ab2_n2); // # 8 SQRTs, 8 DIVs - __m256 r_ab2_n = _mm256_mul_ps(r_ab2_n2, inv_r_ab2_n); // # 8 MULTs - - __m256 r_sum = _mm256_add_ps(r_ab_n, r_ab2_n); // # 8 ADDs - __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); // # 8 MULTs, 8 ADDs - two_b_sq = _mm256_max_ps(two_b_sq, v_eps_sq); - __m256 inv_two_b = _mm256_rsqrt_ps(two_b_sq); // # 8 SQRTs, 8 DIVs - __m256 two_b = _mm256_mul_ps(two_b_sq, inv_two_b); // # 8 MULTs - - __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); // # 8 MULTs - e_arg = _mm256_max_ps(e_arg, v_exp_min); - __m256 e_val = exp256_ps(e_arg); // # 128 MULTs, 112 ADDs - - // smc = numer/denom in the predecessor. with inv-sqrts in hand the divide is free: - // sm_r_ab = smc * r_ab2_n = numer / (two_b * r_ab_n) = numer * inv_two_b * inv_r_ab_n - // sm_r_ab2 = smc * r_ab_n = numer / (two_b * r_ab2_n) = numer * inv_two_b * inv_r_ab2_n - // for the i==j and padding lanes inv_two_b*inv_r_ab_n is huge, but the active-mask AND on - // f_x/f_y below zeros the lane out before accumulation so it cant pollute anything. - __m256 numer_inv_two_b = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), _mm256_mul_ps(e_val, inv_two_b)); // # 24 MULTs - __m256 sm_r_ab = _mm256_mul_ps(numer_inv_two_b, inv_r_ab_n); // # 8 MULTs - __m256 sm_r_ab2 = _mm256_mul_ps(numer_inv_two_b, inv_r_ab2_n); // # 8 MULTs - - __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); // # 16 MULTs, 8 ADDs - __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); // # 16 MULTs, 8 ADDs - - __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); // # 16 MULTs, 8 ADDs - __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); // # 8 MULTs - __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); // # 16 MULTs, 8 ADDs - __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); // # 8 MULTs - - __m256 c1 = _mm256_cmp_ps(e_dot_f, v_zero, _CMP_LT_OQ); - __m256 c2 = _mm256_cmp_ps(e_dot_f_sq, rhs, _CMP_GT_OQ); - __m256 cond = _mm256_and_ps(c1, c2); - __m256 w_val = _mm256_blendv_ps(v_dt_neg, v_c_neg_dt, cond); - - __m256 f_x_safe = _mm256_and_ps(f_x, active); // bitwise, no flop - __m256 f_y_safe = _mm256_and_ps(f_y, active); // bitwise, no flop - acc_x = _mm256_fmadd_ps(f_x_safe, w_val, acc_x); // # 8 MULTs, 8 ADDs - acc_y = _mm256_fmadd_ps(f_y_safe, w_val, acc_y); // # 8 MULTs, 8 ADDs - } - - __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); // # 8 ADDs (x and y interleaved) - __m128 hsum_lo = _mm256_castps256_ps128(hsum); - __m128 hsum_hi = _mm256_extractf128_ps(hsum, 1); - __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); // # 4 ADDs - sum4 = _mm_hadd_ps(sum4, sum4); // # 4 ADDs (total hadd: 16 ADDs for x+y) - alignas(16) float sum_out[4]; - _mm_store_ps(sum_out, sum4); - f_alpha_dt_x += sum_out[0]; // # 1 ADD - f_alpha_dt_y += sum_out[1]; // # 1 ADD - - for (int k = 0; k < board.n_obstacles; k++) { - - float v1_x = pos_x[i] - board.obstacles[k].start_x; // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; // # 1 ADD - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); // # 1 MULT, 2 ADDs - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); // # 1 MULT, 2 ADDs - - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV - float f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT - - float w_val; - - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - - f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD - } - - w_x[i] += f_alpha_dt_x; // # 1 ADD - w_y[i] += f_alpha_dt_y; // # 1 ADD - } - - for (int i = 0; i < n_p; i++) { - - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT - float w_norm2 = w_norm > EPS ? w_norm : EPS; - float correction_factor = min(1.0f, v_max[i] / w_norm2); // # 1 DIV - - curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - float ex = destination_x[i] - pos_x[i]; // # 1 ADD - float ey = destination_y[i] - pos_y[i]; // # 1 ADD - - float e_norm = sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT - float e_norm2 = e_norm > EPS ? e_norm : EPS; - float e_norm_inv = 1.0 / e_norm2; // # 1 DIV - - e_x[i] = ex * e_norm_inv; // # 1 MULT - e_y[i] = ey * e_norm_inv; // # 1 MULT - - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD - float velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - - sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.cpp b/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.cpp index 2efc3c5..e6760fb 100644 --- a/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.cpp +++ b/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt.cpp @@ -187,23 +187,43 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } -// simd expf, same as the predecessor. degree-5 taylor on the [-ln2/2, ln2/2] reduced range, +// simd expf, degree-12 taylor on the [-ln2/2, ln2/2] reduced range, // schraudolph 2^n bit trick on the exponent slot. +// # 128 MULTs, 112 ADDs static inline __m256 exp256_ps(__m256 x) { const __m256 log2e = _mm256_set1_ps(1.4426950408889634f); - __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); + __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); // # 8 MULTs const __m256 ln2 = _mm256_set1_ps(0.6931471805599453f); - __m256 r = _mm256_fnmadd_ps(nf, ln2, x); + __m256 r = _mm256_fnmadd_ps(nf, ln2, x); // # 8 MULTs, 8 ADDs - const __m256 c5 = _mm256_set1_ps(1.0f / 120.0f); - const __m256 c4 = _mm256_set1_ps(1.0f / 24.0f); - const __m256 c3 = _mm256_set1_ps(1.0f / 6.0f); - const __m256 c2 = _mm256_set1_ps(0.5f); - const __m256 c1 = _mm256_set1_ps(1.0f); - const __m256 c0 = _mm256_set1_ps(1.0f); + const __m256 c13 = _mm256_set1_ps(c13_scalar); + const __m256 c12 = _mm256_set1_ps(c12_scalar); + const __m256 c11 = _mm256_set1_ps(c11_scalar); + const __m256 c10 = _mm256_set1_ps(c10_scalar); + const __m256 c9 = _mm256_set1_ps(c9_scalar); + const __m256 c8 = _mm256_set1_ps(c8_scalar); + const __m256 c7 = _mm256_set1_ps(c7_scalar); + const __m256 c6 = _mm256_set1_ps(c6_scalar); + const __m256 c5 = _mm256_set1_ps(c5_scalar); + const __m256 c4 = _mm256_set1_ps(c4_scalar); + const __m256 c3 = _mm256_set1_ps(c3_scalar); + const __m256 c2 = _mm256_set1_ps(c2_scalar); + const __m256 c1 = _mm256_set1_ps(c1_scalar); + const __m256 c0 = _mm256_set1_ps(c0_scalar); - __m256 p = c5; + __m256 p = c13; + + // # 13 * (8 MULTs, 8 ADDs) + // # = 104 MULTs, 104 ADDs + p = _mm256_fmadd_ps(p, r, c12); + p = _mm256_fmadd_ps(p, r, c11); + p = _mm256_fmadd_ps(p, r, c10); + p = _mm256_fmadd_ps(p, r, c9); + p = _mm256_fmadd_ps(p, r, c8); + p = _mm256_fmadd_ps(p, r, c7); + p = _mm256_fmadd_ps(p, r, c6); + p = _mm256_fmadd_ps(p, r, c5); p = _mm256_fmadd_ps(p, r, c4); p = _mm256_fmadd_ps(p, r, c3); p = _mm256_fmadd_ps(p, r, c2); @@ -215,7 +235,7 @@ static inline __m256 exp256_ps(__m256 x) { __m256i scaled = _mm256_slli_epi32(biased, 23); __m256 pow2n = _mm256_castsi256_ps(scaled); - return _mm256_mul_ps(p, pow2n); + return _mm256_mul_ps(p, pow2n); // # 8 MULTs } int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { @@ -253,15 +273,21 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c // const __m256 v_np = _mm256_set1_ps((float)n_p); const __m256 v_exp_min = _mm256_set1_ps(-87.0f); + // # flops per iteration (one pass of the outer time loop): + // # ADDs: n_p * 22 + n_p * n_p_pad * 28 + n_p * board.n_obstacles * 10 + n_p * 7 + // # MULTs: n_p * 4 + n_p * n_p_pad * 42 + n_p * board.n_obstacles * 13 + n_p * 16 + // # DIVs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 2 + // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 + // # EXPs: n_p * board.n_obstacles for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { - float desired_velocity_x = e_x[i] * desired_v0[i]; - float desired_velocity_y = e_y[i] * desired_v0[i]; + float desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT + float desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; + float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD + float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD // ghost-self trick: park pedestrian i at 1e9 for its own j-loop, broadcasting i's // real position from a saved copy. then the j==i lane reads 1e9 like a padding lane @@ -291,51 +317,51 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m256 sbe_jy = _mm256_loadu_ps(&sb_e_y[j]); __m256 sb_jsq = _mm256_loadu_ps(&sb_sq[j]); - __m256 r_ab_x = _mm256_sub_ps(pix, pjx); - __m256 r_ab_y = _mm256_sub_ps(piy, pjy); - __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); + __m256 r_ab_x = _mm256_sub_ps(pix, pjx); // # 8 ADDs + __m256 r_ab_y = _mm256_sub_ps(piy, pjy); // # 8 ADDs + __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); // # 16 MULTs, 8 ADDs // clamp before rsqrt: rsqrt(0) = inf, then x*inf = 0*inf = NaN downstream. EPS_SQ // means the inv_sqrt tops out at 1/EPS and r_ab_n = EPS_SQ/EPS = EPS, matches the predecessor. r_ab_n2 = _mm256_max_ps(r_ab_n2, v_eps_sq); - __m256 inv_r_ab_n = _mm256_rsqrt_ps(r_ab_n2); + __m256 inv_r_ab_n = _mm256_rsqrt_ps(r_ab_n2); // # 8 SQRTs, 8 DIVs //__m256 r_ab_n = _mm256_mul_ps(r_ab_n2, inv_r_ab_n); - __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); - __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); - __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); + __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); // # 8 ADDs + __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); // # 8 ADDs + __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); // # 16 MULTs, 8 ADDs r_ab2_n2 = _mm256_max_ps(r_ab2_n2, v_eps_sq); - __m256 inv_r_ab2_n = _mm256_rsqrt_ps(r_ab2_n2); + __m256 inv_r_ab2_n = _mm256_rsqrt_ps(r_ab2_n2); // # 8 SQRTs, 8 DIVs //__m256 r_ab2_n = _mm256_mul_ps(r_ab2_n2, inv_r_ab2_n); // r_sum = |r_ab| + |r_ab2|, fused. sqrt(x) = x * rsqrt(x) // the two norms are used only here (later code uses inv_r_ab_n / inv_r_ab2_n), so fold the two // recovery muls + add into one mul + one fmadd. -1 FP op, -2 live regs. - __m256 r_sum = _mm256_fmadd_ps(r_ab2_n2, inv_r_ab2_n, _mm256_mul_ps(r_ab_n2, inv_r_ab_n)); - __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); + __m256 r_sum = _mm256_fmadd_ps(r_ab2_n2, inv_r_ab2_n, _mm256_mul_ps(r_ab_n2, inv_r_ab_n)); // # 16 MULTs, 8 ADDs + __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); // # 8 MULTs, 8 ADDs two_b_sq = _mm256_max_ps(two_b_sq, v_eps_sq); - __m256 inv_two_b = _mm256_rsqrt_ps(two_b_sq); - __m256 two_b = _mm256_mul_ps(two_b_sq, inv_two_b); + __m256 inv_two_b = _mm256_rsqrt_ps(two_b_sq); // # 8 SQRTs, 8 DIVs + __m256 two_b = _mm256_mul_ps(two_b_sq, inv_two_b); // # 8 MULTs - __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); + __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); // # 8 MULTs e_arg = _mm256_max_ps(e_arg, v_exp_min); - __m256 e_val = exp256_ps(e_arg); + __m256 e_val = exp256_ps(e_arg); // # 128 MULTs, 112 ADDs // smc = numer/denom in the predecessor. with inv-sqrts in hand the divide is free: // sm_r_ab = smc * r_ab2_n = numer / (two_b * r_ab_n) = numer * inv_two_b * inv_r_ab_n // sm_r_ab2 = smc * r_ab_n = numer / (two_b * r_ab2_n) = numer * inv_two_b * inv_r_ab2_n // for the i==j and padding lanes inv_two_b*inv_r_ab_n is huge, but the active-mask AND on // f_x/f_y below zeros the lane out before accumulation so it cant pollute anything. - __m256 numer_inv_two_b = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), _mm256_mul_ps(e_val, inv_two_b)); - __m256 sm_r_ab = _mm256_mul_ps(numer_inv_two_b, inv_r_ab_n); - __m256 sm_r_ab2 = _mm256_mul_ps(numer_inv_two_b, inv_r_ab2_n); + __m256 numer_inv_two_b = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), _mm256_mul_ps(e_val, inv_two_b)); // # 24 MULTs + __m256 sm_r_ab = _mm256_mul_ps(numer_inv_two_b, inv_r_ab_n); // # 8 MULTs + __m256 sm_r_ab2 = _mm256_mul_ps(numer_inv_two_b, inv_r_ab2_n); // # 8 MULTs - __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); - __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); + __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); // # 16 MULTs, 8 ADDs + __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); // # 16 MULTs, 8 ADDs - __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); - __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); - __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); - __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); + __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); // # 16 MULTs, 8 ADDs + __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); // # 8 MULTs + __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); // # 16 MULTs, 8 ADDs + __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); // # 8 MULTs __m256 c1 = _mm256_cmp_ps(e_dot_f, v_zero, _CMP_LT_OQ); __m256 c2 = _mm256_cmp_ps(e_dot_f_sq, rhs, _CMP_GT_OQ); @@ -344,47 +370,47 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c //__m256 f_x_safe = _mm256_and_ps(f_x, active); //__m256 f_y_safe = _mm256_and_ps(f_y, active); - acc_x = _mm256_fmadd_ps(f_x, w_val, acc_x); - acc_y = _mm256_fmadd_ps(f_y, w_val, acc_y); + acc_x = _mm256_fmadd_ps(f_x, w_val, acc_x); // # 8 MULTs, 8 ADDs + acc_y = _mm256_fmadd_ps(f_y, w_val, acc_y); // # 8 MULTs, 8 ADDs } // restore the values pos_x[i] = saved_pix; pos_y[i] = saved_piy; - __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); + __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); // # 8 ADDs (x and y interleaved) __m128 hsum_lo = _mm256_castps256_ps128(hsum); __m128 hsum_hi = _mm256_extractf128_ps(hsum, 1); - __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); - sum4 = _mm_hadd_ps(sum4, sum4); + __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); // # 4 ADDs + sum4 = _mm_hadd_ps(sum4, sum4); // # 4 ADDs (total hadd: 16 ADDs for x+y) alignas(16) float sum_out[4]; _mm_store_ps(sum_out, sum4); - f_alpha_dt_x += sum_out[0]; - f_alpha_dt_y += sum_out[1]; + f_alpha_dt_x += sum_out[0]; // # 1 ADD + f_alpha_dt_y += sum_out[1]; // # 1 ADD for (int k = 0; k < board.n_obstacles; k++) { - float v1_x = pos_x[i] - board.obstacles[k].start_x; - float v1_y = pos_y[i] - board.obstacles[k].start_y; + float v1_x = pos_x[i] - board.obstacles[k].start_x; // # 1 ADD + float v1_y = pos_y[i] - board.obstacles[k].start_y; // # 1 ADD float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; + float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD if (projection < 0.0) projection = 0.0; else if (projection > 1.0) projection = 1.0; - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); // # 1 MULT, 2 ADDs + float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); // # 1 MULT, 2 ADDs - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); + float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - float f_alpha_B_neg_y = r_aB_y * scalar_mult; + float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV + float f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT + float f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT float w_val; @@ -395,28 +421,28 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else w_val = c_neg_times_dt; - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT + float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - f_alpha_dt_x += f_alpha_B_dt_x; - f_alpha_dt_y += f_alpha_B_dt_y; + f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD + f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD } - w_x[i] += f_alpha_dt_x; - w_y[i] += f_alpha_dt_y; + w_x[i] += f_alpha_dt_x; // # 1 ADD + w_y[i] += f_alpha_dt_y; // # 1 ADD } for (int i = 0; i < n_p; i++) { - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); + float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT float w_norm2 = w_norm > EPS ? w_norm : EPS; - float correction_factor = min(1.0f, v_max[i] / w_norm2); + float correction_factor = min(1.0f, v_max[i] / w_norm2); // # 1 DIV - curr_vel_x[i] = w_x[i] * correction_factor; - curr_vel_y[i] = w_y[i] * correction_factor; + curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT + curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; + pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD + pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { pos_x[i] = 1.0; @@ -437,22 +463,22 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - float ex = destination_x[i] - pos_x[i]; - float ey = destination_y[i] - pos_y[i]; + float ex = destination_x[i] - pos_x[i]; // # 1 ADD + float ey = destination_y[i] - pos_y[i]; // # 1 ADD - float e_norm = sqrt(ex * ex + ey * ey); + float e_norm = sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT float e_norm2 = e_norm > EPS ? e_norm : EPS; - float e_norm_inv = 1.0 / e_norm2; + float e_norm_inv = 1.0 / e_norm2; // # 1 DIV - e_x[i] = ex * e_norm_inv; - e_y[i] = ey * e_norm_inv; + e_x[i] = ex * e_norm_inv; // # 1 MULT + e_y[i] = ey * e_norm_inv; // # 1 MULT - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; - float velocity_norm = sqrt(velocity_norm_sq); + float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD + float velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - sb_sq[i] = velocity_norm_sq * delta_t_sq; - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); + sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT + sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) + sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT #if !BENCHMARK if (dump_path && (t % dump_interval == 0)) { diff --git a/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt_expfix.cpp b/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt_expfix.cpp deleted file mode 100644 index e6760fb..0000000 --- a/variants/variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt_expfix.cpp +++ /dev/null @@ -1,503 +0,0 @@ -// # Changes: pure vrsqrtps -// # Predecessor: variant_float_inlining_SoA_AVX2_polyexp.cpp - -// -// We first tried rsqrt + 1 Newton step to keep full sqrt precision, but it -// turned out slower than just using vsqrtps. The Newton step needs 2 muls -// and an fnmadd on top of rsqrt, so the whole recovered sqrt ends up at -// ~20 cycles of latency, while vsqrtps is only 12. Since the OoO core was -// already running multiple sqrts in parallel, sqrt throughput wasn't really -// the bottleneck. All the Newton step did was make the dependency chain -// longer, which hurt more than the faster rsqrt helped. -// -// So we drop the Newton step. rsqrt + 1 mul gives us a sqrt in 8 cycles -// with about 12-bit precision, which is well within the 1cm verify tolerance. - -// The main structural win is killing the vdivps. rsqrt produces 1/sqrt(x) -// directly, so we keep the inverses around (inv_r_ab_n, inv_r_ab2_n, -// inv_two_b) and rewrite the force scaling. The original had -// "numer / (two_b * r_ab_n * r_ab2_n)" which needs a vdivps; now it's -// "numer * inv_two_b * inv_r_ab_n * inv_r_ab2_n", trading one divide for -// two muls. Divide was the long-pole op on port 0, so this is the bigger -// of the two changes. - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include // avx2 - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - const int n_p_pad = (n_p + 7) & ~7; - - pos_x.resize(n_p_pad); - pos_y.resize(n_p_pad); - sb_sq.resize(n_p_pad); - sb_e_x.resize(n_p_pad); - sb_e_y.resize(n_p_pad); - e_x.resize(n_p_pad); - e_y.resize(n_p_pad); - curr_vel_x.resize(n_p_pad); - curr_vel_y.resize(n_p_pad); - w_x.resize(n_p_pad); - w_y.resize(n_p_pad); - desired_v0.resize(n_p_pad); - v_max.resize(n_p_pad); - destination_x.resize(n_p_pad); - destination_y.resize(n_p_pad); - color.resize(n_p_pad); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int j = n_p; j < n_p_pad; j++) { - pos_x[j] = 1e9f; - pos_y[j] = 1e9f; - sb_sq[j] = 0.0f; - sb_e_x[j] = 0.0f; - sb_e_y[j] = 0.0f; - } - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -// simd expf, degree-12 taylor on the [-ln2/2, ln2/2] reduced range, -// schraudolph 2^n bit trick on the exponent slot. -// # 128 MULTs, 112 ADDs -static inline __m256 exp256_ps(__m256 x) { - const __m256 log2e = _mm256_set1_ps(1.4426950408889634f); - __m256 nf = _mm256_round_ps(_mm256_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); // # 8 MULTs - - const __m256 ln2 = _mm256_set1_ps(0.6931471805599453f); - __m256 r = _mm256_fnmadd_ps(nf, ln2, x); // # 8 MULTs, 8 ADDs - - const __m256 c13 = _mm256_set1_ps(c13_scalar); - const __m256 c12 = _mm256_set1_ps(c12_scalar); - const __m256 c11 = _mm256_set1_ps(c11_scalar); - const __m256 c10 = _mm256_set1_ps(c10_scalar); - const __m256 c9 = _mm256_set1_ps(c9_scalar); - const __m256 c8 = _mm256_set1_ps(c8_scalar); - const __m256 c7 = _mm256_set1_ps(c7_scalar); - const __m256 c6 = _mm256_set1_ps(c6_scalar); - const __m256 c5 = _mm256_set1_ps(c5_scalar); - const __m256 c4 = _mm256_set1_ps(c4_scalar); - const __m256 c3 = _mm256_set1_ps(c3_scalar); - const __m256 c2 = _mm256_set1_ps(c2_scalar); - const __m256 c1 = _mm256_set1_ps(c1_scalar); - const __m256 c0 = _mm256_set1_ps(c0_scalar); - - __m256 p = c13; - - // # 13 * (8 MULTs, 8 ADDs) - // # = 104 MULTs, 104 ADDs - p = _mm256_fmadd_ps(p, r, c12); - p = _mm256_fmadd_ps(p, r, c11); - p = _mm256_fmadd_ps(p, r, c10); - p = _mm256_fmadd_ps(p, r, c9); - p = _mm256_fmadd_ps(p, r, c8); - p = _mm256_fmadd_ps(p, r, c7); - p = _mm256_fmadd_ps(p, r, c6); - p = _mm256_fmadd_ps(p, r, c5); - p = _mm256_fmadd_ps(p, r, c4); - p = _mm256_fmadd_ps(p, r, c3); - p = _mm256_fmadd_ps(p, r, c2); - p = _mm256_fmadd_ps(p, r, c1); - p = _mm256_fmadd_ps(p, r, c0); - - __m256i ni = _mm256_cvtps_epi32(nf); - __m256i biased = _mm256_add_epi32(ni, _mm256_set1_epi32(127)); - __m256i scaled = _mm256_slli_epi32(biased, 23); - __m256 pow2n = _mm256_castsi256_ps(scaled); - - return _mm256_mul_ps(p, pow2n); // # 8 MULTs -} - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - const int n_p_pad = (n_p + 7) & ~7; - - alignas(32) const float lane_offset_arr[8] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; - const __m256 lane_off = _mm256_load_ps(lane_offset_arr); - - const __m256 v_neg_v0_div_2s = _mm256_set1_ps(NEG_V0_AB_div_TWO_SIGMA); - const __m256 v_neg_2s_inv = _mm256_set1_ps(NEG_TWO_SIGMA_INV); - const __m256 v_cos_phi_sq = _mm256_set1_ps(cos_phi_sq); - const __m256 v_dt_neg = _mm256_set1_ps(dt_neg); - const __m256 v_c_neg_dt = _mm256_set1_ps(c_neg_times_dt); - const __m256 v_zero = _mm256_setzero_ps(); - const __m256 v_eps_sq = _mm256_set1_ps(EPS_SQ); - // const __m256 v_np = _mm256_set1_ps((float)n_p); - const __m256 v_exp_min = _mm256_set1_ps(-87.0f); - - // # flops per iteration (one pass of the outer time loop): - // # ADDs: n_p * 22 + n_p * n_p_pad * 28 + n_p * board.n_obstacles * 10 + n_p * 7 - // # MULTs: n_p * 4 + n_p * n_p_pad * 42 + n_p * board.n_obstacles * 13 + n_p * 16 - // # DIVs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 2 - // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 - // # EXPs: n_p * board.n_obstacles - for (int t = 0; t < n_iter; t++) { - - for (int i = 0; i < n_p; i++) { - - float desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - - // ghost-self trick: park pedestrian i at 1e9 for its own j-loop, broadcasting i's - // real position from a saved copy. then the j==i lane reads 1e9 like a padding lane - // -> ~1e-37 force, self-cancels with no mask. restored right after the loop. - const float saved_pix = pos_x[i]; - const float saved_piy = pos_y[i]; - const __m256 pix = _mm256_set1_ps(saved_pix); - const __m256 piy = _mm256_set1_ps(saved_piy); - const __m256 eix = _mm256_set1_ps(e_x[i]); - const __m256 eiy = _mm256_set1_ps(e_y[i]); - pos_x[i] = 1e9f; - pos_y[i] = 1e9f; - - __m256 acc_x = _mm256_setzero_ps(); - __m256 acc_y = _mm256_setzero_ps(); - - for (int j = 0; j < n_p_pad; j += 8) { - // __m256 j_idx = _mm256_add_ps(_mm256_set1_ps((float)j), lane_off); - // - // __m256 m_neq_i = _mm256_cmp_ps(j_idx, v_i, _CMP_NEQ_OQ); - // __m256 m_in_range = _mm256_cmp_ps(j_idx, v_np, _CMP_LT_OQ); - // __m256 active = _mm256_and_ps(m_neq_i, m_in_range); - - __m256 pjx = _mm256_loadu_ps(&pos_x[j]); - __m256 pjy = _mm256_loadu_ps(&pos_y[j]); - __m256 sbe_jx = _mm256_loadu_ps(&sb_e_x[j]); - __m256 sbe_jy = _mm256_loadu_ps(&sb_e_y[j]); - __m256 sb_jsq = _mm256_loadu_ps(&sb_sq[j]); - - __m256 r_ab_x = _mm256_sub_ps(pix, pjx); // # 8 ADDs - __m256 r_ab_y = _mm256_sub_ps(piy, pjy); // # 8 ADDs - __m256 r_ab_n2 = _mm256_fmadd_ps(r_ab_x, r_ab_x, _mm256_mul_ps(r_ab_y, r_ab_y)); // # 16 MULTs, 8 ADDs - // clamp before rsqrt: rsqrt(0) = inf, then x*inf = 0*inf = NaN downstream. EPS_SQ - // means the inv_sqrt tops out at 1/EPS and r_ab_n = EPS_SQ/EPS = EPS, matches the predecessor. - r_ab_n2 = _mm256_max_ps(r_ab_n2, v_eps_sq); - __m256 inv_r_ab_n = _mm256_rsqrt_ps(r_ab_n2); // # 8 SQRTs, 8 DIVs - //__m256 r_ab_n = _mm256_mul_ps(r_ab_n2, inv_r_ab_n); - - __m256 r_ab2_x = _mm256_sub_ps(r_ab_x, sbe_jx); // # 8 ADDs - __m256 r_ab2_y = _mm256_sub_ps(r_ab_y, sbe_jy); // # 8 ADDs - __m256 r_ab2_n2 = _mm256_fmadd_ps(r_ab2_x, r_ab2_x, _mm256_mul_ps(r_ab2_y, r_ab2_y)); // # 16 MULTs, 8 ADDs - r_ab2_n2 = _mm256_max_ps(r_ab2_n2, v_eps_sq); - __m256 inv_r_ab2_n = _mm256_rsqrt_ps(r_ab2_n2); // # 8 SQRTs, 8 DIVs - //__m256 r_ab2_n = _mm256_mul_ps(r_ab2_n2, inv_r_ab2_n); - - // r_sum = |r_ab| + |r_ab2|, fused. sqrt(x) = x * rsqrt(x) - // the two norms are used only here (later code uses inv_r_ab_n / inv_r_ab2_n), so fold the two - // recovery muls + add into one mul + one fmadd. -1 FP op, -2 live regs. - __m256 r_sum = _mm256_fmadd_ps(r_ab2_n2, inv_r_ab2_n, _mm256_mul_ps(r_ab_n2, inv_r_ab_n)); // # 16 MULTs, 8 ADDs - __m256 two_b_sq = _mm256_fmsub_ps(r_sum, r_sum, sb_jsq); // # 8 MULTs, 8 ADDs - two_b_sq = _mm256_max_ps(two_b_sq, v_eps_sq); - __m256 inv_two_b = _mm256_rsqrt_ps(two_b_sq); // # 8 SQRTs, 8 DIVs - __m256 two_b = _mm256_mul_ps(two_b_sq, inv_two_b); // # 8 MULTs - - __m256 e_arg = _mm256_mul_ps(two_b, v_neg_2s_inv); // # 8 MULTs - e_arg = _mm256_max_ps(e_arg, v_exp_min); - __m256 e_val = exp256_ps(e_arg); // # 128 MULTs, 112 ADDs - - // smc = numer/denom in the predecessor. with inv-sqrts in hand the divide is free: - // sm_r_ab = smc * r_ab2_n = numer / (two_b * r_ab_n) = numer * inv_two_b * inv_r_ab_n - // sm_r_ab2 = smc * r_ab_n = numer / (two_b * r_ab2_n) = numer * inv_two_b * inv_r_ab2_n - // for the i==j and padding lanes inv_two_b*inv_r_ab_n is huge, but the active-mask AND on - // f_x/f_y below zeros the lane out before accumulation so it cant pollute anything. - __m256 numer_inv_two_b = _mm256_mul_ps(_mm256_mul_ps(r_sum, v_neg_v0_div_2s), _mm256_mul_ps(e_val, inv_two_b)); // # 24 MULTs - __m256 sm_r_ab = _mm256_mul_ps(numer_inv_two_b, inv_r_ab_n); // # 8 MULTs - __m256 sm_r_ab2 = _mm256_mul_ps(numer_inv_two_b, inv_r_ab2_n); // # 8 MULTs - - __m256 f_x = _mm256_fmadd_ps(r_ab2_x, sm_r_ab2, _mm256_mul_ps(r_ab_x, sm_r_ab)); // # 16 MULTs, 8 ADDs - __m256 f_y = _mm256_fmadd_ps(r_ab2_y, sm_r_ab2, _mm256_mul_ps(r_ab_y, sm_r_ab)); // # 16 MULTs, 8 ADDs - - __m256 f_norm_sq = _mm256_fmadd_ps(f_x, f_x, _mm256_mul_ps(f_y, f_y)); // # 16 MULTs, 8 ADDs - __m256 rhs = _mm256_mul_ps(f_norm_sq, v_cos_phi_sq); // # 8 MULTs - __m256 e_dot_f = _mm256_fmadd_ps(eix, f_x, _mm256_mul_ps(eiy, f_y)); // # 16 MULTs, 8 ADDs - __m256 e_dot_f_sq = _mm256_mul_ps(e_dot_f, e_dot_f); // # 8 MULTs - - __m256 c1 = _mm256_cmp_ps(e_dot_f, v_zero, _CMP_LT_OQ); - __m256 c2 = _mm256_cmp_ps(e_dot_f_sq, rhs, _CMP_GT_OQ); - __m256 cond = _mm256_and_ps(c1, c2); - __m256 w_val = _mm256_blendv_ps(v_dt_neg, v_c_neg_dt, cond); - - //__m256 f_x_safe = _mm256_and_ps(f_x, active); - //__m256 f_y_safe = _mm256_and_ps(f_y, active); - acc_x = _mm256_fmadd_ps(f_x, w_val, acc_x); // # 8 MULTs, 8 ADDs - acc_y = _mm256_fmadd_ps(f_y, w_val, acc_y); // # 8 MULTs, 8 ADDs - } - - // restore the values - pos_x[i] = saved_pix; - pos_y[i] = saved_piy; - - __m256 hsum = _mm256_hadd_ps(acc_x, acc_y); // # 8 ADDs (x and y interleaved) - __m128 hsum_lo = _mm256_castps256_ps128(hsum); - __m128 hsum_hi = _mm256_extractf128_ps(hsum, 1); - __m128 sum4 = _mm_add_ps(hsum_lo, hsum_hi); // # 4 ADDs - sum4 = _mm_hadd_ps(sum4, sum4); // # 4 ADDs (total hadd: 16 ADDs for x+y) - alignas(16) float sum_out[4]; - _mm_store_ps(sum_out, sum4); - f_alpha_dt_x += sum_out[0]; // # 1 ADD - f_alpha_dt_y += sum_out[1]; // # 1 ADD - - for (int k = 0; k < board.n_obstacles; k++) { - - float v1_x = pos_x[i] - board.obstacles[k].start_x; // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; // # 1 ADD - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); // # 1 MULT, 2 ADDs - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); // # 1 MULT, 2 ADDs - - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV - float f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT - - float w_val; - - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - - f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD - } - - w_x[i] += f_alpha_dt_x; // # 1 ADD - w_y[i] += f_alpha_dt_y; // # 1 ADD - } - - for (int i = 0; i < n_p; i++) { - - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT - float w_norm2 = w_norm > EPS ? w_norm : EPS; - float correction_factor = min(1.0f, v_max[i] / w_norm2); // # 1 DIV - - curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - float ex = destination_x[i] - pos_x[i]; // # 1 ADD - float ey = destination_y[i] - pos_y[i]; // # 1 ADD - - float e_norm = sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT - float e_norm2 = e_norm > EPS ? e_norm : EPS; - float e_norm_inv = 1.0 / e_norm2; // # 1 DIV - - e_x[i] = ex * e_norm_inv; // # 1 MULT - e_y[i] = ey * e_norm_inv; // # 1 MULT - - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD - float velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - - sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp.cpp index 95dd277..bcbeeea 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0f #define BOARD_LENGTH 50.0f +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f #define WALL_EPS 1e-3f #define BOARD_WIDTH_MINUS_WALL_EPS 9.999f @@ -161,21 +163,46 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 256 MULTs, 224 ADDs static inline __m512 _avx512_exp_ps(__m512 x) { + const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); + + // # 16 MULTs __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); + + // # 16 MULTs, 16 ADDs __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - const __m512 c5 = _mm512_set1_ps(1.0f / 120.0f); - const __m512 c4 = _mm512_set1_ps(1.0f / 24.0f); - const __m512 c3 = _mm512_set1_ps(1.0f / 6.0f); - const __m512 c2 = _mm512_set1_ps(0.5f); - const __m512 c1 = _mm512_set1_ps(1.0f); - const __m512 c0 = _mm512_set1_ps(1.0f); + const __m512 c13 = _mm512_set1_ps(c13_scalar); + const __m512 c12 = _mm512_set1_ps(c12_scalar); + const __m512 c11 = _mm512_set1_ps(c11_scalar); + const __m512 c10 = _mm512_set1_ps(c10_scalar); + const __m512 c9 = _mm512_set1_ps(c9_scalar); + const __m512 c8 = _mm512_set1_ps(c8_scalar); + const __m512 c7 = _mm512_set1_ps(c7_scalar); + const __m512 c6 = _mm512_set1_ps(c6_scalar); + const __m512 c5 = _mm512_set1_ps(c5_scalar); + const __m512 c4 = _mm512_set1_ps(c4_scalar); + const __m512 c3 = _mm512_set1_ps(c3_scalar); + const __m512 c2 = _mm512_set1_ps(c2_scalar); + const __m512 c1 = _mm512_set1_ps(c1_scalar); + const __m512 c0 = _mm512_set1_ps(c0_scalar); - __m512 p = c5; + __m512 p = c13; + + // # 13 * (16 MULTs, 16 ADDs) + // # = 208 MULTs, 208 ADDs + p = _mm512_fmadd_ps(p, r, c12); + p = _mm512_fmadd_ps(p, r, c11); + p = _mm512_fmadd_ps(p, r, c10); + p = _mm512_fmadd_ps(p, r, c9); + p = _mm512_fmadd_ps(p, r, c8); + p = _mm512_fmadd_ps(p, r, c7); + p = _mm512_fmadd_ps(p, r, c6); + p = _mm512_fmadd_ps(p, r, c5); p = _mm512_fmadd_ps(p, r, c4); p = _mm512_fmadd_ps(p, r, c3); p = _mm512_fmadd_ps(p, r, c2); @@ -187,6 +214,7 @@ static inline __m512 _avx512_exp_ps(__m512 x) { __m512i scaled = _mm512_slli_epi32(biased, 23); __m512 pow2n = _mm512_castsi512_ps(scaled); + // # 16 MULTs return _mm512_mul_ps(p, pow2n); } @@ -225,11 +253,42 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512 v_np = _mm512_set1_ps((float)n_p); const __m512 v_exp_min = _mm512_set1_ps(-87.0f); + // # ADDs: n_iter * ( + // # n_p * 36 + + // # n_p * n_p_pad * 29 + + // # n_p * board.n_obstacles * 10 + + // # n_p * 7 + // # ) + + // # MULTs: n_iter * ( + // # n_p * 4 + + // # n_p * n_p_pad * 40 + + // # n_p * board.n_obstacles * 13 + + // # n_p * 17 + // # ) + + // # DIVs: n_iter * ( + // # n_p * n_p_pad + + // # n_p * board.n_obstacles + + // # n_p * 2 + // # ) + + // # SQRTs: n_iter * ( + // # n_p * n_p_pad * 3 + + // # n_p * board.n_obstacles + + // # n_p * 3 + // # ) + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + + // # 2 MULTs float desired_velocity_x = e_x[i] * desired_v0[i]; float desired_velocity_y = e_y[i] * desired_v0[i]; + // # 2 ADDs, 2 MULTs float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; @@ -243,6 +302,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 acc_y = _mm512_setzero_ps(); for (int j = 0; j < n_p_pad; j += 16) { + + // # 16 ADDs __m512 j_idx = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); __mmask16 m_neq_i = _mm512_cmp_ps_mask(j_idx, v_i, _CMP_NEQ_OQ); @@ -255,42 +316,76 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 sbe_jy = _mm512_loadu_ps(&sb_e_y[j]); __m512 sb_jsq = _mm512_loadu_ps(&sb_sq[j]); + // # 32 ADDs __m512 r_ab_x = _mm512_sub_ps(pix, pjx); __m512 r_ab_y = _mm512_sub_ps(piy, pjy); + + // # 32 MULTs, 16 ADDs __m512 r_ab_n2 = _mm512_fmadd_ps(r_ab_x, r_ab_x, _mm512_mul_ps(r_ab_y, r_ab_y)); + + // # 16 SQRTs __m512 r_ab_n = _mm512_sqrt_ps(r_ab_n2); + r_ab_n = _mm512_max_ps(r_ab_n, v_eps); + // # 32 ADDs __m512 r_ab2_x = _mm512_sub_ps(r_ab_x, sbe_jx); __m512 r_ab2_y = _mm512_sub_ps(r_ab_y, sbe_jy); + + // # 32 MULTs, 16 ADDs __m512 r_ab2_n2 = _mm512_fmadd_ps(r_ab2_x, r_ab2_x, _mm512_mul_ps(r_ab2_y, r_ab2_y)); + + // # 16 SQRTs __m512 r_ab2_n = _mm512_sqrt_ps(r_ab2_n2); + r_ab2_n = _mm512_max_ps(r_ab2_n, v_eps); + // # 16 ADDs __m512 r_sum = _mm512_add_ps(r_ab_n, r_ab2_n); + + // # 16 MULTs, 16 ADDs __m512 two_b_sq = _mm512_fmsub_ps(r_sum, r_sum, sb_jsq); + two_b_sq = _mm512_max_ps(two_b_sq, v_eps_sq); + + // # 16 SQRTs __m512 two_b = _mm512_sqrt_ps(two_b_sq); + // # 16 MULTs __m512 e_arg = _mm512_mul_ps(two_b, v_neg_2s_inv); + e_arg = _mm512_max_ps(e_arg, v_exp_min); + + // # 256 MULTs, 224 ADDs __m512 e_val = _avx512_exp_ps(e_arg); + // # 64 MULTs __m512 numer = _mm512_mul_ps(_mm512_mul_ps(r_sum, v_neg_v0_div_2s), e_val); __m512 denom = _mm512_mul_ps(_mm512_mul_ps(two_b, r_ab_n), r_ab2_n); __m512 denom_safe = _mm512_mask_blend_ps(active, v_one, denom); + + // # 16 DIVs __m512 smc = _mm512_div_ps(numer, denom_safe); + // # 32 MULTs __m512 sm_r_ab = _mm512_mul_ps(smc, r_ab2_n); __m512 sm_r_ab2 = _mm512_mul_ps(smc, r_ab_n); + // # 64 MULTs, 32 ADDs __m512 f_x = _mm512_fmadd_ps(r_ab2_x, sm_r_ab2, _mm512_mul_ps(r_ab_x, sm_r_ab)); __m512 f_y = _mm512_fmadd_ps(r_ab2_y, sm_r_ab2, _mm512_mul_ps(r_ab_y, sm_r_ab)); + // # 32 MULTs, 16 ADDs __m512 f_norm_sq = _mm512_fmadd_ps(f_x, f_x, _mm512_mul_ps(f_y, f_y)); + + // # 16 MULTs __m512 rhs = _mm512_mul_ps(f_norm_sq, v_cos_phi_sq); + + // # 32 MULTs, 16 ADDs __m512 e_dot_f = _mm512_fmadd_ps(eix, f_x, _mm512_mul_ps(eiy, f_y)); + + // # 16 MULTs __m512 e_dot_f_sq = _mm512_mul_ps(e_dot_f, e_dot_f); __mmask16 c1 = _mm512_cmp_ps_mask(e_dot_f, v_zero, _CMP_LT_OQ); @@ -302,19 +397,24 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 f_x_safe = _mm512_maskz_mov_ps(active, f_x); __m512 f_y_safe = _mm512_maskz_mov_ps(active, f_y); + // # 32 MULTs, 32 ADDs acc_x = _mm512_fmadd_ps(f_x_safe, w_val, acc_x); acc_y = _mm512_fmadd_ps(f_y_safe, w_val, acc_y); } + // # 32 ADDs f_alpha_dt_x += _mm512_reduce_add_ps(acc_x); f_alpha_dt_y += _mm512_reduce_add_ps(acc_y); for (int k = 0; k < board.n_obstacles; k++) { + + // # 2 ADDs float v1_x = pos_x[i] - board.obstacles[k].start_x; float v1_y = pos_y[i] - board.obstacles[k].start_y; float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; + // # 3 MULTs, 1 ADD float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0f) @@ -322,12 +422,15 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0f) projection = 1.0f; + // # 4 ADDs, 2 MULTs float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; + // # 4 MULTs, 1 EXP, 1 DIV float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; float f_alpha_B_neg_x = r_aB_x * scalar_mult; float f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -338,9 +441,11 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTS float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + // # 2 ADDs f_alpha_dt_x += f_alpha_B_dt_x; f_alpha_dt_y += f_alpha_B_dt_y; } @@ -350,17 +455,24 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c } for (int i = 0; i < n_p; i++) { + + // # 1 SQRT, 2 MULTs, 1 ADD float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); + float w_norm2 = w_norm > EPS ? w_norm : EPS; + + // # 1 DIV float correction_factor = min(1.0f, v_max[i] / w_norm2); + // # 2 MULTs curr_vel_x[i] = w_x[i] * correction_factor; curr_vel_y[i] = w_y[i] * correction_factor; + // # 2 MULTs, 2 ADDs pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5f)) { + if (color[i] == 0 && pos_x[i] > BOARD_LENGTH_MINUS_ONE_POINT_FIVE) { pos_x[i] = 1.0f; destination_y[i] = pos_y[i]; w_x[i] = desired_v0[i]; @@ -369,7 +481,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c curr_vel_y[i] = w_y[i]; } if (color[i] == 1 && pos_x[i] < 1.5f) { - pos_x[i] = BOARD_LENGTH - 1.0f; + pos_x[i] = BOARD_LENGTH_MINUS_ONE; destination_y[i] = pos_y[i]; w_x[i] = -desired_v0[i]; w_y[i] = 0.0f; @@ -379,19 +491,29 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); + // # 2 ADDs float ex = destination_x[i] - pos_x[i]; float ey = destination_y[i] - pos_y[i]; + // # 1 SQRT, 2 MULTs, 1 ADD float e_norm = sqrt(ex * ex + ey * ey); + float e_norm2 = e_norm > EPS ? e_norm : EPS; + + // # 1 DIV float e_norm_inv = 1.0f / e_norm2; + // # 2 MULTs e_x[i] = ex * e_norm_inv; e_y[i] = ey * e_norm_inv; + // # 2 MULTs, 1 ADD float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; + + // # 1 SQRT float velocity_norm = sqrt(velocity_norm_sq); + // # 5 MULTs sb_sq[i] = velocity_norm_sq * delta_t_sq; sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.cpp index 1e3dbb5..57d5811 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp_fastinvsqrt.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0f #define BOARD_LENGTH 50.0f +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f #define WALL_EPS 1e-3f #define BOARD_WIDTH_MINUS_WALL_EPS 9.999f @@ -161,21 +163,46 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 256 MULTs, 224 ADDs static inline __m512 _avx512_exp_ps(__m512 x) { + const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); + + // # 16 MULTs __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); + + // # 16 MULTs, 16 ADDs __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - const __m512 c5 = _mm512_set1_ps(1.0f / 120.0f); - const __m512 c4 = _mm512_set1_ps(1.0f / 24.0f); - const __m512 c3 = _mm512_set1_ps(1.0f / 6.0f); - const __m512 c2 = _mm512_set1_ps(0.5f); - const __m512 c1 = _mm512_set1_ps(1.0f); - const __m512 c0 = _mm512_set1_ps(1.0f); + const __m512 c13 = _mm512_set1_ps(c13_scalar); + const __m512 c12 = _mm512_set1_ps(c12_scalar); + const __m512 c11 = _mm512_set1_ps(c11_scalar); + const __m512 c10 = _mm512_set1_ps(c10_scalar); + const __m512 c9 = _mm512_set1_ps(c9_scalar); + const __m512 c8 = _mm512_set1_ps(c8_scalar); + const __m512 c7 = _mm512_set1_ps(c7_scalar); + const __m512 c6 = _mm512_set1_ps(c6_scalar); + const __m512 c5 = _mm512_set1_ps(c5_scalar); + const __m512 c4 = _mm512_set1_ps(c4_scalar); + const __m512 c3 = _mm512_set1_ps(c3_scalar); + const __m512 c2 = _mm512_set1_ps(c2_scalar); + const __m512 c1 = _mm512_set1_ps(c1_scalar); + const __m512 c0 = _mm512_set1_ps(c0_scalar); - __m512 p = c5; + __m512 p = c13; + + // # 13 * (16 MULTs, 16 ADDs) + // # = 208 MULTs, 208 ADDs + p = _mm512_fmadd_ps(p, r, c12); + p = _mm512_fmadd_ps(p, r, c11); + p = _mm512_fmadd_ps(p, r, c10); + p = _mm512_fmadd_ps(p, r, c9); + p = _mm512_fmadd_ps(p, r, c8); + p = _mm512_fmadd_ps(p, r, c7); + p = _mm512_fmadd_ps(p, r, c6); + p = _mm512_fmadd_ps(p, r, c5); p = _mm512_fmadd_ps(p, r, c4); p = _mm512_fmadd_ps(p, r, c3); p = _mm512_fmadd_ps(p, r, c2); @@ -187,9 +214,11 @@ static inline __m512 _avx512_exp_ps(__m512 x) { __m512i scaled = _mm512_slli_epi32(biased, 23); __m512 pow2n = _mm512_castsi512_ps(scaled); + // # 16 MULTs return _mm512_mul_ps(p, pow2n); } +// # 7 MULTs, 2 ADDs static inline float fast_inv_sqrt(float number) { float y = number; @@ -199,7 +228,10 @@ static inline float fast_inv_sqrt(float number) { y = bit_cast(i); + // # 1 MULT float x2 = number * 0.5; + + // # 6 MULTs, 2 ADDs y = y * (1.5 - (x2 * y * y)); y = y * (1.5 - (x2 * y * y)); @@ -241,11 +273,41 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512 v_np = _mm512_set1_ps((float)n_p); const __m512 v_exp_min = _mm512_set1_ps(-87.0f); + // # ADDs: n_iter * ( + // # n_p * 36 + + // # n_p * n_p_pad * 29 + + // # n_p * board.n_obstacles * 10 + + // # n_p * 11 + // # ) + + // # MULTs: n_iter * ( + // # n_p * 4 + + // # n_p * n_p_pad * 40 + + // # n_p * board.n_obstacles * 13 + + // # n_p * 30 + // # ) + + // # DIVs: n_iter * ( + // # n_p * n_p_pad + + // # n_p * board.n_obstacles + // # ) + + // # SQRTs: n_iter * ( + // # n_p * n_p_pad * 3 + + // # n_p * board.n_obstacles + + // # n_p + // # ) + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + + // # 2 MULTs float desired_velocity_x = e_x[i] * desired_v0[i]; float desired_velocity_y = e_y[i] * desired_v0[i]; + // # 2 ADDs, 2 MULTs float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; @@ -259,6 +321,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 acc_y = _mm512_setzero_ps(); for (int j = 0; j < n_p_pad; j += 16) { + + // # 16 ADDs __m512 j_idx = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); __mmask16 m_neq_i = _mm512_cmp_ps_mask(j_idx, v_i, _CMP_NEQ_OQ); @@ -271,42 +335,76 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 sbe_jy = _mm512_loadu_ps(&sb_e_y[j]); __m512 sb_jsq = _mm512_loadu_ps(&sb_sq[j]); + // # 32 ADDs __m512 r_ab_x = _mm512_sub_ps(pix, pjx); __m512 r_ab_y = _mm512_sub_ps(piy, pjy); + + // # 32 MULTs, 16 ADDs __m512 r_ab_n2 = _mm512_fmadd_ps(r_ab_x, r_ab_x, _mm512_mul_ps(r_ab_y, r_ab_y)); + + // # 16 SQRTs __m512 r_ab_n = _mm512_sqrt_ps(r_ab_n2); + r_ab_n = _mm512_max_ps(r_ab_n, v_eps); + // # 32 ADDs __m512 r_ab2_x = _mm512_sub_ps(r_ab_x, sbe_jx); __m512 r_ab2_y = _mm512_sub_ps(r_ab_y, sbe_jy); + + // # 32 MULTs, 16 ADDs __m512 r_ab2_n2 = _mm512_fmadd_ps(r_ab2_x, r_ab2_x, _mm512_mul_ps(r_ab2_y, r_ab2_y)); + + // # 16 SQRTs __m512 r_ab2_n = _mm512_sqrt_ps(r_ab2_n2); + r_ab2_n = _mm512_max_ps(r_ab2_n, v_eps); + // # 16 ADDs __m512 r_sum = _mm512_add_ps(r_ab_n, r_ab2_n); + + // # 16 MULTs, 16 ADDs __m512 two_b_sq = _mm512_fmsub_ps(r_sum, r_sum, sb_jsq); + two_b_sq = _mm512_max_ps(two_b_sq, v_eps_sq); + + // # 16 SQRTs __m512 two_b = _mm512_sqrt_ps(two_b_sq); + // # 16 MULTs __m512 e_arg = _mm512_mul_ps(two_b, v_neg_2s_inv); + e_arg = _mm512_max_ps(e_arg, v_exp_min); + + // # 256 MULTs, 224 ADDs __m512 e_val = _avx512_exp_ps(e_arg); + // # 64 MULTs __m512 numer = _mm512_mul_ps(_mm512_mul_ps(r_sum, v_neg_v0_div_2s), e_val); __m512 denom = _mm512_mul_ps(_mm512_mul_ps(two_b, r_ab_n), r_ab2_n); __m512 denom_safe = _mm512_mask_blend_ps(active, v_one, denom); + + // # 16 DIVs __m512 smc = _mm512_div_ps(numer, denom_safe); + // # 32 MULTs __m512 sm_r_ab = _mm512_mul_ps(smc, r_ab2_n); __m512 sm_r_ab2 = _mm512_mul_ps(smc, r_ab_n); + // # 64 MULTs, 32 ADDs __m512 f_x = _mm512_fmadd_ps(r_ab2_x, sm_r_ab2, _mm512_mul_ps(r_ab_x, sm_r_ab)); __m512 f_y = _mm512_fmadd_ps(r_ab2_y, sm_r_ab2, _mm512_mul_ps(r_ab_y, sm_r_ab)); + // # 32 MULTs, 16 ADDs __m512 f_norm_sq = _mm512_fmadd_ps(f_x, f_x, _mm512_mul_ps(f_y, f_y)); + + // # 16 MULTs __m512 rhs = _mm512_mul_ps(f_norm_sq, v_cos_phi_sq); + + // # 32 MULTs, 16 ADDs __m512 e_dot_f = _mm512_fmadd_ps(eix, f_x, _mm512_mul_ps(eiy, f_y)); + + // # 16 MULTs __m512 e_dot_f_sq = _mm512_mul_ps(e_dot_f, e_dot_f); __mmask16 c1 = _mm512_cmp_ps_mask(e_dot_f, v_zero, _CMP_LT_OQ); @@ -318,19 +416,24 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 f_x_safe = _mm512_maskz_mov_ps(active, f_x); __m512 f_y_safe = _mm512_maskz_mov_ps(active, f_y); + // # 32 MULTs, 32 ADDs acc_x = _mm512_fmadd_ps(f_x_safe, w_val, acc_x); acc_y = _mm512_fmadd_ps(f_y_safe, w_val, acc_y); } + // # 32 ADDs f_alpha_dt_x += _mm512_reduce_add_ps(acc_x); f_alpha_dt_y += _mm512_reduce_add_ps(acc_y); for (int k = 0; k < board.n_obstacles; k++) { + + // # 2 ADDs float v1_x = pos_x[i] - board.obstacles[k].start_x; float v1_y = pos_y[i] - board.obstacles[k].start_y; float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; + // # 3 MULTs, 1 ADD float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0f) @@ -338,12 +441,17 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0f) projection = 1.0f; + // # 4 ADDs, 2 MULTs float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); + // # 2 MULTs, 1 EXP, 1 DIV float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; + + // # 2 MULTs float f_alpha_B_neg_x = r_aB_x * scalar_mult; float f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -353,28 +461,36 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTs float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + // # 2 ADDs f_alpha_dt_x += f_alpha_B_dt_x; f_alpha_dt_y += f_alpha_B_dt_y; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } for (int i = 0; i < n_p; i++) { + // # 9 MULTs, 3 ADDs float w_norm_inv = fast_inv_sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); + + // # 1 MULT float correction_factor = min(1.0f, v_max[i] * w_norm_inv); + // # 2 MULTs curr_vel_x[i] = w_x[i] * correction_factor; curr_vel_y[i] = w_y[i] * correction_factor; + // # 2 MULTs, 2 ADDs pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5f)) { + if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH_MINUS_ONE_POINT_FIVE)) { pos_x[i] = 1.0f; destination_y[i] = pos_y[i]; w_x[i] = desired_v0[i]; @@ -383,7 +499,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c curr_vel_y[i] = w_y[i]; } if (color[i] == 1 && pos_x[i] < 1.5f) { - pos_x[i] = BOARD_LENGTH - 1.0f; + pos_x[i] = BOARD_LENGTH_MINUS_ONE; destination_y[i] = pos_y[i]; w_x[i] = -desired_v0[i]; w_y[i] = 0.0f; @@ -393,19 +509,29 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); + // # 2 ADDs float ex = destination_x[i] - pos_x[i]; float ey = destination_y[i] - pos_y[i]; + // # 9 MULTs, 3 ADDs float e_norm_inv = fast_inv_sqrt(ex * ex + ey * ey); + // # 2 MULTs e_x[i] = ex * e_norm_inv; e_y[i] = ey * e_norm_inv; + // # 2 MULTs, 1 ADD double velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; + + // # 1 MULT double sb_sq_i = velocity_norm_sq * delta_t_sq; + + // # 1 SQRT double velocity_norm_mul_delta_t = sqrt(sb_sq_i); sb_sq[i] = sb_sq_i; + + // # 2 MULTs sb_e_x[i] = e_x[i] * velocity_norm_mul_delta_t; sb_e_y[i] = e_y[i] * velocity_norm_mul_delta_t; diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx.cpp index 15e0cbe..89d349d 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0f #define BOARD_LENGTH 50.0f +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f #define WALL_EPS 1e-3f #define BOARD_WIDTH_MINUS_WALL_EPS 9.999f @@ -161,21 +163,46 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 256 MULTs, 224 ADDs static inline __m512 _avx512_exp_ps(__m512 x) { + const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); + + // # 16 MULTs __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); + + // # 16 MULTs, 16 ADDs __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - const __m512 c5 = _mm512_set1_ps(1.0f / 120.0f); - const __m512 c4 = _mm512_set1_ps(1.0f / 24.0f); - const __m512 c3 = _mm512_set1_ps(1.0f / 6.0f); - const __m512 c2 = _mm512_set1_ps(0.5f); - const __m512 c1 = _mm512_set1_ps(1.0f); - const __m512 c0 = _mm512_set1_ps(1.0f); + const __m512 c13 = _mm512_set1_ps(c13_scalar); + const __m512 c12 = _mm512_set1_ps(c12_scalar); + const __m512 c11 = _mm512_set1_ps(c11_scalar); + const __m512 c10 = _mm512_set1_ps(c10_scalar); + const __m512 c9 = _mm512_set1_ps(c9_scalar); + const __m512 c8 = _mm512_set1_ps(c8_scalar); + const __m512 c7 = _mm512_set1_ps(c7_scalar); + const __m512 c6 = _mm512_set1_ps(c6_scalar); + const __m512 c5 = _mm512_set1_ps(c5_scalar); + const __m512 c4 = _mm512_set1_ps(c4_scalar); + const __m512 c3 = _mm512_set1_ps(c3_scalar); + const __m512 c2 = _mm512_set1_ps(c2_scalar); + const __m512 c1 = _mm512_set1_ps(c1_scalar); + const __m512 c0 = _mm512_set1_ps(c0_scalar); - __m512 p = c5; + __m512 p = c13; + + // # 13 * (16 MULTs, 16 ADDs) + // # = 208 MULTs, 208 ADDs + p = _mm512_fmadd_ps(p, r, c12); + p = _mm512_fmadd_ps(p, r, c11); + p = _mm512_fmadd_ps(p, r, c10); + p = _mm512_fmadd_ps(p, r, c9); + p = _mm512_fmadd_ps(p, r, c8); + p = _mm512_fmadd_ps(p, r, c7); + p = _mm512_fmadd_ps(p, r, c6); + p = _mm512_fmadd_ps(p, r, c5); p = _mm512_fmadd_ps(p, r, c4); p = _mm512_fmadd_ps(p, r, c3); p = _mm512_fmadd_ps(p, r, c2); @@ -187,6 +214,7 @@ static inline __m512 _avx512_exp_ps(__m512 x) { __m512i scaled = _mm512_slli_epi32(biased, 23); __m512 pow2n = _mm512_castsi512_ps(scaled); + // # 16 MULTs return _mm512_mul_ps(p, pow2n); } @@ -225,11 +253,42 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512 v_np = _mm512_set1_ps((float)n_p); const __m512 v_exp_min = _mm512_set1_ps(-87.0f); + // # ADDs: n_iter * ( + // # n_p * 36 + + // # n_p * n_p_pad * 29 + + // # n_p * board.n_obstacles * 10 + + // # n_p_pad * 7 + // # ) + + // # MULTs: n_iter * ( + // # n_p * 4 + + // # n_p * n_p_pad * 40 + + // # n_p * board.n_obstacles * 13 + + // # n_p_pad * 15 + // # ) + + // # DIVs: n_iter * ( + // # n_p * n_p_pad + + // # n_p * board.n_obstacles + + // # n_p_pad * 2 + // # ) + + // # SQRTs: n_iter * ( + // # n_p * n_p_pad * 3 + + // # n_p * board.n_obstacles + + // # n_p_pad * 3 + // # ) + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + + // # 2 MULTs float desired_velocity_x = e_x[i] * desired_v0[i]; float desired_velocity_y = e_y[i] * desired_v0[i]; + // # 2 ADDs, 2 MULTs float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; @@ -243,6 +302,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 acc_y = _mm512_setzero_ps(); for (int j = 0; j < n_p_pad; j += 16) { + + // # 16 ADDs __m512 j_idx = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); __mmask16 m_neq_i = _mm512_cmp_ps_mask(j_idx, v_i, _CMP_NEQ_OQ); @@ -255,42 +316,75 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 sbe_jy = _mm512_loadu_ps(&sb_e_y[j]); __m512 sb_jsq = _mm512_loadu_ps(&sb_sq[j]); + // # 32 ADDs __m512 r_ab_x = _mm512_sub_ps(pix, pjx); __m512 r_ab_y = _mm512_sub_ps(piy, pjy); + + // # 32 MULTs, 16 ADDs __m512 r_ab_n2 = _mm512_fmadd_ps(r_ab_x, r_ab_x, _mm512_mul_ps(r_ab_y, r_ab_y)); + + // # 16 SQRTs __m512 r_ab_n = _mm512_sqrt_ps(r_ab_n2); + r_ab_n = _mm512_max_ps(r_ab_n, v_eps); + // # 32 ADDs __m512 r_ab2_x = _mm512_sub_ps(r_ab_x, sbe_jx); __m512 r_ab2_y = _mm512_sub_ps(r_ab_y, sbe_jy); + + // # 32 MULTs, 16 ADDs __m512 r_ab2_n2 = _mm512_fmadd_ps(r_ab2_x, r_ab2_x, _mm512_mul_ps(r_ab2_y, r_ab2_y)); + + // # 16 SQRTs __m512 r_ab2_n = _mm512_sqrt_ps(r_ab2_n2); + r_ab2_n = _mm512_max_ps(r_ab2_n, v_eps); + // # 16 ADDs __m512 r_sum = _mm512_add_ps(r_ab_n, r_ab2_n); + + // # 16 MULTs, 16 ADDs __m512 two_b_sq = _mm512_fmsub_ps(r_sum, r_sum, sb_jsq); + two_b_sq = _mm512_max_ps(two_b_sq, v_eps_sq); + + // # 16 SQRTs __m512 two_b = _mm512_sqrt_ps(two_b_sq); + // # 16 MULTs __m512 e_arg = _mm512_mul_ps(two_b, v_neg_2s_inv); + e_arg = _mm512_max_ps(e_arg, v_exp_min); + + // # 256 MULTs, 224 ADDs __m512 e_val = _avx512_exp_ps(e_arg); + // # 64 MULTs __m512 numer = _mm512_mul_ps(_mm512_mul_ps(r_sum, v_neg_v0_div_2s), e_val); __m512 denom = _mm512_mul_ps(_mm512_mul_ps(two_b, r_ab_n), r_ab2_n); - __m512 denom_safe = _mm512_mask_blend_ps(active, v_one, denom); + + // # 16 DIVs __m512 smc = _mm512_div_ps(numer, denom_safe); + // # 32 MULTs __m512 sm_r_ab = _mm512_mul_ps(smc, r_ab2_n); __m512 sm_r_ab2 = _mm512_mul_ps(smc, r_ab_n); + // # 64 MULTs, 32 ADDs __m512 f_x = _mm512_fmadd_ps(r_ab2_x, sm_r_ab2, _mm512_mul_ps(r_ab_x, sm_r_ab)); __m512 f_y = _mm512_fmadd_ps(r_ab2_y, sm_r_ab2, _mm512_mul_ps(r_ab_y, sm_r_ab)); + // # 32 MULTs, 16 ADDs __m512 f_norm_sq = _mm512_fmadd_ps(f_x, f_x, _mm512_mul_ps(f_y, f_y)); + + // # 16 MULTs __m512 rhs = _mm512_mul_ps(f_norm_sq, v_cos_phi_sq); + + // # 32 MULTs, 16 ADDs __m512 e_dot_f = _mm512_fmadd_ps(eix, f_x, _mm512_mul_ps(eiy, f_y)); + + // # 16 MULTs __m512 e_dot_f_sq = _mm512_mul_ps(e_dot_f, e_dot_f); __mmask16 c1 = _mm512_cmp_ps_mask(e_dot_f, v_zero, _CMP_LT_OQ); @@ -302,19 +396,24 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 f_x_safe = _mm512_maskz_mov_ps(active, f_x); __m512 f_y_safe = _mm512_maskz_mov_ps(active, f_y); + // # 32 MULTs, 32 ADDs acc_x = _mm512_fmadd_ps(f_x_safe, w_val, acc_x); acc_y = _mm512_fmadd_ps(f_y_safe, w_val, acc_y); } + // # 32 ADDs f_alpha_dt_x += _mm512_reduce_add_ps(acc_x); f_alpha_dt_y += _mm512_reduce_add_ps(acc_y); for (int k = 0; k < board.n_obstacles; k++) { + + // # 2 ADDs float v1_x = pos_x[i] - board.obstacles[k].start_x; float v1_y = pos_y[i] - board.obstacles[k].start_y; float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; + // # 3 MULTs, 1 ADD float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0f) @@ -322,12 +421,15 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0f) projection = 1.0f; + // # 4 ADDs, 2 MULTs float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; + // # 4 MULTs, 1 EXP, 1 DIV float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; float f_alpha_B_neg_x = r_aB_x * scalar_mult; float f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -338,19 +440,22 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTS float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + // # 2 ADDs f_alpha_dt_x += f_alpha_B_dt_x; f_alpha_dt_y += f_alpha_B_dt_y; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } for (int i = 0; i < n_p_pad; i += 16) { - + __m512 w_x_i_vec = _mm512_loadu_ps(&w_x[i]); __m512 w_y_i_vec = _mm512_loadu_ps(&w_y[i]); __m512 v_max_i_vec = _mm512_loadu_ps(&v_max[i]); @@ -359,23 +464,31 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 destination_y_i_vec = _mm512_loadu_ps(&destination_y[i]); __m512 destination_x_i_vec = _mm512_loadu_ps(&destination_x[i]); __m512 desired_v0_i_vec = _mm512_loadu_ps(&desired_v0[i]); - - __m512i color_i_vec = _mm512_loadu_si512((__m512i*)&color[i]); + __m512i color_i_vec = _mm512_loadu_si512((__m512i *)&color[i]); + + // # 32 MULTs, 16 ADDs __m512 w_norm_sq = _mm512_fmadd_ps(w_x_i_vec, w_x_i_vec, _mm512_mul_ps(w_y_i_vec, w_y_i_vec)); + + // # 16 SQRTs __m512 w_norm = _mm512_sqrt_ps(w_norm_sq); __m512 w_norm_safe = _mm512_max_ps(w_norm, _mm512_set1_ps(EPS)); + + // # 16 DIVs __m512 correction_factor = _mm512_min_ps(_mm512_set1_ps(1.0f), _mm512_div_ps(v_max_i_vec, w_norm_safe)); + // # 32 MULTs __m512 curr_vel_x_i_vec = _mm512_mul_ps(w_x_i_vec, correction_factor); __m512 curr_vel_y_i_vec = _mm512_mul_ps(w_y_i_vec, correction_factor); __m512 dt_vec = _mm512_set1_ps(dt); + + // # 32 MULTs, 32 ADDs pos_x_i_vec = _mm512_fmadd_ps(curr_vel_x_i_vec, dt_vec, pos_x_i_vec); pos_y_i_vec = _mm512_fmadd_ps(curr_vel_y_i_vec, dt_vec, pos_y_i_vec); __mmask16 cond_1_left = _mm512_cmpeq_epi32_mask(color_i_vec, _mm512_setzero_si512()); - __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.5f), _CMP_GT_OQ); + __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); __mmask16 cond_1 = cond_1_left & cond_1_right; pos_x_i_vec = _mm512_mask_blend_ps(cond_1, pos_x_i_vec, _mm512_set1_ps(1.0f)); @@ -389,7 +502,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __mmask16 cond_2_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(1.5f), _CMP_LT_OQ); __mmask16 cond_2 = cond_2_left & cond_2_right; - pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.0f)); + pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE)); destination_y_i_vec = _mm512_mask_blend_ps(cond_2, destination_y_i_vec, pos_y_i_vec); __m512 neg_desired_v0 = _mm512_xor_ps(desired_v0_i_vec, _mm512_set1_ps(-0.0f)); w_x_i_vec = _mm512_mask_blend_ps(cond_2, w_x_i_vec, neg_desired_v0); @@ -400,16 +513,30 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i_vec = _mm512_max_ps(pos_y_i_vec, _mm512_set1_ps(WALL_EPS)); pos_y_i_vec = _mm512_min_ps(pos_y_i_vec, _mm512_set1_ps(BOARD_WIDTH_MINUS_WALL_EPS)); + // # 32 ADDs __m512 ex_vec = _mm512_sub_ps(destination_x_i_vec, pos_x_i_vec); __m512 ey_vec = _mm512_sub_ps(destination_y_i_vec, pos_y_i_vec); + + // # 32 MULTs, 16 ADDs __m512 e_norm_sq = _mm512_fmadd_ps(ex_vec, ex_vec, _mm512_mul_ps(ey_vec, ey_vec)); + + // # 16 SQRTs __m512 e_norm = _mm512_sqrt_ps(e_norm_sq); + + // # 16 DIVs __m512 e_norm_inv = _mm512_div_ps(_mm512_set1_ps(1.0f), e_norm); + + // # 32 MULTs __m512 e_x_i_vec = _mm512_mul_ps(ex_vec, e_norm_inv); __m512 e_y_i_vec = _mm512_mul_ps(ey_vec, e_norm_inv); + // # 32 MULTs, 16 ADDs __m512 vel_norm_sq = _mm512_fmadd_ps(curr_vel_x_i_vec, curr_vel_x_i_vec, _mm512_mul_ps(curr_vel_y_i_vec, curr_vel_y_i_vec)); + + // # 16 MULTs __m512 sb_sq_i_vec = _mm512_mul_ps(vel_norm_sq, _mm512_set1_ps(delta_t_sq)); + + // # 16 SQRTs __m512 vel_norm_dt = _mm512_sqrt_ps(sb_sq_i_vec); _mm512_storeu_ps(&curr_vel_x[i], curr_vel_x_i_vec); @@ -422,6 +549,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c _mm512_storeu_ps(&e_y[i], e_y_i_vec); _mm512_storeu_ps(&destination_y[i], destination_y_i_vec); _mm512_storeu_ps(&sb_sq[i], sb_sq_i_vec); + + // # 32 MULTs _mm512_storeu_ps(&sb_e_x[i], _mm512_mul_ps(e_x_i_vec, vel_norm_dt)); _mm512_storeu_ps(&sb_e_y[i], _mm512_mul_ps(e_y_i_vec, vel_norm_dt)); } diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.cpp index 83d3c57..3bcba7a 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0f #define BOARD_LENGTH 50.0f +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f #define WALL_EPS 1e-3f #define BOARD_WIDTH_MINUS_WALL_EPS 9.999f @@ -160,21 +162,46 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 256 MULTs, 224 ADDs static inline __m512 _avx512_exp_ps(__m512 x) { + const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); + + // # 16 MULTs __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); + + // # 16 MULTs, 16 ADDs __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - const __m512 c5 = _mm512_set1_ps(1.0f / 120.0f); - const __m512 c4 = _mm512_set1_ps(1.0f / 24.0f); - const __m512 c3 = _mm512_set1_ps(1.0f / 6.0f); - const __m512 c2 = _mm512_set1_ps(0.5f); - const __m512 c1 = _mm512_set1_ps(1.0f); - const __m512 c0 = _mm512_set1_ps(1.0f); + const __m512 c13 = _mm512_set1_ps(c13_scalar); + const __m512 c12 = _mm512_set1_ps(c12_scalar); + const __m512 c11 = _mm512_set1_ps(c11_scalar); + const __m512 c10 = _mm512_set1_ps(c10_scalar); + const __m512 c9 = _mm512_set1_ps(c9_scalar); + const __m512 c8 = _mm512_set1_ps(c8_scalar); + const __m512 c7 = _mm512_set1_ps(c7_scalar); + const __m512 c6 = _mm512_set1_ps(c6_scalar); + const __m512 c5 = _mm512_set1_ps(c5_scalar); + const __m512 c4 = _mm512_set1_ps(c4_scalar); + const __m512 c3 = _mm512_set1_ps(c3_scalar); + const __m512 c2 = _mm512_set1_ps(c2_scalar); + const __m512 c1 = _mm512_set1_ps(c1_scalar); + const __m512 c0 = _mm512_set1_ps(c0_scalar); - __m512 p = c5; + __m512 p = c13; + + // # 13 * (16 MULTs, 16 ADDs) + // # = 208 MULTs, 208 ADDs + p = _mm512_fmadd_ps(p, r, c12); + p = _mm512_fmadd_ps(p, r, c11); + p = _mm512_fmadd_ps(p, r, c10); + p = _mm512_fmadd_ps(p, r, c9); + p = _mm512_fmadd_ps(p, r, c8); + p = _mm512_fmadd_ps(p, r, c7); + p = _mm512_fmadd_ps(p, r, c6); + p = _mm512_fmadd_ps(p, r, c5); p = _mm512_fmadd_ps(p, r, c4); p = _mm512_fmadd_ps(p, r, c3); p = _mm512_fmadd_ps(p, r, c2); @@ -186,6 +213,7 @@ static inline __m512 _avx512_exp_ps(__m512 x) { __m512i scaled = _mm512_slli_epi32(biased, 23); __m512 pow2n = _mm512_castsi512_ps(scaled); + // # 16 MULTs return _mm512_mul_ps(p, pow2n); } @@ -224,11 +252,42 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512 v_np = _mm512_set1_ps((float)n_p); const __m512 v_exp_min = _mm512_set1_ps(-87.0f); + // # ADDs: n_iter * ( + // # n_p * 68 + + // # n_p * n_p_pad * 29 + + // # n_p * board.n_obstacles * 10 + + // # n_p_pad * 7 + // # ) + + // # MULTs: n_iter * ( + // # n_p * 4 + + // # n_p * n_p_pad * 40 + + // # n_p * board.n_obstacles * 13 + + // # n_p_pad * 15 + // # ) + + // # SQRTs: n_iter * ( + // # n_p * n_p_pad * 3 + + // # n_p * board.n_obstacles + + // # n_p_pad * 3 + // # ) + + // # DIVs: n_iter * ( + // # n_p * n_p_pad + + // # n_p * board.n_obstacles + + // # n_p_pad * 2 + // # ) + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + + // # 2 MULTs float desired_velocity_x = e_x[i] * desired_v0[i]; float desired_velocity_y = e_y[i] * desired_v0[i]; + // # 2 ADDs, 2 MULTs float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; @@ -244,6 +303,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 acc_y_1 = _mm512_setzero_ps(); for (int j = 0; j < n_p_pad; j += 32) { + + // # 32 ADDs __m512 j_idx_0 = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); __m512 j_idx_1 = _mm512_add_ps(_mm512_set1_ps((float)j + 16), lane_off); @@ -259,11 +320,9 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 pjx_0 = _mm512_loadu_ps(&pos_x[j]); __m512 pjx_1 = _mm512_loadu_ps(&pos_x[j + 16]); - __m512 pjy_0 = _mm512_loadu_ps(&pos_y[j]); __m512 pjy_1 = _mm512_loadu_ps(&pos_y[j + 16]); - __m512 sbe_jx_0 = _mm512_loadu_ps(&sb_e_x[j]); __m512 sbe_jx_1 = _mm512_loadu_ps(&sb_e_x[j + 16]); @@ -273,81 +332,105 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 sb_jsq_0 = _mm512_loadu_ps(&sb_sq[j]); __m512 sb_jsq_1 = _mm512_loadu_ps(&sb_sq[j + 16]); + // # 32 ADDs __m512 r_ab_x_0 = _mm512_sub_ps(pix, pjx_0); __m512 r_ab_x_1 = _mm512_sub_ps(pix, pjx_1); + // # 32 ADDs __m512 r_ab_y_0 = _mm512_sub_ps(piy, pjy_0); __m512 r_ab_y_1 = _mm512_sub_ps(piy, pjy_1); + // # 64 MULTs, 32 ADDs __m512 r_ab_n2_0 = _mm512_fmadd_ps(r_ab_x_0, r_ab_x_0, _mm512_mul_ps(r_ab_y_0, r_ab_y_0)); __m512 r_ab_n2_1 = _mm512_fmadd_ps(r_ab_x_1, r_ab_x_1, _mm512_mul_ps(r_ab_y_1, r_ab_y_1)); + // # 32 SQRTs __m512 r_ab_n_0 = _mm512_sqrt_ps(r_ab_n2_0); __m512 r_ab_n_1 = _mm512_sqrt_ps(r_ab_n2_1); + // # 32 ADDs __m512 r_ab2_x_0 = _mm512_sub_ps(r_ab_x_0, sbe_jx_0); __m512 r_ab2_x_1 = _mm512_sub_ps(r_ab_x_1, sbe_jx_1); + // # 32 ADDs __m512 r_ab2_y_0 = _mm512_sub_ps(r_ab_y_0, sbe_jy_0); __m512 r_ab2_y_1 = _mm512_sub_ps(r_ab_y_1, sbe_jy_1); + // # 64 MULTs, 32 ADDs __m512 r_ab2_n2_0 = _mm512_fmadd_ps(r_ab2_x_0, r_ab2_x_0, _mm512_mul_ps(r_ab2_y_0, r_ab2_y_0)); __m512 r_ab2_n2_1 = _mm512_fmadd_ps(r_ab2_x_1, r_ab2_x_1, _mm512_mul_ps(r_ab2_y_1, r_ab2_y_1)); + // # 32 SQRTs __m512 r_ab2_n_0 = _mm512_sqrt_ps(r_ab2_n2_0); __m512 r_ab2_n_1 = _mm512_sqrt_ps(r_ab2_n2_1); + // # 32 ADDs __m512 r_sum_0 = _mm512_add_ps(r_ab_n_0, r_ab2_n_0); __m512 r_sum_1 = _mm512_add_ps(r_ab_n_1, r_ab2_n_1); + // # 32 MULTs, 32 ADDs __m512 two_b_sq_0 = _mm512_fmsub_ps(r_sum_0, r_sum_0, sb_jsq_0); __m512 two_b_sq_1 = _mm512_fmsub_ps(r_sum_1, r_sum_1, sb_jsq_1); + // # 32 SQRTs __m512 two_b_0 = _mm512_sqrt_ps(two_b_sq_0); __m512 two_b_1 = _mm512_sqrt_ps(two_b_sq_1); + // # 32 MULTs __m512 e_arg_0 = _mm512_mul_ps(two_b_0, v_neg_2s_inv); __m512 e_arg_1 = _mm512_mul_ps(two_b_1, v_neg_2s_inv); e_arg_0 = _mm512_max_ps(e_arg_0, v_exp_min); e_arg_1 = _mm512_max_ps(e_arg_1, v_exp_min); + // # 512 MULTs, 448 ADDs __m512 e_val_0 = _avx512_exp_ps(e_arg_0); __m512 e_val_1 = _avx512_exp_ps(e_arg_1); + // # 64 MULTs __m512 numer_0 = _mm512_mul_ps(_mm512_mul_ps(r_sum_0, v_neg_v0_div_2s), e_val_0); __m512 numer_1 = _mm512_mul_ps(_mm512_mul_ps(r_sum_1, v_neg_v0_div_2s), e_val_1); + // # 64 MULTs __m512 denom_0 = _mm512_mul_ps(_mm512_mul_ps(two_b_0, r_ab_n_0), r_ab2_n_0); __m512 denom_1 = _mm512_mul_ps(_mm512_mul_ps(two_b_1, r_ab_n_1), r_ab2_n_1); __m512 denom_safe_0 = _mm512_mask_blend_ps(active_0, v_one, denom_0); __m512 denom_safe_1 = _mm512_mask_blend_ps(active_1, v_one, denom_1); + // # 32 DIVs __m512 smc_0 = _mm512_div_ps(numer_0, denom_safe_0); __m512 smc_1 = _mm512_div_ps(numer_1, denom_safe_1); + // # 32 MULTs __m512 sm_r_ab_0 = _mm512_mul_ps(smc_0, r_ab2_n_0); __m512 sm_r_ab_1 = _mm512_mul_ps(smc_1, r_ab2_n_1); + // # 32 MULTs __m512 sm_r_ab2_0 = _mm512_mul_ps(smc_0, r_ab_n_0); __m512 sm_r_ab2_1 = _mm512_mul_ps(smc_1, r_ab_n_1); + // # 64 MULTs, 32 ADDs __m512 f_x_0 = _mm512_fmadd_ps(r_ab2_x_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_x_0, sm_r_ab_0)); __m512 f_x_1 = _mm512_fmadd_ps(r_ab2_x_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_x_1, sm_r_ab_1)); + // # 64 MULTs, 32 ADDs __m512 f_y_0 = _mm512_fmadd_ps(r_ab2_y_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_y_0, sm_r_ab_0)); __m512 f_y_1 = _mm512_fmadd_ps(r_ab2_y_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_y_1, sm_r_ab_1)); + // # 64 MULTs, 32 ADDs __m512 f_norm_sq_0 = _mm512_fmadd_ps(f_x_0, f_x_0, _mm512_mul_ps(f_y_0, f_y_0)); __m512 f_norm_sq_1 = _mm512_fmadd_ps(f_x_1, f_x_1, _mm512_mul_ps(f_y_1, f_y_1)); + // # 32 MULTs __m512 rhs_0 = _mm512_mul_ps(f_norm_sq_0, v_cos_phi_sq); __m512 rhs_1 = _mm512_mul_ps(f_norm_sq_1, v_cos_phi_sq); + // # 64 MULTs, 32 ADDs __m512 e_dot_f_0 = _mm512_fmadd_ps(eix, f_x_0, _mm512_mul_ps(eiy, f_y_0)); __m512 e_dot_f_1 = _mm512_fmadd_ps(eix, f_x_1, _mm512_mul_ps(eiy, f_y_1)); + // # 32 MULTs __m512 e_dot_f_sq_0 = _mm512_mul_ps(e_dot_f_0, e_dot_f_0); __m512 e_dot_f_sq_1 = _mm512_mul_ps(e_dot_f_1, e_dot_f_1); @@ -369,22 +452,28 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 f_y_safe_0 = _mm512_maskz_mov_ps(active_0, f_y_0); __m512 f_y_safe_1 = _mm512_maskz_mov_ps(active_1, f_y_1); + // # 32 MULTs, 32 ADDs acc_x_0 = _mm512_fmadd_ps(f_x_safe_0, w_val_0, acc_x_0); acc_x_1 = _mm512_fmadd_ps(f_x_safe_1, w_val_1, acc_x_1); + // # 32 MULTs, 32 ADDs acc_y_0 = _mm512_fmadd_ps(f_y_safe_0, w_val_0, acc_y_0); acc_y_1 = _mm512_fmadd_ps(f_y_safe_1, w_val_1, acc_y_1); } + // # 64 ADDs f_alpha_dt_x += _mm512_reduce_add_ps(_mm512_add_ps(acc_x_0, acc_x_1)); f_alpha_dt_y += _mm512_reduce_add_ps(_mm512_add_ps(acc_y_0, acc_y_1)); for (int k = 0; k < board.n_obstacles; k++) { + + // # 2 ADDs float v1_x = pos_x[i] - board.obstacles[k].start_x; float v1_y = pos_y[i] - board.obstacles[k].start_y; float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; + // # 3 MULTs, 1 ADD float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0f) @@ -392,12 +481,15 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0f) projection = 1.0f; + // # 4 ADDs, 2 MULTs float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; + // # 1 EXP, 4 MULTs, 1 DIV float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; float f_alpha_B_neg_x = r_aB_x * scalar_mult; float f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -408,19 +500,22 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTs float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + // # 2 ADDs f_alpha_dt_x += f_alpha_B_dt_x; f_alpha_dt_y += f_alpha_B_dt_y; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } for (int i = 0; i < n_p_pad; i += 16) { - + __m512 w_x_i_vec = _mm512_loadu_ps(&w_x[i]); __m512 w_y_i_vec = _mm512_loadu_ps(&w_y[i]); __m512 v_max_i_vec = _mm512_loadu_ps(&v_max[i]); @@ -429,22 +524,30 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 destination_y_i_vec = _mm512_loadu_ps(&destination_y[i]); __m512 destination_x_i_vec = _mm512_loadu_ps(&destination_x[i]); __m512 desired_v0_i_vec = _mm512_loadu_ps(&desired_v0[i]); - - __m512i color_i_vec = _mm512_loadu_si512((__m512i*)&color[i]); + __m512i color_i_vec = _mm512_loadu_si512((__m512i *)&color[i]); + + // # 32 MULTs, 16 ADDs __m512 w_norm_sq = _mm512_fmadd_ps(w_x_i_vec, w_x_i_vec, _mm512_mul_ps(w_y_i_vec, w_y_i_vec)); + + // # 16 SQRTs __m512 w_norm = _mm512_sqrt_ps(w_norm_sq); + + // # 16 DIVs __m512 correction_factor = _mm512_min_ps(_mm512_set1_ps(1.0f), _mm512_div_ps(v_max_i_vec, w_norm)); + // # 32 MULTs __m512 curr_vel_x_i_vec = _mm512_mul_ps(w_x_i_vec, correction_factor); __m512 curr_vel_y_i_vec = _mm512_mul_ps(w_y_i_vec, correction_factor); __m512 dt_vec = _mm512_set1_ps(dt); + + // # 32 MULTs, 32 ADDs pos_x_i_vec = _mm512_fmadd_ps(curr_vel_x_i_vec, dt_vec, pos_x_i_vec); pos_y_i_vec = _mm512_fmadd_ps(curr_vel_y_i_vec, dt_vec, pos_y_i_vec); __mmask16 cond_1_left = _mm512_cmpeq_epi32_mask(color_i_vec, _mm512_setzero_si512()); - __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.5f), _CMP_GT_OQ); + __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); __mmask16 cond_1 = cond_1_left & cond_1_right; pos_x_i_vec = _mm512_mask_blend_ps(cond_1, pos_x_i_vec, _mm512_set1_ps(1.0f)); @@ -458,7 +561,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __mmask16 cond_2_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(1.5f), _CMP_LT_OQ); __mmask16 cond_2 = cond_2_left & cond_2_right; - pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.0f)); + pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE)); destination_y_i_vec = _mm512_mask_blend_ps(cond_2, destination_y_i_vec, pos_y_i_vec); __m512 neg_desired_v0 = _mm512_xor_ps(desired_v0_i_vec, _mm512_set1_ps(-0.0f)); w_x_i_vec = _mm512_mask_blend_ps(cond_2, w_x_i_vec, neg_desired_v0); @@ -469,16 +572,30 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i_vec = _mm512_max_ps(pos_y_i_vec, _mm512_set1_ps(WALL_EPS)); pos_y_i_vec = _mm512_min_ps(pos_y_i_vec, _mm512_set1_ps(BOARD_WIDTH_MINUS_WALL_EPS)); + // # 32 ADDs __m512 ex_vec = _mm512_sub_ps(destination_x_i_vec, pos_x_i_vec); __m512 ey_vec = _mm512_sub_ps(destination_y_i_vec, pos_y_i_vec); + + // # 32 MULTs, 16 ADDs __m512 e_norm_sq = _mm512_fmadd_ps(ex_vec, ex_vec, _mm512_mul_ps(ey_vec, ey_vec)); + + // # 16 SQRTs __m512 e_norm = _mm512_sqrt_ps(e_norm_sq); + + // # 16 DIVs __m512 e_norm_inv = _mm512_div_ps(_mm512_set1_ps(1.0f), e_norm); + + // # 32 MULTs __m512 e_x_i_vec = _mm512_mul_ps(ex_vec, e_norm_inv); __m512 e_y_i_vec = _mm512_mul_ps(ey_vec, e_norm_inv); + // # 32 MULTs, 16 ADDs __m512 vel_norm_sq = _mm512_fmadd_ps(curr_vel_x_i_vec, curr_vel_x_i_vec, _mm512_mul_ps(curr_vel_y_i_vec, curr_vel_y_i_vec)); + + // # 16 MULTs __m512 sb_sq_i_vec = _mm512_mul_ps(vel_norm_sq, _mm512_set1_ps(delta_t_sq)); + + // # 16 SQRTs __m512 vel_norm_dt = _mm512_sqrt_ps(sb_sq_i_vec); _mm512_storeu_ps(&curr_vel_x[i], curr_vel_x_i_vec); @@ -491,11 +608,12 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c _mm512_storeu_ps(&e_y[i], e_y_i_vec); _mm512_storeu_ps(&destination_y[i], destination_y_i_vec); _mm512_storeu_ps(&sb_sq[i], sb_sq_i_vec); + + // # 32 MULTs _mm512_storeu_ps(&sb_e_x[i], _mm512_mul_ps(e_x_i_vec, vel_norm_dt)); _mm512_storeu_ps(&sb_e_y[i], _mm512_mul_ps(e_y_i_vec, vel_norm_dt)); } - #if !BENCHMARK for (int i = 0; i < n_p; i++) { if (dump_path && (t % dump_interval == 0)) { diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.cpp index 80d9b8f..25a468c 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_moreilp.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0f #define BOARD_LENGTH 50.0f +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f #define WALL_EPS 1e-3f #define BOARD_WIDTH_MINUS_WALL_EPS 9.999f @@ -160,21 +162,46 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 256 MULTs, 224 ADDs static inline __m512 _avx512_exp_ps(__m512 x) { + const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); + + // # 16 MULTs __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); + + // # 16 MULTs, 16 ADDs __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - const __m512 c5 = _mm512_set1_ps(1.0f / 120.0f); - const __m512 c4 = _mm512_set1_ps(1.0f / 24.0f); - const __m512 c3 = _mm512_set1_ps(1.0f / 6.0f); - const __m512 c2 = _mm512_set1_ps(0.5f); - const __m512 c1 = _mm512_set1_ps(1.0f); - const __m512 c0 = _mm512_set1_ps(1.0f); + const __m512 c13 = _mm512_set1_ps(c13_scalar); + const __m512 c12 = _mm512_set1_ps(c12_scalar); + const __m512 c11 = _mm512_set1_ps(c11_scalar); + const __m512 c10 = _mm512_set1_ps(c10_scalar); + const __m512 c9 = _mm512_set1_ps(c9_scalar); + const __m512 c8 = _mm512_set1_ps(c8_scalar); + const __m512 c7 = _mm512_set1_ps(c7_scalar); + const __m512 c6 = _mm512_set1_ps(c6_scalar); + const __m512 c5 = _mm512_set1_ps(c5_scalar); + const __m512 c4 = _mm512_set1_ps(c4_scalar); + const __m512 c3 = _mm512_set1_ps(c3_scalar); + const __m512 c2 = _mm512_set1_ps(c2_scalar); + const __m512 c1 = _mm512_set1_ps(c1_scalar); + const __m512 c0 = _mm512_set1_ps(c0_scalar); - __m512 p = c5; + __m512 p = c13; + + // # 13 * (16 MULTs, 16 ADDs) + // # = 208 MULTs, 208 ADDs + p = _mm512_fmadd_ps(p, r, c12); + p = _mm512_fmadd_ps(p, r, c11); + p = _mm512_fmadd_ps(p, r, c10); + p = _mm512_fmadd_ps(p, r, c9); + p = _mm512_fmadd_ps(p, r, c8); + p = _mm512_fmadd_ps(p, r, c7); + p = _mm512_fmadd_ps(p, r, c6); + p = _mm512_fmadd_ps(p, r, c5); p = _mm512_fmadd_ps(p, r, c4); p = _mm512_fmadd_ps(p, r, c3); p = _mm512_fmadd_ps(p, r, c2); @@ -186,6 +213,7 @@ static inline __m512 _avx512_exp_ps(__m512 x) { __m512i scaled = _mm512_slli_epi32(biased, 23); __m512 pow2n = _mm512_castsi512_ps(scaled); + // # 16 MULTs return _mm512_mul_ps(p, pow2n); } @@ -224,11 +252,42 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512 v_np = _mm512_set1_ps((float)n_p); const __m512 v_exp_min = _mm512_set1_ps(-87.0f); + // # ADDs: n_iter * ( + // # n_p * 68 + + // # n_p * n_p_pad * 29 + + // # n_p * board.n_obstacles * 10 + + // # n_p_pad * 7 + // # ) + + // # MULTs: n_iter * ( + // # n_p * 4 + + // # n_p * n_p_pad * 40 + + // # n_p * board.n_obstacles * 13 + + // # n_p_pad * 15 + // # ) + + // # DIVs: n_iter * ( + // # n_p * n_p_pad + + // # n_p * board.n_obstacles + + // # n_p_pad * 2 + // # ) + + // # SQRTs: n_iter * ( + // # n_p * n_p_pad * 3 + + // # n_p * board.n_obstacles + + // # n_p_pad * 3 + // # ) + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + + // # 2 MULTs float desired_velocity_x = e_x[i] * desired_v0[i]; float desired_velocity_y = e_y[i] * desired_v0[i]; + // # 2 ADDs, 2 MULTs float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; @@ -244,6 +303,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 acc_y_1 = _mm512_setzero_ps(); for (int j = 0; j < n_p_pad; j += 32) { + + // # 32 ADDs __m512 j_idx_0 = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); __m512 j_idx_1 = _mm512_add_ps(_mm512_set1_ps((float)j + 16), lane_off); @@ -271,81 +332,105 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 sb_jsq_0 = _mm512_loadu_ps(&sb_sq[j]); __m512 sb_jsq_1 = _mm512_loadu_ps(&sb_sq[j + 16]); + // # 32 ADDs __m512 r_ab_x_0 = _mm512_sub_ps(pix, pjx_0); __m512 r_ab_x_1 = _mm512_sub_ps(pix, pjx_1); + // # 32 ADDs __m512 r_ab_y_0 = _mm512_sub_ps(piy, pjy_0); __m512 r_ab_y_1 = _mm512_sub_ps(piy, pjy_1); + // # 64 MULTs, 32 ADDs __m512 r_ab_n2_0 = _mm512_fmadd_ps(r_ab_x_0, r_ab_x_0, _mm512_mul_ps(r_ab_y_0, r_ab_y_0)); __m512 r_ab_n2_1 = _mm512_fmadd_ps(r_ab_x_1, r_ab_x_1, _mm512_mul_ps(r_ab_y_1, r_ab_y_1)); + // # 32 SQRTs __m512 r_ab_n_0 = _mm512_sqrt_ps(r_ab_n2_0); __m512 r_ab_n_1 = _mm512_sqrt_ps(r_ab_n2_1); + // # 32 ADDs __m512 r_ab2_x_0 = _mm512_sub_ps(r_ab_x_0, sbe_jx_0); __m512 r_ab2_x_1 = _mm512_sub_ps(r_ab_x_1, sbe_jx_1); + // # 32 ADDs __m512 r_ab2_y_0 = _mm512_sub_ps(r_ab_y_0, sbe_jy_0); __m512 r_ab2_y_1 = _mm512_sub_ps(r_ab_y_1, sbe_jy_1); + // # 64 MULTs, 32 ADDs __m512 r_ab2_n2_0 = _mm512_fmadd_ps(r_ab2_x_0, r_ab2_x_0, _mm512_mul_ps(r_ab2_y_0, r_ab2_y_0)); __m512 r_ab2_n2_1 = _mm512_fmadd_ps(r_ab2_x_1, r_ab2_x_1, _mm512_mul_ps(r_ab2_y_1, r_ab2_y_1)); + // # 32 SQRTs __m512 r_ab2_n_0 = _mm512_sqrt_ps(r_ab2_n2_0); __m512 r_ab2_n_1 = _mm512_sqrt_ps(r_ab2_n2_1); + // # 32 ADDs __m512 r_sum_0 = _mm512_add_ps(r_ab_n_0, r_ab2_n_0); __m512 r_sum_1 = _mm512_add_ps(r_ab_n_1, r_ab2_n_1); + // # 32 MULTs, 32 ADDs __m512 two_b_sq_0 = _mm512_fmsub_ps(r_sum_0, r_sum_0, sb_jsq_0); __m512 two_b_sq_1 = _mm512_fmsub_ps(r_sum_1, r_sum_1, sb_jsq_1); + // # 32 SQRTs __m512 two_b_0 = _mm512_sqrt_ps(two_b_sq_0); __m512 two_b_1 = _mm512_sqrt_ps(two_b_sq_1); + // # 32 MULTs __m512 e_arg_0 = _mm512_mul_ps(two_b_0, v_neg_2s_inv); __m512 e_arg_1 = _mm512_mul_ps(two_b_1, v_neg_2s_inv); e_arg_0 = _mm512_max_ps(e_arg_0, v_exp_min); e_arg_1 = _mm512_max_ps(e_arg_1, v_exp_min); + // # 512 MULTs, 448 ADDs __m512 e_val_0 = _avx512_exp_ps(e_arg_0); __m512 e_val_1 = _avx512_exp_ps(e_arg_1); + // # 64 MULTs __m512 numer_0 = _mm512_mul_ps(_mm512_mul_ps(r_sum_0, v_neg_v0_div_2s), e_val_0); __m512 numer_1 = _mm512_mul_ps(_mm512_mul_ps(r_sum_1, v_neg_v0_div_2s), e_val_1); + // # 64 MULTs __m512 denom_0 = _mm512_mul_ps(_mm512_mul_ps(two_b_0, r_ab_n_0), r_ab2_n_0); __m512 denom_1 = _mm512_mul_ps(_mm512_mul_ps(two_b_1, r_ab_n_1), r_ab2_n_1); __m512 denom_safe_0 = _mm512_mask_blend_ps(active_0, v_one, denom_0); __m512 denom_safe_1 = _mm512_mask_blend_ps(active_1, v_one, denom_1); + // # 32 DIVs __m512 smc_0 = _mm512_div_ps(numer_0, denom_safe_0); __m512 smc_1 = _mm512_div_ps(numer_1, denom_safe_1); + // # 32 MULTs __m512 sm_r_ab_0 = _mm512_mul_ps(smc_0, r_ab2_n_0); __m512 sm_r_ab_1 = _mm512_mul_ps(smc_1, r_ab2_n_1); + // # 32 MULTs __m512 sm_r_ab2_0 = _mm512_mul_ps(smc_0, r_ab_n_0); __m512 sm_r_ab2_1 = _mm512_mul_ps(smc_1, r_ab_n_1); + // # 64 MULTs, 32 ADDs __m512 f_x_0 = _mm512_fmadd_ps(r_ab2_x_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_x_0, sm_r_ab_0)); __m512 f_x_1 = _mm512_fmadd_ps(r_ab2_x_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_x_1, sm_r_ab_1)); + // # 64 MULTs, 32 ADDs __m512 f_y_0 = _mm512_fmadd_ps(r_ab2_y_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_y_0, sm_r_ab_0)); __m512 f_y_1 = _mm512_fmadd_ps(r_ab2_y_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_y_1, sm_r_ab_1)); + // # 64 MULTs, 32 ADDs __m512 f_norm_sq_0 = _mm512_fmadd_ps(f_x_0, f_x_0, _mm512_mul_ps(f_y_0, f_y_0)); __m512 f_norm_sq_1 = _mm512_fmadd_ps(f_x_1, f_x_1, _mm512_mul_ps(f_y_1, f_y_1)); + // # 32 MULTs __m512 rhs_0 = _mm512_mul_ps(f_norm_sq_0, v_cos_phi_sq); __m512 rhs_1 = _mm512_mul_ps(f_norm_sq_1, v_cos_phi_sq); + // # 64 MULTs, 32 ADDs __m512 e_dot_f_0 = _mm512_fmadd_ps(eix, f_x_0, _mm512_mul_ps(eiy, f_y_0)); __m512 e_dot_f_1 = _mm512_fmadd_ps(eix, f_x_1, _mm512_mul_ps(eiy, f_y_1)); + // # 32 MULTs __m512 e_dot_f_sq_0 = _mm512_mul_ps(e_dot_f_0, e_dot_f_0); __m512 e_dot_f_sq_1 = _mm512_mul_ps(e_dot_f_1, e_dot_f_1); @@ -367,22 +452,28 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 f_y_safe_0 = _mm512_maskz_mov_ps(active_0, f_y_0); __m512 f_y_safe_1 = _mm512_maskz_mov_ps(active_1, f_y_1); + // # 32 MULTs, 32 ADDs acc_x_0 = _mm512_fmadd_ps(f_x_safe_0, w_val_0, acc_x_0); acc_x_1 = _mm512_fmadd_ps(f_x_safe_1, w_val_1, acc_x_1); + // # 32 MULTs, 32 ADDs acc_y_0 = _mm512_fmadd_ps(f_y_safe_0, w_val_0, acc_y_0); acc_y_1 = _mm512_fmadd_ps(f_y_safe_1, w_val_1, acc_y_1); } + // # 64 ADDs f_alpha_dt_x += _mm512_reduce_add_ps(_mm512_add_ps(acc_x_0, acc_x_1)); f_alpha_dt_y += _mm512_reduce_add_ps(_mm512_add_ps(acc_y_0, acc_y_1)); for (int k = 0; k < board.n_obstacles; k++) { + + // # 2 ADDs float v1_x = pos_x[i] - board.obstacles[k].start_x; float v1_y = pos_y[i] - board.obstacles[k].start_y; float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; + // # 3 MULTs, 1 ADD float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0f) @@ -390,12 +481,15 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0f) projection = 1.0f; + // # 4 ADDs, 2 MULTs float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; + // # 4 MULTs, 1 EXP, 1 DIV float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; float f_alpha_B_neg_x = r_aB_x * scalar_mult; float f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -406,13 +500,16 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTS float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + // # 2 ADDs f_alpha_dt_x += f_alpha_B_dt_x; f_alpha_dt_y += f_alpha_B_dt_y; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } @@ -446,34 +543,41 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512i color_i_vec_0 = _mm512_loadu_si512((__m512i *)&color[i]); __m512i color_i_vec_1 = _mm512_loadu_si512((__m512i *)&color[i + 16]); + // # 64 MULTs, 32 ADDs __m512 w_norm_sq_0 = _mm512_fmadd_ps(w_x_i_vec_0, w_x_i_vec_0, _mm512_mul_ps(w_y_i_vec_0, w_y_i_vec_0)); __m512 w_norm_sq_1 = _mm512_fmadd_ps(w_x_i_vec_1, w_x_i_vec_1, _mm512_mul_ps(w_y_i_vec_1, w_y_i_vec_1)); + // # 32 SQRTs __m512 w_norm_0 = _mm512_sqrt_ps(w_norm_sq_0); __m512 w_norm_1 = _mm512_sqrt_ps(w_norm_sq_1); + // # 32 DIVs __m512 correction_factor_0 = _mm512_min_ps(_mm512_set1_ps(1.0f), _mm512_div_ps(v_max_i_vec_0, w_norm_0)); __m512 correction_factor_1 = _mm512_min_ps(_mm512_set1_ps(1.0f), _mm512_div_ps(v_max_i_vec_1, w_norm_1)); + // # 32 MULTs __m512 curr_vel_x_i_vec_0 = _mm512_mul_ps(w_x_i_vec_0, correction_factor_0); __m512 curr_vel_x_i_vec_1 = _mm512_mul_ps(w_x_i_vec_1, correction_factor_1); + // # 32 MULTs __m512 curr_vel_y_i_vec_0 = _mm512_mul_ps(w_y_i_vec_0, correction_factor_0); __m512 curr_vel_y_i_vec_1 = _mm512_mul_ps(w_y_i_vec_1, correction_factor_1); __m512 dt_vec = _mm512_set1_ps(dt); + // # 32 MULTs, 32 ADDs pos_x_i_vec_0 = _mm512_fmadd_ps(curr_vel_x_i_vec_0, dt_vec, pos_x_i_vec_0); pos_x_i_vec_1 = _mm512_fmadd_ps(curr_vel_x_i_vec_1, dt_vec, pos_x_i_vec_1); + // # 32 MULTs, 32 ADDs pos_y_i_vec_0 = _mm512_fmadd_ps(curr_vel_y_i_vec_0, dt_vec, pos_y_i_vec_0); pos_y_i_vec_1 = _mm512_fmadd_ps(curr_vel_y_i_vec_1, dt_vec, pos_y_i_vec_1); __mmask16 cond_1_left_0 = _mm512_cmpeq_epi32_mask(color_i_vec_0, _mm512_setzero_si512()); __mmask16 cond_1_left_1 = _mm512_cmpeq_epi32_mask(color_i_vec_1, _mm512_setzero_si512()); - __mmask16 cond_1_right_0 = _mm512_cmp_ps_mask(pos_x_i_vec_0, _mm512_set1_ps(BOARD_LENGTH - 1.5f), _CMP_GT_OQ); - __mmask16 cond_1_right_1 = _mm512_cmp_ps_mask(pos_x_i_vec_1, _mm512_set1_ps(BOARD_LENGTH - 1.5f), _CMP_GT_OQ); + __mmask16 cond_1_right_0 = _mm512_cmp_ps_mask(pos_x_i_vec_0, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); + __mmask16 cond_1_right_1 = _mm512_cmp_ps_mask(pos_x_i_vec_1, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); __mmask16 cond_1_0 = cond_1_left_0 & cond_1_right_0; __mmask16 cond_1_1 = cond_1_left_1 & cond_1_right_1; @@ -529,33 +633,43 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i_vec_0 = _mm512_min_ps(pos_y_i_vec_0, _mm512_set1_ps(BOARD_WIDTH_MINUS_WALL_EPS)); pos_y_i_vec_1 = _mm512_min_ps(pos_y_i_vec_1, _mm512_set1_ps(BOARD_WIDTH_MINUS_WALL_EPS)); + // # 32 ADDs __m512 ex_vec_0 = _mm512_sub_ps(destination_x_i_vec_0, pos_x_i_vec_0); __m512 ex_vec_1 = _mm512_sub_ps(destination_x_i_vec_1, pos_x_i_vec_1); + // # 32 ADDs __m512 ey_vec_0 = _mm512_sub_ps(destination_y_i_vec_0, pos_y_i_vec_0); __m512 ey_vec_1 = _mm512_sub_ps(destination_y_i_vec_1, pos_y_i_vec_1); + // # 64 MULTs, 32 ADDs __m512 e_norm_sq_0 = _mm512_fmadd_ps(ex_vec_0, ex_vec_0, _mm512_mul_ps(ey_vec_0, ey_vec_0)); __m512 e_norm_sq_1 = _mm512_fmadd_ps(ex_vec_1, ex_vec_1, _mm512_mul_ps(ey_vec_1, ey_vec_1)); + // # 32 SQRTs __m512 e_norm_0 = _mm512_sqrt_ps(e_norm_sq_0); __m512 e_norm_1 = _mm512_sqrt_ps(e_norm_sq_1); + // # 32 DIVs __m512 e_norm_inv_0 = _mm512_div_ps(_mm512_set1_ps(1.0f), e_norm_0); __m512 e_norm_inv_1 = _mm512_div_ps(_mm512_set1_ps(1.0f), e_norm_1); + // # 32 MULTs __m512 e_x_i_vec_0 = _mm512_mul_ps(ex_vec_0, e_norm_inv_0); __m512 e_x_i_vec_1 = _mm512_mul_ps(ex_vec_1, e_norm_inv_1); + // # 32 MULTs __m512 e_y_i_vec_0 = _mm512_mul_ps(ey_vec_0, e_norm_inv_0); __m512 e_y_i_vec_1 = _mm512_mul_ps(ey_vec_1, e_norm_inv_1); + // # 64 MULTs, 32 ADDs __m512 vel_norm_sq_0 = _mm512_fmadd_ps(curr_vel_x_i_vec_0, curr_vel_x_i_vec_0, _mm512_mul_ps(curr_vel_y_i_vec_0, curr_vel_y_i_vec_0)); __m512 vel_norm_sq_1 = _mm512_fmadd_ps(curr_vel_x_i_vec_1, curr_vel_x_i_vec_1, _mm512_mul_ps(curr_vel_y_i_vec_1, curr_vel_y_i_vec_1)); + // # 32 MULTs __m512 sb_sq_i_vec_0 = _mm512_mul_ps(vel_norm_sq_0, _mm512_set1_ps(delta_t_sq)); __m512 sb_sq_i_vec_1 = _mm512_mul_ps(vel_norm_sq_1, _mm512_set1_ps(delta_t_sq)); + // # 32 SQRTs __m512 vel_norm_dt_0 = _mm512_sqrt_ps(sb_sq_i_vec_0); __m512 vel_norm_dt_1 = _mm512_sqrt_ps(sb_sq_i_vec_1); @@ -589,9 +703,11 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c _mm512_storeu_ps(&sb_sq[i], sb_sq_i_vec_0); _mm512_storeu_ps(&sb_sq[i + 16], sb_sq_i_vec_1); + // # 32 MULTs _mm512_storeu_ps(&sb_e_x[i], _mm512_mul_ps(e_x_i_vec_0, vel_norm_dt_0)); _mm512_storeu_ps(&sb_e_x[i + 16], _mm512_mul_ps(e_x_i_vec_1, vel_norm_dt_1)); + // # 32 MULTs _mm512_storeu_ps(&sb_e_y[i], _mm512_mul_ps(e_y_i_vec_0, vel_norm_dt_0)); _mm512_storeu_ps(&sb_e_y[i + 16], _mm512_mul_ps(e_y_i_vec_1, vel_norm_dt_1)); } diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.cpp index 828a9b1..ec69800 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0f #define BOARD_LENGTH 50.0f +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f #define WALL_EPS 1e-3f #define BOARD_WIDTH_MINUS_WALL_EPS 9.999f @@ -160,21 +162,46 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 256 MULTs, 224 ADDs static inline __m512 _avx512_exp_ps(__m512 x) { + const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); + + // # 16 MULTs __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); + + // # 16 MULTs, 16 ADDs __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - const __m512 c5 = _mm512_set1_ps(1.0f / 120.0f); - const __m512 c4 = _mm512_set1_ps(1.0f / 24.0f); - const __m512 c3 = _mm512_set1_ps(1.0f / 6.0f); - const __m512 c2 = _mm512_set1_ps(0.5f); - const __m512 c1 = _mm512_set1_ps(1.0f); - const __m512 c0 = _mm512_set1_ps(1.0f); + const __m512 c13 = _mm512_set1_ps(c13_scalar); + const __m512 c12 = _mm512_set1_ps(c12_scalar); + const __m512 c11 = _mm512_set1_ps(c11_scalar); + const __m512 c10 = _mm512_set1_ps(c10_scalar); + const __m512 c9 = _mm512_set1_ps(c9_scalar); + const __m512 c8 = _mm512_set1_ps(c8_scalar); + const __m512 c7 = _mm512_set1_ps(c7_scalar); + const __m512 c6 = _mm512_set1_ps(c6_scalar); + const __m512 c5 = _mm512_set1_ps(c5_scalar); + const __m512 c4 = _mm512_set1_ps(c4_scalar); + const __m512 c3 = _mm512_set1_ps(c3_scalar); + const __m512 c2 = _mm512_set1_ps(c2_scalar); + const __m512 c1 = _mm512_set1_ps(c1_scalar); + const __m512 c0 = _mm512_set1_ps(c0_scalar); - __m512 p = c5; + __m512 p = c13; + + // # 13 * (16 MULTs, 16 ADDs) + // # = 208 MULTs, 208 ADDs + p = _mm512_fmadd_ps(p, r, c12); + p = _mm512_fmadd_ps(p, r, c11); + p = _mm512_fmadd_ps(p, r, c10); + p = _mm512_fmadd_ps(p, r, c9); + p = _mm512_fmadd_ps(p, r, c8); + p = _mm512_fmadd_ps(p, r, c7); + p = _mm512_fmadd_ps(p, r, c6); + p = _mm512_fmadd_ps(p, r, c5); p = _mm512_fmadd_ps(p, r, c4); p = _mm512_fmadd_ps(p, r, c3); p = _mm512_fmadd_ps(p, r, c2); @@ -186,6 +213,7 @@ static inline __m512 _avx512_exp_ps(__m512 x) { __m512i scaled = _mm512_slli_epi32(biased, 23); __m512 pow2n = _mm512_castsi512_ps(scaled); + // # 16 MULTs return _mm512_mul_ps(p, pow2n); } @@ -224,11 +252,42 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512 v_np = _mm512_set1_ps((float)n_p); const __m512 v_exp_min = _mm512_set1_ps(-87.0f); + // # ADDs: n_iter * ( + // # n_p * 68 + + // # n_p * n_p_pad * 29 + // # n_p * board.n_obstacles * 10 + // # n_p_pad * 9 + // # ) + + // # MULTs: n_iter * ( + // # n_p * 4 + + // # n_p * n_p_pad * 40 + // # n_p * board.n_obstacles * 13 + // # n_p_pad * 17 + // # ) + + // # DIVs: n_iter * ( + // # n_p * n_p_pad + // # n_p * board.n_obstacles + // # n_p_pad * 2 + // # ) + + // # SQRTs: n_iter * ( + // # n_p * n_p_pad * 3 + // # n_p * board.n_obstacles + // # n_p_pad * 3 + // # ) + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + + // # 2 MULTs float desired_velocity_x = e_x[i] * desired_v0[i]; float desired_velocity_y = e_y[i] * desired_v0[i]; + // # 2 ADDs, 2 MULTs float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; @@ -244,6 +303,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 acc_y_1 = _mm512_setzero_ps(); for (int j = 0; j < n_p_pad; j += 32) { + + // # 32 ADDs __m512 j_idx_0 = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); __m512 j_idx_1 = _mm512_add_ps(_mm512_set1_ps((float)j + 16), lane_off); @@ -271,81 +332,105 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 sb_jsq_0 = _mm512_loadu_ps(&sb_sq[j]); __m512 sb_jsq_1 = _mm512_loadu_ps(&sb_sq[j + 16]); + // # 32 ADDs __m512 r_ab_x_0 = _mm512_sub_ps(pix, pjx_0); __m512 r_ab_x_1 = _mm512_sub_ps(pix, pjx_1); + // # 32 ADDs __m512 r_ab_y_0 = _mm512_sub_ps(piy, pjy_0); __m512 r_ab_y_1 = _mm512_sub_ps(piy, pjy_1); + // # 64 MULTs, 32 ADDs __m512 r_ab_n2_0 = _mm512_fmadd_ps(r_ab_x_0, r_ab_x_0, _mm512_mul_ps(r_ab_y_0, r_ab_y_0)); __m512 r_ab_n2_1 = _mm512_fmadd_ps(r_ab_x_1, r_ab_x_1, _mm512_mul_ps(r_ab_y_1, r_ab_y_1)); + // # 32 SQRTs __m512 r_ab_n_0 = _mm512_sqrt_ps(r_ab_n2_0); __m512 r_ab_n_1 = _mm512_sqrt_ps(r_ab_n2_1); + // # 32 ADDs __m512 r_ab2_x_0 = _mm512_sub_ps(r_ab_x_0, sbe_jx_0); __m512 r_ab2_x_1 = _mm512_sub_ps(r_ab_x_1, sbe_jx_1); + // # 32 ADDs __m512 r_ab2_y_0 = _mm512_sub_ps(r_ab_y_0, sbe_jy_0); __m512 r_ab2_y_1 = _mm512_sub_ps(r_ab_y_1, sbe_jy_1); + // # 64 MULTs, 32 ADDs __m512 r_ab2_n2_0 = _mm512_fmadd_ps(r_ab2_x_0, r_ab2_x_0, _mm512_mul_ps(r_ab2_y_0, r_ab2_y_0)); __m512 r_ab2_n2_1 = _mm512_fmadd_ps(r_ab2_x_1, r_ab2_x_1, _mm512_mul_ps(r_ab2_y_1, r_ab2_y_1)); + // # 32 SQRTs __m512 r_ab2_n_0 = _mm512_sqrt_ps(r_ab2_n2_0); __m512 r_ab2_n_1 = _mm512_sqrt_ps(r_ab2_n2_1); + // # 32 ADDs __m512 r_sum_0 = _mm512_add_ps(r_ab_n_0, r_ab2_n_0); __m512 r_sum_1 = _mm512_add_ps(r_ab_n_1, r_ab2_n_1); + // # 32 MULTs, 32 ADDs __m512 two_b_sq_0 = _mm512_fmsub_ps(r_sum_0, r_sum_0, sb_jsq_0); __m512 two_b_sq_1 = _mm512_fmsub_ps(r_sum_1, r_sum_1, sb_jsq_1); + // # 32 SQRTs __m512 two_b_0 = _mm512_sqrt_ps(two_b_sq_0); __m512 two_b_1 = _mm512_sqrt_ps(two_b_sq_1); + // # 32 MULTs __m512 e_arg_0 = _mm512_mul_ps(two_b_0, v_neg_2s_inv); __m512 e_arg_1 = _mm512_mul_ps(two_b_1, v_neg_2s_inv); e_arg_0 = _mm512_max_ps(e_arg_0, v_exp_min); e_arg_1 = _mm512_max_ps(e_arg_1, v_exp_min); + // # 512 MULTs, 448 ADDs __m512 e_val_0 = _avx512_exp_ps(e_arg_0); __m512 e_val_1 = _avx512_exp_ps(e_arg_1); + // # 64 MULTs __m512 numer_0 = _mm512_mul_ps(_mm512_mul_ps(r_sum_0, v_neg_v0_div_2s), e_val_0); __m512 numer_1 = _mm512_mul_ps(_mm512_mul_ps(r_sum_1, v_neg_v0_div_2s), e_val_1); + // # 64 MULTs __m512 denom_0 = _mm512_mul_ps(_mm512_mul_ps(two_b_0, r_ab_n_0), r_ab2_n_0); __m512 denom_1 = _mm512_mul_ps(_mm512_mul_ps(two_b_1, r_ab_n_1), r_ab2_n_1); __m512 denom_safe_0 = _mm512_mask_blend_ps(active_0, v_one, denom_0); __m512 denom_safe_1 = _mm512_mask_blend_ps(active_1, v_one, denom_1); + // # 32 DIVs __m512 smc_0 = _mm512_div_ps(numer_0, denom_safe_0); __m512 smc_1 = _mm512_div_ps(numer_1, denom_safe_1); + // # 32 MULTs __m512 sm_r_ab_0 = _mm512_mul_ps(smc_0, r_ab2_n_0); __m512 sm_r_ab_1 = _mm512_mul_ps(smc_1, r_ab2_n_1); + // # 32 MULTs __m512 sm_r_ab2_0 = _mm512_mul_ps(smc_0, r_ab_n_0); __m512 sm_r_ab2_1 = _mm512_mul_ps(smc_1, r_ab_n_1); + // # 64 MULTs, 32 ADDs __m512 f_x_0 = _mm512_fmadd_ps(r_ab2_x_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_x_0, sm_r_ab_0)); __m512 f_x_1 = _mm512_fmadd_ps(r_ab2_x_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_x_1, sm_r_ab_1)); + // # 64 MULTs, 32 ADDs __m512 f_y_0 = _mm512_fmadd_ps(r_ab2_y_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_y_0, sm_r_ab_0)); __m512 f_y_1 = _mm512_fmadd_ps(r_ab2_y_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_y_1, sm_r_ab_1)); + // # 64 MULTs, 32 ADDs __m512 f_norm_sq_0 = _mm512_fmadd_ps(f_x_0, f_x_0, _mm512_mul_ps(f_y_0, f_y_0)); __m512 f_norm_sq_1 = _mm512_fmadd_ps(f_x_1, f_x_1, _mm512_mul_ps(f_y_1, f_y_1)); + // # 32 MULTs __m512 rhs_0 = _mm512_mul_ps(f_norm_sq_0, v_cos_phi_sq); __m512 rhs_1 = _mm512_mul_ps(f_norm_sq_1, v_cos_phi_sq); + // # 64 MULTs, 32 ADDs __m512 e_dot_f_0 = _mm512_fmadd_ps(eix, f_x_0, _mm512_mul_ps(eiy, f_y_0)); __m512 e_dot_f_1 = _mm512_fmadd_ps(eix, f_x_1, _mm512_mul_ps(eiy, f_y_1)); + // # 32 MULTs __m512 e_dot_f_sq_0 = _mm512_mul_ps(e_dot_f_0, e_dot_f_0); __m512 e_dot_f_sq_1 = _mm512_mul_ps(e_dot_f_1, e_dot_f_1); @@ -367,22 +452,28 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 f_y_safe_0 = _mm512_maskz_mov_ps(active_0, f_y_0); __m512 f_y_safe_1 = _mm512_maskz_mov_ps(active_1, f_y_1); + // # 32 MULTs, 32 ADDs acc_x_0 = _mm512_fmadd_ps(f_x_safe_0, w_val_0, acc_x_0); acc_x_1 = _mm512_fmadd_ps(f_x_safe_1, w_val_1, acc_x_1); + // # 32 MULTs, 32 ADDs acc_y_0 = _mm512_fmadd_ps(f_y_safe_0, w_val_0, acc_y_0); acc_y_1 = _mm512_fmadd_ps(f_y_safe_1, w_val_1, acc_y_1); } + // # 64 ADDs f_alpha_dt_x += _mm512_reduce_add_ps(_mm512_add_ps(acc_x_0, acc_x_1)); f_alpha_dt_y += _mm512_reduce_add_ps(_mm512_add_ps(acc_y_0, acc_y_1)); for (int k = 0; k < board.n_obstacles; k++) { + + // # 2 ADDs float v1_x = pos_x[i] - board.obstacles[k].start_x; float v1_y = pos_y[i] - board.obstacles[k].start_y; float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; + // # 3 MULTs, 1 ADD float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0f) @@ -390,12 +481,15 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0f) projection = 1.0f; + // # 4 ADDs, 2 MULTs float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; + // # 4 MULTs, 1 EXP, 1 DIV float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; float f_alpha_B_neg_x = r_aB_x * scalar_mult; float f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -406,13 +500,16 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTS float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + // # 2 ADDs f_alpha_dt_x += f_alpha_B_dt_x; f_alpha_dt_y += f_alpha_B_dt_y; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } @@ -430,19 +527,27 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512i color_i_vec = _mm512_loadu_si512((__m512i *)&color[i]); + // # 32 MULTs, 16 ADDs __m512 w_norm_sq = _mm512_fmadd_ps(w_x_i_vec, w_x_i_vec, _mm512_mul_ps(w_y_i_vec, w_y_i_vec)); + + // # 16 SQRTs, 16 DIVs __m512 w_norm_inv = _mm512_rsqrt14_ps(w_norm_sq); + + // # 16 MULTs __m512 correction_factor = _mm512_min_ps(_mm512_set1_ps(1.0f), _mm512_mul_ps(v_max_i_vec, w_norm_inv)); + // # 32 MULTs __m512 curr_vel_x_i_vec = _mm512_mul_ps(w_x_i_vec, correction_factor); __m512 curr_vel_y_i_vec = _mm512_mul_ps(w_y_i_vec, correction_factor); __m512 dt_vec = _mm512_set1_ps(dt); + + // # 32 MULTs, 32 ADDs pos_x_i_vec = _mm512_fmadd_ps(curr_vel_x_i_vec, dt_vec, pos_x_i_vec); pos_y_i_vec = _mm512_fmadd_ps(curr_vel_y_i_vec, dt_vec, pos_y_i_vec); __mmask16 cond_1_left = _mm512_cmpeq_epi32_mask(color_i_vec, _mm512_setzero_si512()); - __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.5f), _CMP_GT_OQ); + __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); __mmask16 cond_1 = cond_1_left & cond_1_right; pos_x_i_vec = _mm512_mask_blend_ps(cond_1, pos_x_i_vec, _mm512_set1_ps(1.0f)); @@ -456,7 +561,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __mmask16 cond_2_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(1.5f), _CMP_LT_OQ); __mmask16 cond_2 = cond_2_left & cond_2_right; - pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.0f)); + pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE)); destination_y_i_vec = _mm512_mask_blend_ps(cond_2, destination_y_i_vec, pos_y_i_vec); __m512 neg_desired_v0 = _mm512_xor_ps(desired_v0_i_vec, _mm512_set1_ps(-0.0f)); w_x_i_vec = _mm512_mask_blend_ps(cond_2, w_x_i_vec, neg_desired_v0); @@ -467,15 +572,27 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i_vec = _mm512_max_ps(pos_y_i_vec, _mm512_set1_ps(WALL_EPS)); pos_y_i_vec = _mm512_min_ps(pos_y_i_vec, _mm512_set1_ps(BOARD_WIDTH_MINUS_WALL_EPS)); + // # 32 ADDs __m512 ex_vec = _mm512_sub_ps(destination_x_i_vec, pos_x_i_vec); __m512 ey_vec = _mm512_sub_ps(destination_y_i_vec, pos_y_i_vec); + + // # 32 MULTs, 16 ADDs __m512 e_norm_sq = _mm512_fmadd_ps(ex_vec, ex_vec, _mm512_mul_ps(ey_vec, ey_vec)); + + // # 16 DIVs, 16 SQRTs __m512 e_norm_inv = _mm512_rsqrt14_ps(e_norm_sq); + + // # 32 MULTs __m512 e_x_i_vec = _mm512_mul_ps(ex_vec, e_norm_inv); __m512 e_y_i_vec = _mm512_mul_ps(ey_vec, e_norm_inv); + // # 32 MULTs, 16 ADDs __m512 vel_norm_sq = _mm512_fmadd_ps(curr_vel_x_i_vec, curr_vel_x_i_vec, _mm512_mul_ps(curr_vel_y_i_vec, curr_vel_y_i_vec)); + + // # 16 MULTs __m512 sb_sq_i_vec = _mm512_mul_ps(vel_norm_sq, _mm512_set1_ps(delta_t_sq)); + + // # 16 SQRTs __m512 vel_norm_dt = _mm512_sqrt_ps(sb_sq_i_vec); _mm512_storeu_ps(&curr_vel_x[i], curr_vel_x_i_vec); @@ -488,6 +605,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c _mm512_storeu_ps(&e_y[i], e_y_i_vec); _mm512_storeu_ps(&destination_y[i], destination_y_i_vec); _mm512_storeu_ps(&sb_sq[i], sb_sq_i_vec); + + // # 32 MULTs _mm512_storeu_ps(&sb_e_x[i], _mm512_mul_ps(e_x_i_vec, vel_norm_dt)); _mm512_storeu_ps(&sb_e_y[i], _mm512_mul_ps(e_y_i_vec, vel_norm_dt)); } diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp index 9e4233c..d4253cf 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0f #define BOARD_LENGTH 50.0f +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f #define WALL_EPS 1e-3f #define BOARD_WIDTH_MINUS_WALL_EPS 9.999f @@ -160,41 +162,58 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 256 MULTs, 224 ADDs static inline __m512 _avx512_exp_ps(__m512 x) { - // define own inlined intrinsic function for approximating the exponent function - // e^x = 2^n * e^r --> x = n * ln(2) + r const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); + + // # 16 MULTs __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); - // r = x - n * ln(2) + + // # 16 MULTs, 16 ADDs __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - // approximation of e^r using taylor polynomial - // cNUM --> coefficients - const __m512 c5 = _mm512_set1_ps(1.0f / 120.0f); - const __m512 c4 = _mm512_set1_ps(1.0f / 24.0f); - const __m512 c3 = _mm512_set1_ps(1.0f / 6.0f); - const __m512 c2 = _mm512_set1_ps(0.5f); - const __m512 c1 = _mm512_set1_ps(1.0f); - const __m512 c0 = _mm512_set1_ps(1.0f); + const __m512 c13 = _mm512_set1_ps(c13_scalar); + const __m512 c12 = _mm512_set1_ps(c12_scalar); + const __m512 c11 = _mm512_set1_ps(c11_scalar); + const __m512 c10 = _mm512_set1_ps(c10_scalar); + const __m512 c9 = _mm512_set1_ps(c9_scalar); + const __m512 c8 = _mm512_set1_ps(c8_scalar); + const __m512 c7 = _mm512_set1_ps(c7_scalar); + const __m512 c6 = _mm512_set1_ps(c6_scalar); + const __m512 c5 = _mm512_set1_ps(c5_scalar); + const __m512 c4 = _mm512_set1_ps(c4_scalar); + const __m512 c3 = _mm512_set1_ps(c3_scalar); + const __m512 c2 = _mm512_set1_ps(c2_scalar); + const __m512 c1 = _mm512_set1_ps(c1_scalar); + const __m512 c0 = _mm512_set1_ps(c0_scalar); - // apply coefficients - __m512 p = c5; + __m512 p = c13; + + // # 13 * (16 MULTs, 16 ADDs) + // # = 208 MULTs, 208 ADDs + p = _mm512_fmadd_ps(p, r, c12); + p = _mm512_fmadd_ps(p, r, c11); + p = _mm512_fmadd_ps(p, r, c10); + p = _mm512_fmadd_ps(p, r, c9); + p = _mm512_fmadd_ps(p, r, c8); + p = _mm512_fmadd_ps(p, r, c7); + p = _mm512_fmadd_ps(p, r, c6); + p = _mm512_fmadd_ps(p, r, c5); p = _mm512_fmadd_ps(p, r, c4); p = _mm512_fmadd_ps(p, r, c3); p = _mm512_fmadd_ps(p, r, c2); p = _mm512_fmadd_ps(p, r, c1); p = _mm512_fmadd_ps(p, r, c0); - // use bit shifting for 2^n and bias because float have 23 bit mantissa __m512i ni = _mm512_cvtps_epi32(nf); __m512i biased = _mm512_add_epi32(ni, _mm512_set1_epi32(127)); __m512i scaled = _mm512_slli_epi32(biased, 23); __m512 pow2n = _mm512_castsi512_ps(scaled); - // e^x + // # 16 MULTs return _mm512_mul_ps(p, pow2n); } @@ -233,254 +252,227 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512 v_np = _mm512_set1_ps((float)n_p); const __m512 v_exp_min = _mm512_set1_ps(-87.0f); + // # ADDs: n_iter * ( + // # n_p * 68 + + // # n_p * n_p_pad * 29 + + // # n_p * board.n_obstacles * 10 + + // # n_p_pad * 7 + // # ) + + // # MULTs: n_iter * ( + // # n_p * 4 + + // # n_p * n_p_pad * 42 + + // # n_p * board.n_obstacles * 13 + + // # n_p_pad * 16 + // # ) + + // # DIVs: n_iter * ( + // # n_p * n_p_pad * 3 + + // # n_p * board.n_obstacles + + // # n_p_pad * 2 + // # ) + + // # SQRTs: n_iter * ( + // # n_p * n_p_pad * 3 + + // # n_p * board.n_obstacles + + // # n_p_pad * 3 + // # ) + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { - // set the vector for the current pedestrian - // get desired velocity + // # 2 MULTs float desired_velocity_x = e_x[i] * desired_v0[i]; float desired_velocity_y = e_y[i] * desired_v0[i]; - // velocity change accumulator + // # 2 ADDs, 2 MULTs float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; - // pos const __m512 pix = _mm512_set1_ps(pos_x[i]); const __m512 piy = _mm512_set1_ps(pos_y[i]); - // desired direction const __m512 eix = _mm512_set1_ps(e_x[i]); const __m512 eiy = _mm512_set1_ps(e_y[i]); - // broadcast index used by mask to prevent situation pedestrian[i] == pedestrian[j], see m_neq_i_0 and m_neq_i_1 const __m512 v_i = _mm512_set1_ps((float)i); - // accumulators --> 16 pedestrians per accumulator __m512 acc_x_0 = _mm512_setzero_ps(); __m512 acc_y_0 = _mm512_setzero_ps(); __m512 acc_x_1 = _mm512_setzero_ps(); __m512 acc_y_1 = _mm512_setzero_ps(); - // force between pedestrians - // loop unroll factor 32 with 2 vectors - // --> each vector has 16 lanes for (int j = 0; j < n_p_pad; j += 32) { - - // [j, j+1, j+2, ..., j+15] and [j+16, j+17, j+18, ..., j+31] + + // # 32 ADDs __m512 j_idx_0 = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); __m512 j_idx_1 = _mm512_add_ps(_mm512_set1_ps((float)j + 16), lane_off); - // lane is active if j != i - // [1,1,1,0,1,1,1,...] 0 --> i == j, 1 --> i != j __mmask16 m_neq_i_0 = _mm512_cmp_ps_mask(j_idx_0, v_i, _CMP_NEQ_OQ); __mmask16 m_neq_i_1 = _mm512_cmp_ps_mask(j_idx_1, v_i, _CMP_NEQ_OQ); - - // lane is active if j < n_p --> valid range - // [1,1,1,0,1,1,1,...] 0 --> j >= n_p, 1 --> j < n_p + __mmask16 m_in_range_0 = _mm512_cmp_ps_mask(j_idx_0, v_np, _CMP_LT_OQ); __mmask16 m_in_range_1 = _mm512_cmp_ps_mask(j_idx_1, v_np, _CMP_LT_OQ); - // bitwise comparison if not valid --> 0 - // finally check if lane is valid or not --> prevents i == j, and - // j >= n_p because of loop unrolling --> we can therefore omit a remainings for loop __mmask16 active_0 = m_neq_i_0 & m_in_range_0; __mmask16 active_1 = m_neq_i_1 & m_in_range_1; - // --- - - // load the positions of the other pedestrians x value - // [pos[j], pos[j+1], ..., pos[j+15]] and [pos[16], pos[j+17], ..., pos[j+31]] for x and y __m512 pjx_0 = _mm512_loadu_ps(&pos_x[j]); __m512 pjx_1 = _mm512_loadu_ps(&pos_x[j + 16]); __m512 pjy_0 = _mm512_loadu_ps(&pos_y[j]); __m512 pjy_1 = _mm512_loadu_ps(&pos_y[j + 16]); - // --- - - // step vector of j - // [(v_j * delta_t * e_j).x, (v_j+1 * delta_t * e_j+1).x, ...] __m512 sbe_jx_0 = _mm512_loadu_ps(&sb_e_x[j]); __m512 sbe_jx_1 = _mm512_loadu_ps(&sb_e_x[j + 16]); __m512 sbe_jy_0 = _mm512_loadu_ps(&sb_e_y[j]); __m512 sbe_jy_1 = _mm512_loadu_ps(&sb_e_y[j + 16]); - // --- - - // squared step length - // [j, j+1, ...] __m512 sb_jsq_0 = _mm512_loadu_ps(&sb_sq[j]); __m512 sb_jsq_1 = _mm512_loadu_ps(&sb_sq[j + 16]); - // --- - - // distance vector - // [j, j+1, ...] + // # 64 ADDs __m512 r_ab_x_0 = _mm512_sub_ps(pix, pjx_0); __m512 r_ab_x_1 = _mm512_sub_ps(pix, pjx_1); __m512 r_ab_y_0 = _mm512_sub_ps(piy, pjy_0); __m512 r_ab_y_1 = _mm512_sub_ps(piy, pjy_1); - // |r_ab|^2 needed for rsqrt to prevent 0 values --> x^2 + y^2 + // # 64 MULTs, 32 ADDs __m512 r_ab_n2_0 = _mm512_fmadd_ps(r_ab_x_0, r_ab_x_0, _mm512_mul_ps(r_ab_y_0, r_ab_y_0)); __m512 r_ab_n2_1 = _mm512_fmadd_ps(r_ab_x_1, r_ab_x_1, _mm512_mul_ps(r_ab_y_1, r_ab_y_1)); - // prevent cases where distance = 0 (=pedestrian on top of each other) - // [dist_i_j, dist_i_j+1, ...] r_ab_n2_0 = _mm512_max_ps(r_ab_n2_0, v_eps_sq); r_ab_n2_1 = _mm512_max_ps(r_ab_n2_1, v_eps_sq); - - // --- - - // 1 / |r_ab| --> 14 bit rsqrt approx. rsqrt --> 1 / x^2 --> rsqrt(x^2) = 1/x - // [1 / dist_i_j, 1 / dist_i_j+1, ...] + + // # 32 DIVs, 32 SQRTs __m512 inv_r_ab_n_0 = _mm512_rsqrt14_ps(r_ab_n2_0); __m512 inv_r_ab_n_1 = _mm512_rsqrt14_ps(r_ab_n2_1); - // r_ab^2 * 1 / r_ab = |r_ab| --> normed - // [r_i_j, r_i_j+1, ...] + // # 32 MULTs __m512 r_ab_n_0 = _mm512_mul_ps(r_ab_n2_0, inv_r_ab_n_0); __m512 r_ab_n_1 = _mm512_mul_ps(r_ab_n2_1, inv_r_ab_n_1); - // r_ab - step[j] - // j next pos + // # 64 ADDs __m512 r_ab2_x_0 = _mm512_sub_ps(r_ab_x_0, sbe_jx_0); __m512 r_ab2_x_1 = _mm512_sub_ps(r_ab_x_1, sbe_jx_1); __m512 r_ab2_y_0 = _mm512_sub_ps(r_ab_y_0, sbe_jy_0); __m512 r_ab2_y_1 = _mm512_sub_ps(r_ab_y_1, sbe_jy_1); - // |r_ab_2| --> needed for rsqrt trick (r_ab - step) + // # 64 MULTs, 32 ADDs __m512 r_ab2_n2_0 = _mm512_fmadd_ps(r_ab2_x_0, r_ab2_x_0, _mm512_mul_ps(r_ab2_y_0, r_ab2_y_0)); __m512 r_ab2_n2_1 = _mm512_fmadd_ps(r_ab2_x_1, r_ab2_x_1, _mm512_mul_ps(r_ab2_y_1, r_ab2_y_1)); - // prevent 0 case --> infinity r_ab2_n2_0 = _mm512_max_ps(r_ab2_n2_0, v_eps_sq); r_ab2_n2_1 = _mm512_max_ps(r_ab2_n2_1, v_eps_sq); - // same as before use rsqrt with 14bit approx - // [1 / |r_ab_2|, 1 / |r_ab_2|, ...] + // # 32 DIVs, 32 SQRTs __m512 inv_r_ab2_n_0 = _mm512_rsqrt14_ps(r_ab2_n2_0); __m512 inv_r_ab2_n_1 = _mm512_rsqrt14_ps(r_ab2_n2_1); - // |r_ab_2_second_distance_vector| * 1 / |r_ab_2_first_distance_vector| + // # 32 MULTs __m512 r_ab2_n_0 = _mm512_mul_ps(r_ab2_n2_0, inv_r_ab2_n_0); __m512 r_ab2_n_1 = _mm512_mul_ps(r_ab2_n2_1, inv_r_ab2_n_1); - // r_sum = |r_ab| + |r_ab - step[j]| --> [w_0, w_1, w_2, ..., w_15] and [w_16, ..., w_31] + // # 32 ADDs __m512 r_sum_0 = _mm512_add_ps(r_ab_n_0, r_ab2_n_0); __m512 r_sum_1 = _mm512_add_ps(r_ab_n_1, r_ab2_n_1); - // 2_b^2 = r_sum^2 - step[j]^2 + // # 32 MULTs, 32 ADDs __m512 two_b_sq_0 = _mm512_fmsub_ps(r_sum_0, r_sum_0, sb_jsq_0); __m512 two_b_sq_1 = _mm512_fmsub_ps(r_sum_1, r_sum_1, sb_jsq_1); - // prevent 0 case two_b_sq_0 = _mm512_max_ps(two_b_sq_0, v_eps_sq); two_b_sq_1 = _mm512_max_ps(two_b_sq_1, v_eps_sq); - // 2_b - // 1 / 2_b^(2*0.5) --> 1 / 2_b + // # 32 DIVs, 32 SQRTs __m512 inv_two_b_0 = _mm512_rsqrt14_ps(two_b_sq_0); __m512 inv_two_b_1 = _mm512_rsqrt14_ps(two_b_sq_1); - // 2_b = 2_b^2 * 1 / 2_b + // # 32 MULTs __m512 two_b_0 = _mm512_mul_ps(two_b_sq_0, inv_two_b_0); __m512 two_b_1 = _mm512_mul_ps(two_b_sq_1, inv_two_b_1); - // --- - - // two_b * -1 / (2*sigma) --> exp + // # 32 MULTs __m512 e_arg_0 = _mm512_mul_ps(two_b_0, v_neg_2s_inv); __m512 e_arg_1 = _mm512_mul_ps(two_b_1, v_neg_2s_inv); - // exp(x) - // when x is small just assume constant value --> faster exp e_arg_0 = _mm512_max_ps(e_arg_0, v_exp_min); e_arg_1 = _mm512_max_ps(e_arg_1, v_exp_min); - // custom fast exponent intrinsic using polynomial approximations and some precomputed constants + // # 512 MULTs, 448 ADDs __m512 e_val_0 = _avx512_exp_ps(e_arg_0); __m512 e_val_1 = _avx512_exp_ps(e_arg_1); - // [−(V0/σ) * e^(−b/σ) * w/(4b), ...] --> scalar + // # 96 MULTs __m512 numer_inv_two_b_0 = _mm512_mul_ps(_mm512_mul_ps(r_sum_0, v_neg_v0_div_2s), _mm512_mul_ps(e_val_0, inv_two_b_0)); __m512 numer_inv_two_b_1 = _mm512_mul_ps(_mm512_mul_ps(r_sum_1, v_neg_v0_div_2s), _mm512_mul_ps(e_val_1, inv_two_b_1)); - // scalar * 1 / |r_ab| + // # 32 MULTs __m512 sm_r_ab_0 = _mm512_mul_ps(numer_inv_two_b_0, inv_r_ab_n_0); __m512 sm_r_ab_1 = _mm512_mul_ps(numer_inv_two_b_1, inv_r_ab_n_1); - // scalar * 1 / |r_ab_2| + // # 32 MULTs __m512 sm_r_ab2_0 = _mm512_mul_ps(numer_inv_two_b_0, inv_r_ab2_n_0); __m512 sm_r_ab2_1 = _mm512_mul_ps(numer_inv_two_b_1, inv_r_ab2_n_1); - // summed force + // # 128 MULTs, 64 ADDs __m512 f_x_0 = _mm512_fmadd_ps(r_ab2_x_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_x_0, sm_r_ab_0)); __m512 f_x_1 = _mm512_fmadd_ps(r_ab2_x_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_x_1, sm_r_ab_1)); __m512 f_y_0 = _mm512_fmadd_ps(r_ab2_y_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_y_0, sm_r_ab_0)); __m512 f_y_1 = _mm512_fmadd_ps(r_ab2_y_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_y_1, sm_r_ab_1)); - // using masking to perform --> field of view formula - // if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - // return dt_neg; - // else - // return c_neg_times_dt; - // + // # 64 MULTs, 32 ADDs __m512 f_norm_sq_0 = _mm512_fmadd_ps(f_x_0, f_x_0, _mm512_mul_ps(f_y_0, f_y_0)); __m512 f_norm_sq_1 = _mm512_fmadd_ps(f_x_1, f_x_1, _mm512_mul_ps(f_y_1, f_y_1)); + // # 32 MULTs __m512 rhs_0 = _mm512_mul_ps(f_norm_sq_0, v_cos_phi_sq); __m512 rhs_1 = _mm512_mul_ps(f_norm_sq_1, v_cos_phi_sq); + // # 64 MULTs, 32 ADDs __m512 e_dot_f_0 = _mm512_fmadd_ps(eix, f_x_0, _mm512_mul_ps(eiy, f_y_0)); __m512 e_dot_f_1 = _mm512_fmadd_ps(eix, f_x_1, _mm512_mul_ps(eiy, f_y_1)); + // # 32 MULTs __m512 e_dot_f_sq_0 = _mm512_mul_ps(e_dot_f_0, e_dot_f_0); __m512 e_dot_f_sq_1 = _mm512_mul_ps(e_dot_f_1, e_dot_f_1); - // e_i * f < 0 --> behind __mmask16 c1_0 = _mm512_cmp_ps_mask(e_dot_f_0, v_zero, _CMP_LT_OQ); __mmask16 c1_1 = _mm512_cmp_ps_mask(e_dot_f_1, v_zero, _CMP_LT_OQ); - // (e_i * f)^2 > cos(phi) --> out of view __mmask16 c2_0 = _mm512_cmp_ps_mask(e_dot_f_sq_0, rhs_0, _CMP_GT_OQ); __mmask16 c2_1 = _mm512_cmp_ps_mask(e_dot_f_sq_1, rhs_1, _CMP_GT_OQ); - // get the case (in field of view or not) - // 1 = reduced weight, 0 = regular __mmask16 cond_0 = c1_0 & c2_0; __mmask16 cond_1 = c1_1 & c2_1; - // apply the correct weight __m512 w_val_0 = _mm512_mask_blend_ps(cond_0, v_dt_neg, v_c_neg_dt); __m512 w_val_1 = _mm512_mask_blend_ps(cond_1, v_dt_neg, v_c_neg_dt); - // take care of i == j case by zeroing it out - // see active_0 and active_1 at the beginning of this loop - // [f.x or 0, ...] __m512 f_x_safe_0 = _mm512_maskz_mov_ps(active_0, f_x_0); __m512 f_x_safe_1 = _mm512_maskz_mov_ps(active_1, f_x_1); __m512 f_y_safe_0 = _mm512_maskz_mov_ps(active_0, f_y_0); __m512 f_y_safe_1 = _mm512_maskz_mov_ps(active_1, f_y_1); - // accumulate force with weight - // force_P_total += weight * force_P_other + // # 64 MULTs, 64 ADDs acc_x_0 = _mm512_fmadd_ps(f_x_safe_0, w_val_0, acc_x_0); acc_x_1 = _mm512_fmadd_ps(f_x_safe_1, w_val_1, acc_x_1); acc_y_0 = _mm512_fmadd_ps(f_y_safe_0, w_val_0, acc_y_0); acc_y_1 = _mm512_fmadd_ps(f_y_safe_1, w_val_1, acc_y_1); } - // scalar force accumulator + // # 64 ADDs f_alpha_dt_x += _mm512_reduce_add_ps(_mm512_add_ps(acc_x_0, acc_x_1)); f_alpha_dt_y += _mm512_reduce_add_ps(_mm512_add_ps(acc_y_0, acc_y_1)); - // force between pedestrian and obstacles - // no optimizations, because force between pedestrian is the bottleneck of the computation for (int k = 0; k < board.n_obstacles; k++) { + + // # 2 ADDs float v1_x = pos_x[i] - board.obstacles[k].start_x; float v1_y = pos_y[i] - board.obstacles[k].start_y; float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; + // # 3 MULTs, 1 ADD float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0f) @@ -488,12 +480,15 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0f) projection = 1.0f; + // # 4 ADDs, 2 MULTs float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; + // # 4 MULTs, 1 EXP, 1 DIV float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; float f_alpha_B_neg_x = r_aB_x * scalar_mult; float f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -504,21 +499,21 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTS float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + // # 2 ADDs f_alpha_dt_x += f_alpha_B_dt_x; f_alpha_dt_y += f_alpha_B_dt_y; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } - // update each pedestrian for (int i = 0; i < n_p_pad; i += 16) { - // load 16 pedestrians per iteration with their data - // SoA in vectors __m512 w_x_i_vec = _mm512_loadu_ps(&w_x[i]); __m512 w_y_i_vec = _mm512_loadu_ps(&w_y[i]); __m512 v_max_i_vec = _mm512_loadu_ps(&v_max[i]); @@ -530,20 +525,27 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512i color_i_vec = _mm512_loadu_si512((__m512i *)&color[i]); + // # 32 MULTs, 16 ADDs __m512 w_norm_sq = _mm512_fmadd_ps(w_x_i_vec, w_x_i_vec, _mm512_mul_ps(w_y_i_vec, w_y_i_vec)); + + // # 16 SQRTs, 16 DIVs __m512 w_norm_inv = _mm512_rsqrt14_ps(w_norm_sq); + + // # 16 MULTs __m512 correction_factor = _mm512_min_ps(_mm512_set1_ps(1.0f), _mm512_mul_ps(v_max_i_vec, w_norm_inv)); + // # 32 MULTs __m512 curr_vel_x_i_vec = _mm512_mul_ps(w_x_i_vec, correction_factor); __m512 curr_vel_y_i_vec = _mm512_mul_ps(w_y_i_vec, correction_factor); __m512 dt_vec = _mm512_set1_ps(dt); + + // # 32 MULTs, 32 ADDs pos_x_i_vec = _mm512_fmadd_ps(curr_vel_x_i_vec, dt_vec, pos_x_i_vec); pos_y_i_vec = _mm512_fmadd_ps(curr_vel_y_i_vec, dt_vec, pos_y_i_vec); - // differentiate whether pedestrian came from left or right using masking __mmask16 cond_1_left = _mm512_cmpeq_epi32_mask(color_i_vec, _mm512_setzero_si512()); - __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.5f), _CMP_GT_OQ); + __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); __mmask16 cond_1 = cond_1_left & cond_1_right; pos_x_i_vec = _mm512_mask_blend_ps(cond_1, pos_x_i_vec, _mm512_set1_ps(1.0f)); @@ -557,7 +559,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __mmask16 cond_2_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(1.5f), _CMP_LT_OQ); __mmask16 cond_2 = cond_2_left & cond_2_right; - pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.0f)); + pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE)); destination_y_i_vec = _mm512_mask_blend_ps(cond_2, destination_y_i_vec, pos_y_i_vec); __m512 neg_desired_v0 = _mm512_xor_ps(desired_v0_i_vec, _mm512_set1_ps(-0.0f)); w_x_i_vec = _mm512_mask_blend_ps(cond_2, w_x_i_vec, neg_desired_v0); @@ -568,18 +570,29 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i_vec = _mm512_max_ps(pos_y_i_vec, _mm512_set1_ps(WALL_EPS)); pos_y_i_vec = _mm512_min_ps(pos_y_i_vec, _mm512_set1_ps(BOARD_WIDTH_MINUS_WALL_EPS)); + // # 32 ADDs __m512 ex_vec = _mm512_sub_ps(destination_x_i_vec, pos_x_i_vec); __m512 ey_vec = _mm512_sub_ps(destination_y_i_vec, pos_y_i_vec); + + // # 32 MULTs, 16 ADDs __m512 e_norm_sq = _mm512_fmadd_ps(ex_vec, ex_vec, _mm512_mul_ps(ey_vec, ey_vec)); + + // # 16 SQRTs, 16 DIVs __m512 e_norm_inv = _mm512_rsqrt14_ps(e_norm_sq); + + // # 32 MULTs __m512 e_x_i_vec = _mm512_mul_ps(ex_vec, e_norm_inv); __m512 e_y_i_vec = _mm512_mul_ps(ey_vec, e_norm_inv); + // # 32 MULTs, 16 ADDs __m512 vel_norm_sq = _mm512_fmadd_ps(curr_vel_x_i_vec, curr_vel_x_i_vec, _mm512_mul_ps(curr_vel_y_i_vec, curr_vel_y_i_vec)); + + // # 16 MULTs __m512 sb_sq_i_vec = _mm512_mul_ps(vel_norm_sq, _mm512_set1_ps(delta_t_sq)); + + // # 16 SQRTs __m512 vel_norm_dt = _mm512_sqrt_ps(sb_sq_i_vec); - // write the data _mm512_storeu_ps(&curr_vel_x[i], curr_vel_x_i_vec); _mm512_storeu_ps(&curr_vel_y[i], curr_vel_y_i_vec); _mm512_storeu_ps(&w_x[i], w_x_i_vec); @@ -590,6 +603,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c _mm512_storeu_ps(&e_y[i], e_y_i_vec); _mm512_storeu_ps(&destination_y[i], destination_y_i_vec); _mm512_storeu_ps(&sb_sq[i], sb_sq_i_vec); + + // # 32 MULTs _mm512_storeu_ps(&sb_e_x[i], _mm512_mul_ps(e_x_i_vec, vel_norm_dt)); _mm512_storeu_ps(&sb_e_y[i], _mm512_mul_ps(e_y_i_vec, vel_norm_dt)); } diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp deleted file mode 100644 index a9bbd5c..0000000 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp +++ /dev/null @@ -1,632 +0,0 @@ -// # Changes: Added more correction rounds to _avx512_exp_ps to do 30 flops per scalar -// # Predecessor: variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include - -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f -#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; -alignas(64) vector pos_x, pos_y; -alignas(64) vector sb_sq; -alignas(64) vector sb_e_x, sb_e_y; -alignas(64) vector e_x, e_y; -alignas(64) vector curr_vel_x, curr_vel_y; -alignas(64) vector w_x, w_y; -alignas(64) vector desired_v0; -alignas(64) vector v_max; -alignas(64) vector destination_x, destination_y; -alignas(64) vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - const int n_p_pad = (n_p + 31) & ~31; - - pos_x.resize(n_p_pad); - pos_y.resize(n_p_pad); - sb_sq.resize(n_p_pad); - sb_e_x.resize(n_p_pad); - sb_e_y.resize(n_p_pad); - e_x.resize(n_p_pad); - e_y.resize(n_p_pad); - curr_vel_x.resize(n_p_pad); - curr_vel_y.resize(n_p_pad); - w_x.resize(n_p_pad); - w_y.resize(n_p_pad); - desired_v0.resize(n_p_pad); - v_max.resize(n_p_pad); - destination_x.resize(n_p_pad); - destination_y.resize(n_p_pad); - color.resize(n_p_pad); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0f; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1f); - - float v_m = 1.3f * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30.0f; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1f); - - float v_m = 1.3f * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int j = n_p; j < n_p_pad; j++) { - pos_x[j] = 1e9f; - pos_y[j] = 1e9f; - sb_sq[j] = 0.0f; - sb_e_x[j] = 0.0f; - sb_e_y[j] = 0.0f; - } - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -// # 256 MULTs, 224 ADDs -static inline __m512 _avx512_exp_ps(__m512 x) { - - const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); - - // # 16 MULTs - __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); - - const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); - - // # 16 MULTs, 16 ADDs - __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - - const __m512 c13 = _mm512_set1_ps(c13_scalar); - const __m512 c12 = _mm512_set1_ps(c12_scalar); - const __m512 c11 = _mm512_set1_ps(c11_scalar); - const __m512 c10 = _mm512_set1_ps(c10_scalar); - const __m512 c9 = _mm512_set1_ps(c9_scalar); - const __m512 c8 = _mm512_set1_ps(c8_scalar); - const __m512 c7 = _mm512_set1_ps(c7_scalar); - const __m512 c6 = _mm512_set1_ps(c6_scalar); - const __m512 c5 = _mm512_set1_ps(c5_scalar); - const __m512 c4 = _mm512_set1_ps(c4_scalar); - const __m512 c3 = _mm512_set1_ps(c3_scalar); - const __m512 c2 = _mm512_set1_ps(c2_scalar); - const __m512 c1 = _mm512_set1_ps(c1_scalar); - const __m512 c0 = _mm512_set1_ps(c0_scalar); - - __m512 p = c13; - - // # 13 * (16 MULTs, 16 ADDs) - // # = 208 MULTs, 208 ADDs - p = _mm512_fmadd_ps(p, r, c12); - p = _mm512_fmadd_ps(p, r, c11); - p = _mm512_fmadd_ps(p, r, c10); - p = _mm512_fmadd_ps(p, r, c9); - p = _mm512_fmadd_ps(p, r, c8); - p = _mm512_fmadd_ps(p, r, c7); - p = _mm512_fmadd_ps(p, r, c6); - p = _mm512_fmadd_ps(p, r, c5); - p = _mm512_fmadd_ps(p, r, c4); - p = _mm512_fmadd_ps(p, r, c3); - p = _mm512_fmadd_ps(p, r, c2); - p = _mm512_fmadd_ps(p, r, c1); - p = _mm512_fmadd_ps(p, r, c0); - - __m512i ni = _mm512_cvtps_epi32(nf); - __m512i biased = _mm512_add_epi32(ni, _mm512_set1_epi32(127)); - __m512i scaled = _mm512_slli_epi32(biased, 23); - __m512 pow2n = _mm512_castsi512_ps(scaled); - - // # 16 MULTs - return _mm512_mul_ps(p, pow2n); -} - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - const int n_p_pad = (n_p + 31) & ~31; - - alignas(64) const float lane_offset_arr[16] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f}; - const __m512 lane_off = _mm512_load_ps(lane_offset_arr); - - const __m512 v_neg_v0_div_2s = _mm512_set1_ps(NEG_V0_AB_div_TWO_SIGMA); - const __m512 v_neg_2s_inv = _mm512_set1_ps(NEG_TWO_SIGMA_INV); - const __m512 v_cos_phi_sq = _mm512_set1_ps(cos_phi_sq); - const __m512 v_dt_neg = _mm512_set1_ps(dt_neg); - const __m512 v_c_neg_dt = _mm512_set1_ps(c_neg_times_dt); - const __m512 v_zero = _mm512_setzero_ps(); - const __m512 v_one = _mm512_set1_ps(1.0f); - const __m512 v_eps = _mm512_set1_ps(EPS); - const __m512 v_eps_sq = _mm512_set1_ps(EPS_SQ); - const __m512 v_np = _mm512_set1_ps((float)n_p); - const __m512 v_exp_min = _mm512_set1_ps(-87.0f); - - // # ADDs: n_iter * ( - // # n_p * 68 + - // # n_p * n_p_pad * 29 + - // # n_p * board.n_obstacles * 10 + - // # n_p_pad * 7 - // # ) - - // # MULTs: n_iter * ( - // # n_p * 4 + - // # n_p * n_p_pad * 42 + - // # n_p * board.n_obstacles * 13 + - // # n_p_pad * 16 - // # ) - - // # DIVs: n_iter * ( - // # n_p * n_p_pad * 3 + - // # n_p * board.n_obstacles + - // # n_p_pad * 2 - // # ) - - // # SQRTs: n_iter * ( - // # n_p * n_p_pad * 3 + - // # n_p * board.n_obstacles + - // # n_p_pad * 3 - // # ) - - // # EXPs: n_iter * n_p * board.n_obstacles - - for (int t = 0; t < n_iter; t++) { - for (int i = 0; i < n_p; i++) { - - // # 2 MULTs - float desired_velocity_x = e_x[i] * desired_v0[i]; - float desired_velocity_y = e_y[i] * desired_v0[i]; - - // # 2 ADDs, 2 MULTs - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; - - const __m512 pix = _mm512_set1_ps(pos_x[i]); - const __m512 piy = _mm512_set1_ps(pos_y[i]); - const __m512 eix = _mm512_set1_ps(e_x[i]); - const __m512 eiy = _mm512_set1_ps(e_y[i]); - const __m512 v_i = _mm512_set1_ps((float)i); - - __m512 acc_x_0 = _mm512_setzero_ps(); - __m512 acc_y_0 = _mm512_setzero_ps(); - __m512 acc_x_1 = _mm512_setzero_ps(); - __m512 acc_y_1 = _mm512_setzero_ps(); - - for (int j = 0; j < n_p_pad; j += 32) { - - // # 32 ADDs - __m512 j_idx_0 = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); - __m512 j_idx_1 = _mm512_add_ps(_mm512_set1_ps((float)j + 16), lane_off); - - __mmask16 m_neq_i_0 = _mm512_cmp_ps_mask(j_idx_0, v_i, _CMP_NEQ_OQ); - __mmask16 m_neq_i_1 = _mm512_cmp_ps_mask(j_idx_1, v_i, _CMP_NEQ_OQ); - - __mmask16 m_in_range_0 = _mm512_cmp_ps_mask(j_idx_0, v_np, _CMP_LT_OQ); - __mmask16 m_in_range_1 = _mm512_cmp_ps_mask(j_idx_1, v_np, _CMP_LT_OQ); - - __mmask16 active_0 = m_neq_i_0 & m_in_range_0; - __mmask16 active_1 = m_neq_i_1 & m_in_range_1; - - __m512 pjx_0 = _mm512_loadu_ps(&pos_x[j]); - __m512 pjx_1 = _mm512_loadu_ps(&pos_x[j + 16]); - __m512 pjy_0 = _mm512_loadu_ps(&pos_y[j]); - __m512 pjy_1 = _mm512_loadu_ps(&pos_y[j + 16]); - - __m512 sbe_jx_0 = _mm512_loadu_ps(&sb_e_x[j]); - __m512 sbe_jx_1 = _mm512_loadu_ps(&sb_e_x[j + 16]); - __m512 sbe_jy_0 = _mm512_loadu_ps(&sb_e_y[j]); - __m512 sbe_jy_1 = _mm512_loadu_ps(&sb_e_y[j + 16]); - - __m512 sb_jsq_0 = _mm512_loadu_ps(&sb_sq[j]); - __m512 sb_jsq_1 = _mm512_loadu_ps(&sb_sq[j + 16]); - - // # 64 ADDs - __m512 r_ab_x_0 = _mm512_sub_ps(pix, pjx_0); - __m512 r_ab_x_1 = _mm512_sub_ps(pix, pjx_1); - __m512 r_ab_y_0 = _mm512_sub_ps(piy, pjy_0); - __m512 r_ab_y_1 = _mm512_sub_ps(piy, pjy_1); - - // # 64 MULTs, 32 ADDs - __m512 r_ab_n2_0 = _mm512_fmadd_ps(r_ab_x_0, r_ab_x_0, _mm512_mul_ps(r_ab_y_0, r_ab_y_0)); - __m512 r_ab_n2_1 = _mm512_fmadd_ps(r_ab_x_1, r_ab_x_1, _mm512_mul_ps(r_ab_y_1, r_ab_y_1)); - - r_ab_n2_0 = _mm512_max_ps(r_ab_n2_0, v_eps_sq); - r_ab_n2_1 = _mm512_max_ps(r_ab_n2_1, v_eps_sq); - - // # 32 DIVs, 32 SQRTs - __m512 inv_r_ab_n_0 = _mm512_rsqrt14_ps(r_ab_n2_0); - __m512 inv_r_ab_n_1 = _mm512_rsqrt14_ps(r_ab_n2_1); - - // # 32 MULTs - __m512 r_ab_n_0 = _mm512_mul_ps(r_ab_n2_0, inv_r_ab_n_0); - __m512 r_ab_n_1 = _mm512_mul_ps(r_ab_n2_1, inv_r_ab_n_1); - - // # 64 ADDs - __m512 r_ab2_x_0 = _mm512_sub_ps(r_ab_x_0, sbe_jx_0); - __m512 r_ab2_x_1 = _mm512_sub_ps(r_ab_x_1, sbe_jx_1); - __m512 r_ab2_y_0 = _mm512_sub_ps(r_ab_y_0, sbe_jy_0); - __m512 r_ab2_y_1 = _mm512_sub_ps(r_ab_y_1, sbe_jy_1); - - // # 64 MULTs, 32 ADDs - __m512 r_ab2_n2_0 = _mm512_fmadd_ps(r_ab2_x_0, r_ab2_x_0, _mm512_mul_ps(r_ab2_y_0, r_ab2_y_0)); - __m512 r_ab2_n2_1 = _mm512_fmadd_ps(r_ab2_x_1, r_ab2_x_1, _mm512_mul_ps(r_ab2_y_1, r_ab2_y_1)); - - r_ab2_n2_0 = _mm512_max_ps(r_ab2_n2_0, v_eps_sq); - r_ab2_n2_1 = _mm512_max_ps(r_ab2_n2_1, v_eps_sq); - - // # 32 DIVs, 32 SQRTs - __m512 inv_r_ab2_n_0 = _mm512_rsqrt14_ps(r_ab2_n2_0); - __m512 inv_r_ab2_n_1 = _mm512_rsqrt14_ps(r_ab2_n2_1); - - // # 32 MULTs - __m512 r_ab2_n_0 = _mm512_mul_ps(r_ab2_n2_0, inv_r_ab2_n_0); - __m512 r_ab2_n_1 = _mm512_mul_ps(r_ab2_n2_1, inv_r_ab2_n_1); - - // # 32 ADDs - __m512 r_sum_0 = _mm512_add_ps(r_ab_n_0, r_ab2_n_0); - __m512 r_sum_1 = _mm512_add_ps(r_ab_n_1, r_ab2_n_1); - - // # 32 MULTs, 32 ADDs - __m512 two_b_sq_0 = _mm512_fmsub_ps(r_sum_0, r_sum_0, sb_jsq_0); - __m512 two_b_sq_1 = _mm512_fmsub_ps(r_sum_1, r_sum_1, sb_jsq_1); - - two_b_sq_0 = _mm512_max_ps(two_b_sq_0, v_eps_sq); - two_b_sq_1 = _mm512_max_ps(two_b_sq_1, v_eps_sq); - - // # 32 DIVs, 32 SQRTs - __m512 inv_two_b_0 = _mm512_rsqrt14_ps(two_b_sq_0); - __m512 inv_two_b_1 = _mm512_rsqrt14_ps(two_b_sq_1); - - // # 32 MULTs - __m512 two_b_0 = _mm512_mul_ps(two_b_sq_0, inv_two_b_0); - __m512 two_b_1 = _mm512_mul_ps(two_b_sq_1, inv_two_b_1); - - // # 32 MULTs - __m512 e_arg_0 = _mm512_mul_ps(two_b_0, v_neg_2s_inv); - __m512 e_arg_1 = _mm512_mul_ps(two_b_1, v_neg_2s_inv); - - e_arg_0 = _mm512_max_ps(e_arg_0, v_exp_min); - e_arg_1 = _mm512_max_ps(e_arg_1, v_exp_min); - - // # 512 MULTs, 448 ADDs - __m512 e_val_0 = _avx512_exp_ps(e_arg_0); - __m512 e_val_1 = _avx512_exp_ps(e_arg_1); - - // # 96 MULTs - __m512 numer_inv_two_b_0 = _mm512_mul_ps(_mm512_mul_ps(r_sum_0, v_neg_v0_div_2s), _mm512_mul_ps(e_val_0, inv_two_b_0)); - __m512 numer_inv_two_b_1 = _mm512_mul_ps(_mm512_mul_ps(r_sum_1, v_neg_v0_div_2s), _mm512_mul_ps(e_val_1, inv_two_b_1)); - - // # 32 MULTs - __m512 sm_r_ab_0 = _mm512_mul_ps(numer_inv_two_b_0, inv_r_ab_n_0); - __m512 sm_r_ab_1 = _mm512_mul_ps(numer_inv_two_b_1, inv_r_ab_n_1); - - // # 32 MULTs - __m512 sm_r_ab2_0 = _mm512_mul_ps(numer_inv_two_b_0, inv_r_ab2_n_0); - __m512 sm_r_ab2_1 = _mm512_mul_ps(numer_inv_two_b_1, inv_r_ab2_n_1); - - // # 128 MULTs, 64 ADDs - __m512 f_x_0 = _mm512_fmadd_ps(r_ab2_x_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_x_0, sm_r_ab_0)); - __m512 f_x_1 = _mm512_fmadd_ps(r_ab2_x_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_x_1, sm_r_ab_1)); - __m512 f_y_0 = _mm512_fmadd_ps(r_ab2_y_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_y_0, sm_r_ab_0)); - __m512 f_y_1 = _mm512_fmadd_ps(r_ab2_y_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_y_1, sm_r_ab_1)); - - // # 64 MULTs, 32 ADDs - __m512 f_norm_sq_0 = _mm512_fmadd_ps(f_x_0, f_x_0, _mm512_mul_ps(f_y_0, f_y_0)); - __m512 f_norm_sq_1 = _mm512_fmadd_ps(f_x_1, f_x_1, _mm512_mul_ps(f_y_1, f_y_1)); - - // # 32 MULTs - __m512 rhs_0 = _mm512_mul_ps(f_norm_sq_0, v_cos_phi_sq); - __m512 rhs_1 = _mm512_mul_ps(f_norm_sq_1, v_cos_phi_sq); - - // # 64 MULTs, 32 ADDs - __m512 e_dot_f_0 = _mm512_fmadd_ps(eix, f_x_0, _mm512_mul_ps(eiy, f_y_0)); - __m512 e_dot_f_1 = _mm512_fmadd_ps(eix, f_x_1, _mm512_mul_ps(eiy, f_y_1)); - - // # 32 MULTs - __m512 e_dot_f_sq_0 = _mm512_mul_ps(e_dot_f_0, e_dot_f_0); - __m512 e_dot_f_sq_1 = _mm512_mul_ps(e_dot_f_1, e_dot_f_1); - - __mmask16 c1_0 = _mm512_cmp_ps_mask(e_dot_f_0, v_zero, _CMP_LT_OQ); - __mmask16 c1_1 = _mm512_cmp_ps_mask(e_dot_f_1, v_zero, _CMP_LT_OQ); - - __mmask16 c2_0 = _mm512_cmp_ps_mask(e_dot_f_sq_0, rhs_0, _CMP_GT_OQ); - __mmask16 c2_1 = _mm512_cmp_ps_mask(e_dot_f_sq_1, rhs_1, _CMP_GT_OQ); - - __mmask16 cond_0 = c1_0 & c2_0; - __mmask16 cond_1 = c1_1 & c2_1; - - __m512 w_val_0 = _mm512_mask_blend_ps(cond_0, v_dt_neg, v_c_neg_dt); - __m512 w_val_1 = _mm512_mask_blend_ps(cond_1, v_dt_neg, v_c_neg_dt); - - __m512 f_x_safe_0 = _mm512_maskz_mov_ps(active_0, f_x_0); - __m512 f_x_safe_1 = _mm512_maskz_mov_ps(active_1, f_x_1); - __m512 f_y_safe_0 = _mm512_maskz_mov_ps(active_0, f_y_0); - __m512 f_y_safe_1 = _mm512_maskz_mov_ps(active_1, f_y_1); - - // # 64 MULTs, 64 ADDs - acc_x_0 = _mm512_fmadd_ps(f_x_safe_0, w_val_0, acc_x_0); - acc_x_1 = _mm512_fmadd_ps(f_x_safe_1, w_val_1, acc_x_1); - acc_y_0 = _mm512_fmadd_ps(f_y_safe_0, w_val_0, acc_y_0); - acc_y_1 = _mm512_fmadd_ps(f_y_safe_1, w_val_1, acc_y_1); - } - - // # 64 ADDs - f_alpha_dt_x += _mm512_reduce_add_ps(_mm512_add_ps(acc_x_0, acc_x_1)); - f_alpha_dt_y += _mm512_reduce_add_ps(_mm512_add_ps(acc_y_0, acc_y_1)); - - for (int k = 0; k < board.n_obstacles; k++) { - - // # 2 ADDs - float v1_x = pos_x[i] - board.obstacles[k].start_x; - float v1_y = pos_y[i] - board.obstacles[k].start_y; - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - // # 3 MULTs, 1 ADD - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0f) - projection = 0.0f; - else if (projection > 1.0f) - projection = 1.0f; - - // # 4 ADDs, 2 MULTs - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); - - // # 1 SQRT, 2 MULTs, 1 ADD - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - // # 4 MULTs, 1 EXP, 1 DIV - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - float f_alpha_B_neg_y = r_aB_y * scalar_mult; - - float w_val = dt_neg; - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f < cos_phi) - w_val = c_neg_times_dt; - - // # 2 MULTS - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // # 2 ADDs - f_alpha_dt_x += f_alpha_B_dt_x; - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // # 2 ADDs - w_x[i] += f_alpha_dt_x; - w_y[i] += f_alpha_dt_y; - } - - for (int i = 0; i < n_p_pad; i += 16) { - __m512 w_x_i_vec = _mm512_loadu_ps(&w_x[i]); - __m512 w_y_i_vec = _mm512_loadu_ps(&w_y[i]); - __m512 v_max_i_vec = _mm512_loadu_ps(&v_max[i]); - __m512 pos_x_i_vec = _mm512_loadu_ps(&pos_x[i]); - __m512 pos_y_i_vec = _mm512_loadu_ps(&pos_y[i]); - __m512 destination_y_i_vec = _mm512_loadu_ps(&destination_y[i]); - __m512 destination_x_i_vec = _mm512_loadu_ps(&destination_x[i]); - __m512 desired_v0_i_vec = _mm512_loadu_ps(&desired_v0[i]); - - __m512i color_i_vec = _mm512_loadu_si512((__m512i *)&color[i]); - - // # 32 MULTs, 16 ADDs - __m512 w_norm_sq = _mm512_fmadd_ps(w_x_i_vec, w_x_i_vec, _mm512_mul_ps(w_y_i_vec, w_y_i_vec)); - - // # 16 SQRTs, 16 DIVs - __m512 w_norm_inv = _mm512_rsqrt14_ps(w_norm_sq); - - // # 16 MULTs - __m512 correction_factor = _mm512_min_ps(_mm512_set1_ps(1.0f), _mm512_mul_ps(v_max_i_vec, w_norm_inv)); - - // # 32 MULTs - __m512 curr_vel_x_i_vec = _mm512_mul_ps(w_x_i_vec, correction_factor); - __m512 curr_vel_y_i_vec = _mm512_mul_ps(w_y_i_vec, correction_factor); - - __m512 dt_vec = _mm512_set1_ps(dt); - - // # 32 MULTs, 32 ADDs - pos_x_i_vec = _mm512_fmadd_ps(curr_vel_x_i_vec, dt_vec, pos_x_i_vec); - pos_y_i_vec = _mm512_fmadd_ps(curr_vel_y_i_vec, dt_vec, pos_y_i_vec); - - __mmask16 cond_1_left = _mm512_cmpeq_epi32_mask(color_i_vec, _mm512_setzero_si512()); - __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); - __mmask16 cond_1 = cond_1_left & cond_1_right; - - pos_x_i_vec = _mm512_mask_blend_ps(cond_1, pos_x_i_vec, _mm512_set1_ps(1.0f)); - destination_y_i_vec = _mm512_mask_blend_ps(cond_1, destination_y_i_vec, pos_y_i_vec); - w_x_i_vec = _mm512_mask_blend_ps(cond_1, w_x_i_vec, desired_v0_i_vec); - w_y_i_vec = _mm512_mask_blend_ps(cond_1, w_y_i_vec, _mm512_setzero_ps()); - curr_vel_x_i_vec = _mm512_mask_blend_ps(cond_1, curr_vel_x_i_vec, w_x_i_vec); - curr_vel_y_i_vec = _mm512_mask_blend_ps(cond_1, curr_vel_y_i_vec, w_y_i_vec); - - __mmask16 cond_2_left = _mm512_cmpeq_epi32_mask(color_i_vec, _mm512_set1_epi32(1)); - __mmask16 cond_2_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(1.5f), _CMP_LT_OQ); - __mmask16 cond_2 = cond_2_left & cond_2_right; - - pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE)); - destination_y_i_vec = _mm512_mask_blend_ps(cond_2, destination_y_i_vec, pos_y_i_vec); - __m512 neg_desired_v0 = _mm512_xor_ps(desired_v0_i_vec, _mm512_set1_ps(-0.0f)); - w_x_i_vec = _mm512_mask_blend_ps(cond_2, w_x_i_vec, neg_desired_v0); - w_y_i_vec = _mm512_mask_blend_ps(cond_2, w_y_i_vec, _mm512_setzero_ps()); - curr_vel_x_i_vec = _mm512_mask_blend_ps(cond_2, curr_vel_x_i_vec, w_x_i_vec); - curr_vel_y_i_vec = _mm512_mask_blend_ps(cond_2, curr_vel_y_i_vec, w_y_i_vec); - - pos_y_i_vec = _mm512_max_ps(pos_y_i_vec, _mm512_set1_ps(WALL_EPS)); - pos_y_i_vec = _mm512_min_ps(pos_y_i_vec, _mm512_set1_ps(BOARD_WIDTH_MINUS_WALL_EPS)); - - // # 32 ADDs - __m512 ex_vec = _mm512_sub_ps(destination_x_i_vec, pos_x_i_vec); - __m512 ey_vec = _mm512_sub_ps(destination_y_i_vec, pos_y_i_vec); - - // # 32 MULTs, 16 ADDs - __m512 e_norm_sq = _mm512_fmadd_ps(ex_vec, ex_vec, _mm512_mul_ps(ey_vec, ey_vec)); - - // # 16 SQRTs, 16 DIVs - __m512 e_norm_inv = _mm512_rsqrt14_ps(e_norm_sq); - - // # 32 MULTs - __m512 e_x_i_vec = _mm512_mul_ps(ex_vec, e_norm_inv); - __m512 e_y_i_vec = _mm512_mul_ps(ey_vec, e_norm_inv); - - // # 32 MULTs, 16 ADDs - __m512 vel_norm_sq = _mm512_fmadd_ps(curr_vel_x_i_vec, curr_vel_x_i_vec, _mm512_mul_ps(curr_vel_y_i_vec, curr_vel_y_i_vec)); - - // # 16 MULTs - __m512 sb_sq_i_vec = _mm512_mul_ps(vel_norm_sq, _mm512_set1_ps(delta_t_sq)); - - // # 16 SQRTs - __m512 vel_norm_dt = _mm512_sqrt_ps(sb_sq_i_vec); - - _mm512_storeu_ps(&curr_vel_x[i], curr_vel_x_i_vec); - _mm512_storeu_ps(&curr_vel_y[i], curr_vel_y_i_vec); - _mm512_storeu_ps(&w_x[i], w_x_i_vec); - _mm512_storeu_ps(&w_y[i], w_y_i_vec); - _mm512_storeu_ps(&pos_x[i], pos_x_i_vec); - _mm512_storeu_ps(&pos_y[i], pos_y_i_vec); - _mm512_storeu_ps(&e_x[i], e_x_i_vec); - _mm512_storeu_ps(&e_y[i], e_y_i_vec); - _mm512_storeu_ps(&destination_y[i], destination_y_i_vec); - _mm512_storeu_ps(&sb_sq[i], sb_sq_i_vec); - - // # 32 MULTs - _mm512_storeu_ps(&sb_e_x[i], _mm512_mul_ps(e_x_i_vec, vel_norm_dt)); - _mm512_storeu_ps(&sb_e_y[i], _mm512_mul_ps(e_y_i_vec, vel_norm_dt)); - } - -#if !BENCHMARK - for (int i = 0; i < n_p; i++) { - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved.cpp similarity index 99% rename from variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved.cpp rename to variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved.cpp index fa51a70..e01412e 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved.cpp @@ -1,5 +1,5 @@ // # Changes: - used _mm512_mask3_fmadd_ps, to save one masking instruction -// - avoid doing an expensive brodcast for index vectors update, update them through additions instead +// - avoid doing an expensive broadcast for index vectors update, update them through additions instead // - replace array allocation + load with a direct set for lane_off initialization // # Predecessor: variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix.cpp diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved_moreilp.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp.cpp similarity index 100% rename from variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_expfix_improved_moreilp.cpp rename to variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt_improved_moreilp.cpp diff --git a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.cpp b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.cpp index 67b5f14..1548d65 100644 --- a/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.cpp +++ b/variants/variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_4.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0f #define BOARD_LENGTH 50.0f +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0f +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5f #define WALL_EPS 1e-3f #define BOARD_WIDTH_MINUS_WALL_EPS 9.999f @@ -160,21 +162,46 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 256 MULTs, 224 ADDs static inline __m512 _avx512_exp_ps(__m512 x) { + const __m512 log2e = _mm512_set1_ps(1.4426950408889634f); + + // # 16 MULTs __m512 nf = _mm512_roundscale_ps(_mm512_mul_ps(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512 ln2 = _mm512_set1_ps(0.6931471805599453f); + + // # 16 MULTs, 16 ADDs __m512 r = _mm512_fnmadd_ps(nf, ln2, x); - const __m512 c5 = _mm512_set1_ps(1.0f / 120.0f); - const __m512 c4 = _mm512_set1_ps(1.0f / 24.0f); - const __m512 c3 = _mm512_set1_ps(1.0f / 6.0f); - const __m512 c2 = _mm512_set1_ps(0.5f); - const __m512 c1 = _mm512_set1_ps(1.0f); - const __m512 c0 = _mm512_set1_ps(1.0f); + const __m512 c13 = _mm512_set1_ps(c13_scalar); + const __m512 c12 = _mm512_set1_ps(c12_scalar); + const __m512 c11 = _mm512_set1_ps(c11_scalar); + const __m512 c10 = _mm512_set1_ps(c10_scalar); + const __m512 c9 = _mm512_set1_ps(c9_scalar); + const __m512 c8 = _mm512_set1_ps(c8_scalar); + const __m512 c7 = _mm512_set1_ps(c7_scalar); + const __m512 c6 = _mm512_set1_ps(c6_scalar); + const __m512 c5 = _mm512_set1_ps(c5_scalar); + const __m512 c4 = _mm512_set1_ps(c4_scalar); + const __m512 c3 = _mm512_set1_ps(c3_scalar); + const __m512 c2 = _mm512_set1_ps(c2_scalar); + const __m512 c1 = _mm512_set1_ps(c1_scalar); + const __m512 c0 = _mm512_set1_ps(c0_scalar); - __m512 p = c5; + __m512 p = c13; + + // # 13 * (16 MULTs, 16 ADDs) + // # = 208 MULTs, 208 ADDs + p = _mm512_fmadd_ps(p, r, c12); + p = _mm512_fmadd_ps(p, r, c11); + p = _mm512_fmadd_ps(p, r, c10); + p = _mm512_fmadd_ps(p, r, c9); + p = _mm512_fmadd_ps(p, r, c8); + p = _mm512_fmadd_ps(p, r, c7); + p = _mm512_fmadd_ps(p, r, c6); + p = _mm512_fmadd_ps(p, r, c5); p = _mm512_fmadd_ps(p, r, c4); p = _mm512_fmadd_ps(p, r, c3); p = _mm512_fmadd_ps(p, r, c2); @@ -186,6 +213,7 @@ static inline __m512 _avx512_exp_ps(__m512 x) { __m512i scaled = _mm512_slli_epi32(biased, 23); __m512 pow2n = _mm512_castsi512_ps(scaled); + // # 16 MULTs return _mm512_mul_ps(p, pow2n); } @@ -224,11 +252,42 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512 v_np = _mm512_set1_ps((float)n_p); const __m512 v_exp_min = _mm512_set1_ps(-87.0f); + // # ADDs: n_iter * ( + // # n_p * 132 + + // # n_p * n_p_pad * 29 + + // # n_p * board.n_obstacles * 10 + + // # n_p_pad * 7 + // # ) + + // # MULTs: n_iter * ( + // # n_p * 4 + + // # n_p * n_p_pad * 40 + + // # n_p * board.n_obstacles * 13 + + // # n_p_pad * 15 + // # ) + + // # DIVs: n_iter * ( + // # n_p * n_p_pad + + // # n_p * board.n_obstacles + + // # n_p_pad * 2 + // # ) + + // # SQRTs: n_iter * ( + // # n_p * n_p_pad * 3 + + // # n_p * board.n_obstacles + + // # n_p_pad * 3 + // # ) + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + + // # 2 MULTs float desired_velocity_x = e_x[i] * desired_v0[i]; float desired_velocity_y = e_y[i] * desired_v0[i]; + // # 2 ADDs, 2 MULTs float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; @@ -249,6 +308,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 acc_y_3 = _mm512_setzero_ps(); for (int j = 0; j < n_p_pad; j += 64) { + + // # 64 ADDs __m512 j_idx_0 = _mm512_add_ps(_mm512_set1_ps((float)j), lane_off); __m512 j_idx_1 = _mm512_add_ps(_mm512_set1_ps((float)j + 16), lane_off); __m512 j_idx_2 = _mm512_add_ps(_mm512_set1_ps((float)j + 32), lane_off); @@ -294,61 +355,73 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 sb_jsq_2 = _mm512_loadu_ps(&sb_sq[j + 32]); __m512 sb_jsq_3 = _mm512_loadu_ps(&sb_sq[j + 48]); + // # 64 ADDs __m512 r_ab_x_0 = _mm512_sub_ps(pix, pjx_0); __m512 r_ab_x_1 = _mm512_sub_ps(pix, pjx_1); __m512 r_ab_x_2 = _mm512_sub_ps(pix, pjx_2); __m512 r_ab_x_3 = _mm512_sub_ps(pix, pjx_3); + // # 64 ADDs __m512 r_ab_y_0 = _mm512_sub_ps(piy, pjy_0); __m512 r_ab_y_1 = _mm512_sub_ps(piy, pjy_1); __m512 r_ab_y_2 = _mm512_sub_ps(piy, pjy_2); __m512 r_ab_y_3 = _mm512_sub_ps(piy, pjy_3); + // # 128 MULTs, 64 ADDs __m512 r_ab_n2_0 = _mm512_fmadd_ps(r_ab_x_0, r_ab_x_0, _mm512_mul_ps(r_ab_y_0, r_ab_y_0)); __m512 r_ab_n2_1 = _mm512_fmadd_ps(r_ab_x_1, r_ab_x_1, _mm512_mul_ps(r_ab_y_1, r_ab_y_1)); __m512 r_ab_n2_2 = _mm512_fmadd_ps(r_ab_x_2, r_ab_x_2, _mm512_mul_ps(r_ab_y_2, r_ab_y_2)); __m512 r_ab_n2_3 = _mm512_fmadd_ps(r_ab_x_3, r_ab_x_3, _mm512_mul_ps(r_ab_y_3, r_ab_y_3)); + // # 64 SQRTs __m512 r_ab_n_0 = _mm512_sqrt_ps(r_ab_n2_0); __m512 r_ab_n_1 = _mm512_sqrt_ps(r_ab_n2_1); __m512 r_ab_n_2 = _mm512_sqrt_ps(r_ab_n2_2); __m512 r_ab_n_3 = _mm512_sqrt_ps(r_ab_n2_3); + // # 64 ADDs __m512 r_ab2_x_0 = _mm512_sub_ps(r_ab_x_0, sbe_jx_0); __m512 r_ab2_x_1 = _mm512_sub_ps(r_ab_x_1, sbe_jx_1); __m512 r_ab2_x_2 = _mm512_sub_ps(r_ab_x_2, sbe_jx_2); __m512 r_ab2_x_3 = _mm512_sub_ps(r_ab_x_3, sbe_jx_3); + // # 64 ADDs __m512 r_ab2_y_0 = _mm512_sub_ps(r_ab_y_0, sbe_jy_0); __m512 r_ab2_y_1 = _mm512_sub_ps(r_ab_y_1, sbe_jy_1); __m512 r_ab2_y_2 = _mm512_sub_ps(r_ab_y_2, sbe_jy_2); __m512 r_ab2_y_3 = _mm512_sub_ps(r_ab_y_3, sbe_jy_3); + // # 128 MULTs, 64 ADDs __m512 r_ab2_n2_0 = _mm512_fmadd_ps(r_ab2_x_0, r_ab2_x_0, _mm512_mul_ps(r_ab2_y_0, r_ab2_y_0)); __m512 r_ab2_n2_1 = _mm512_fmadd_ps(r_ab2_x_1, r_ab2_x_1, _mm512_mul_ps(r_ab2_y_1, r_ab2_y_1)); __m512 r_ab2_n2_2 = _mm512_fmadd_ps(r_ab2_x_2, r_ab2_x_2, _mm512_mul_ps(r_ab2_y_2, r_ab2_y_2)); __m512 r_ab2_n2_3 = _mm512_fmadd_ps(r_ab2_x_3, r_ab2_x_3, _mm512_mul_ps(r_ab2_y_3, r_ab2_y_3)); + // # 64 SQRTs __m512 r_ab2_n_0 = _mm512_sqrt_ps(r_ab2_n2_0); __m512 r_ab2_n_1 = _mm512_sqrt_ps(r_ab2_n2_1); __m512 r_ab2_n_2 = _mm512_sqrt_ps(r_ab2_n2_2); __m512 r_ab2_n_3 = _mm512_sqrt_ps(r_ab2_n2_3); + // # 64 ADDs __m512 r_sum_0 = _mm512_add_ps(r_ab_n_0, r_ab2_n_0); __m512 r_sum_1 = _mm512_add_ps(r_ab_n_1, r_ab2_n_1); __m512 r_sum_2 = _mm512_add_ps(r_ab_n_2, r_ab2_n_2); __m512 r_sum_3 = _mm512_add_ps(r_ab_n_3, r_ab2_n_3); + // # 64 MULTs, 64 ADDs __m512 two_b_sq_0 = _mm512_fmsub_ps(r_sum_0, r_sum_0, sb_jsq_0); __m512 two_b_sq_1 = _mm512_fmsub_ps(r_sum_1, r_sum_1, sb_jsq_1); __m512 two_b_sq_2 = _mm512_fmsub_ps(r_sum_2, r_sum_2, sb_jsq_2); __m512 two_b_sq_3 = _mm512_fmsub_ps(r_sum_3, r_sum_3, sb_jsq_3); + // # 64 SQRTs __m512 two_b_0 = _mm512_sqrt_ps(two_b_sq_0); __m512 two_b_1 = _mm512_sqrt_ps(two_b_sq_1); __m512 two_b_2 = _mm512_sqrt_ps(two_b_sq_2); __m512 two_b_3 = _mm512_sqrt_ps(two_b_sq_3); + // # 64 MULTs __m512 e_arg_0 = _mm512_mul_ps(two_b_0, v_neg_2s_inv); __m512 e_arg_1 = _mm512_mul_ps(two_b_1, v_neg_2s_inv); __m512 e_arg_2 = _mm512_mul_ps(two_b_2, v_neg_2s_inv); @@ -359,16 +432,19 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c e_arg_2 = _mm512_max_ps(e_arg_2, v_exp_min); e_arg_3 = _mm512_max_ps(e_arg_3, v_exp_min); + // # 1024 MULTs, 896 ADDs __m512 e_val_0 = _avx512_exp_ps(e_arg_0); __m512 e_val_1 = _avx512_exp_ps(e_arg_1); __m512 e_val_2 = _avx512_exp_ps(e_arg_2); __m512 e_val_3 = _avx512_exp_ps(e_arg_3); + // # 128 MULTs __m512 numer_0 = _mm512_mul_ps(_mm512_mul_ps(r_sum_0, v_neg_v0_div_2s), e_val_0); __m512 numer_1 = _mm512_mul_ps(_mm512_mul_ps(r_sum_1, v_neg_v0_div_2s), e_val_1); __m512 numer_2 = _mm512_mul_ps(_mm512_mul_ps(r_sum_2, v_neg_v0_div_2s), e_val_2); __m512 numer_3 = _mm512_mul_ps(_mm512_mul_ps(r_sum_3, v_neg_v0_div_2s), e_val_3); + // # 128 MULTs __m512 denom_0 = _mm512_mul_ps(_mm512_mul_ps(two_b_0, r_ab_n_0), r_ab2_n_0); __m512 denom_1 = _mm512_mul_ps(_mm512_mul_ps(two_b_1, r_ab_n_1), r_ab2_n_1); __m512 denom_2 = _mm512_mul_ps(_mm512_mul_ps(two_b_2, r_ab_n_2), r_ab2_n_2); @@ -379,46 +455,55 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 denom_safe_2 = _mm512_mask_blend_ps(active_2, v_one, denom_2); __m512 denom_safe_3 = _mm512_mask_blend_ps(active_3, v_one, denom_3); + // # 64 DIVs __m512 smc_0 = _mm512_div_ps(numer_0, denom_safe_0); __m512 smc_1 = _mm512_div_ps(numer_1, denom_safe_1); __m512 smc_2 = _mm512_div_ps(numer_2, denom_safe_2); __m512 smc_3 = _mm512_div_ps(numer_3, denom_safe_3); + // # 64 MULTs __m512 sm_r_ab_0 = _mm512_mul_ps(smc_0, r_ab2_n_0); __m512 sm_r_ab_1 = _mm512_mul_ps(smc_1, r_ab2_n_1); __m512 sm_r_ab_2 = _mm512_mul_ps(smc_2, r_ab2_n_2); __m512 sm_r_ab_3 = _mm512_mul_ps(smc_3, r_ab2_n_3); + // # 64 MULTs __m512 sm_r_ab2_0 = _mm512_mul_ps(smc_0, r_ab_n_0); __m512 sm_r_ab2_1 = _mm512_mul_ps(smc_1, r_ab_n_1); __m512 sm_r_ab2_2 = _mm512_mul_ps(smc_2, r_ab_n_2); __m512 sm_r_ab2_3 = _mm512_mul_ps(smc_3, r_ab_n_3); + // # 128 MULTs, 64 ADDs __m512 f_x_0 = _mm512_fmadd_ps(r_ab2_x_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_x_0, sm_r_ab_0)); __m512 f_x_1 = _mm512_fmadd_ps(r_ab2_x_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_x_1, sm_r_ab_1)); __m512 f_x_2 = _mm512_fmadd_ps(r_ab2_x_2, sm_r_ab2_2, _mm512_mul_ps(r_ab_x_2, sm_r_ab_2)); __m512 f_x_3 = _mm512_fmadd_ps(r_ab2_x_3, sm_r_ab2_3, _mm512_mul_ps(r_ab_x_3, sm_r_ab_3)); + // # 128 MULTs, 64 ADDs __m512 f_y_0 = _mm512_fmadd_ps(r_ab2_y_0, sm_r_ab2_0, _mm512_mul_ps(r_ab_y_0, sm_r_ab_0)); __m512 f_y_1 = _mm512_fmadd_ps(r_ab2_y_1, sm_r_ab2_1, _mm512_mul_ps(r_ab_y_1, sm_r_ab_1)); __m512 f_y_2 = _mm512_fmadd_ps(r_ab2_y_2, sm_r_ab2_2, _mm512_mul_ps(r_ab_y_2, sm_r_ab_2)); __m512 f_y_3 = _mm512_fmadd_ps(r_ab2_y_3, sm_r_ab2_3, _mm512_mul_ps(r_ab_y_3, sm_r_ab_3)); + // # 128 MULTs, 64 ADDs __m512 f_norm_sq_0 = _mm512_fmadd_ps(f_x_0, f_x_0, _mm512_mul_ps(f_y_0, f_y_0)); __m512 f_norm_sq_1 = _mm512_fmadd_ps(f_x_1, f_x_1, _mm512_mul_ps(f_y_1, f_y_1)); __m512 f_norm_sq_2 = _mm512_fmadd_ps(f_x_2, f_x_2, _mm512_mul_ps(f_y_2, f_y_2)); __m512 f_norm_sq_3 = _mm512_fmadd_ps(f_x_3, f_x_3, _mm512_mul_ps(f_y_3, f_y_3)); + // # 64 MULTs __m512 rhs_0 = _mm512_mul_ps(f_norm_sq_0, v_cos_phi_sq); __m512 rhs_1 = _mm512_mul_ps(f_norm_sq_1, v_cos_phi_sq); __m512 rhs_2 = _mm512_mul_ps(f_norm_sq_2, v_cos_phi_sq); __m512 rhs_3 = _mm512_mul_ps(f_norm_sq_3, v_cos_phi_sq); + // # 128 MULTs, 64 ADDs __m512 e_dot_f_0 = _mm512_fmadd_ps(eix, f_x_0, _mm512_mul_ps(eiy, f_y_0)); __m512 e_dot_f_1 = _mm512_fmadd_ps(eix, f_x_1, _mm512_mul_ps(eiy, f_y_1)); __m512 e_dot_f_2 = _mm512_fmadd_ps(eix, f_x_2, _mm512_mul_ps(eiy, f_y_2)); __m512 e_dot_f_3 = _mm512_fmadd_ps(eix, f_x_3, _mm512_mul_ps(eiy, f_y_3)); + // # 64 MULTs __m512 e_dot_f_sq_0 = _mm512_mul_ps(e_dot_f_0, e_dot_f_0); __m512 e_dot_f_sq_1 = _mm512_mul_ps(e_dot_f_1, e_dot_f_1); __m512 e_dot_f_sq_2 = _mm512_mul_ps(e_dot_f_2, e_dot_f_2); @@ -454,26 +539,31 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 f_y_safe_2 = _mm512_maskz_mov_ps(active_2, f_y_2); __m512 f_y_safe_3 = _mm512_maskz_mov_ps(active_3, f_y_3); + // # 64 MULTs, 64 ADDs acc_x_0 = _mm512_fmadd_ps(f_x_safe_0, w_val_0, acc_x_0); acc_x_1 = _mm512_fmadd_ps(f_x_safe_1, w_val_1, acc_x_1); acc_x_2 = _mm512_fmadd_ps(f_x_safe_2, w_val_2, acc_x_2); acc_x_3 = _mm512_fmadd_ps(f_x_safe_3, w_val_3, acc_x_3); + // # 64 MULTs, 64 ADDs acc_y_0 = _mm512_fmadd_ps(f_y_safe_0, w_val_0, acc_y_0); acc_y_1 = _mm512_fmadd_ps(f_y_safe_1, w_val_1, acc_y_1); acc_y_2 = _mm512_fmadd_ps(f_y_safe_2, w_val_2, acc_y_2); acc_y_3 = _mm512_fmadd_ps(f_y_safe_3, w_val_3, acc_y_3); } - + // # 128 ADDs f_alpha_dt_x += _mm512_reduce_add_ps(_mm512_add_ps(_mm512_add_ps(acc_x_0, acc_x_1), _mm512_add_ps(acc_x_2, acc_x_3))); f_alpha_dt_y += _mm512_reduce_add_ps(_mm512_add_ps(_mm512_add_ps(acc_y_0, acc_y_1), _mm512_add_ps(acc_y_2, acc_y_3))); for (int k = 0; k < board.n_obstacles; k++) { + + // # 2 ADDs float v1_x = pos_x[i] - board.obstacles[k].start_x; float v1_y = pos_y[i] - board.obstacles[k].start_y; float v2_x = board.obstacles[k].obstacle_v_x; float v2_y = board.obstacles[k].obstacle_v_y; + // # 3 MULTs, 1 ADD float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0f) @@ -481,12 +571,15 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0f) projection = 1.0f; + // # 4 ADDs, 2 MULTs float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; + // # 1 EXP, 4 MULTs, 1 DIV float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; float f_alpha_B_neg_x = r_aB_x * scalar_mult; float f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -497,19 +590,22 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTs float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + // # 2 ADDs f_alpha_dt_x += f_alpha_B_dt_x; f_alpha_dt_y += f_alpha_B_dt_y; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } for (int i = 0; i < n_p_pad; i += 16) { - + __m512 w_x_i_vec = _mm512_loadu_ps(&w_x[i]); __m512 w_y_i_vec = _mm512_loadu_ps(&w_y[i]); __m512 v_max_i_vec = _mm512_loadu_ps(&v_max[i]); @@ -518,22 +614,30 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512 destination_y_i_vec = _mm512_loadu_ps(&destination_y[i]); __m512 destination_x_i_vec = _mm512_loadu_ps(&destination_x[i]); __m512 desired_v0_i_vec = _mm512_loadu_ps(&desired_v0[i]); - - __m512i color_i_vec = _mm512_loadu_si512((__m512i*)&color[i]); + __m512i color_i_vec = _mm512_loadu_si512((__m512i *)&color[i]); + + // # 32 MULTs, 16 ADDs __m512 w_norm_sq = _mm512_fmadd_ps(w_x_i_vec, w_x_i_vec, _mm512_mul_ps(w_y_i_vec, w_y_i_vec)); + + // # 16 SQRTs __m512 w_norm = _mm512_sqrt_ps(w_norm_sq); + + // # 16 DIVs __m512 correction_factor = _mm512_min_ps(_mm512_set1_ps(1.0f), _mm512_div_ps(v_max_i_vec, w_norm)); + // # 32 MULTs __m512 curr_vel_x_i_vec = _mm512_mul_ps(w_x_i_vec, correction_factor); __m512 curr_vel_y_i_vec = _mm512_mul_ps(w_y_i_vec, correction_factor); __m512 dt_vec = _mm512_set1_ps(dt); + + // # 32 MULTs, 32 ADDs pos_x_i_vec = _mm512_fmadd_ps(curr_vel_x_i_vec, dt_vec, pos_x_i_vec); pos_y_i_vec = _mm512_fmadd_ps(curr_vel_y_i_vec, dt_vec, pos_y_i_vec); __mmask16 cond_1_left = _mm512_cmpeq_epi32_mask(color_i_vec, _mm512_setzero_si512()); - __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.5f), _CMP_GT_OQ); + __mmask16 cond_1_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); __mmask16 cond_1 = cond_1_left & cond_1_right; pos_x_i_vec = _mm512_mask_blend_ps(cond_1, pos_x_i_vec, _mm512_set1_ps(1.0f)); @@ -547,7 +651,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __mmask16 cond_2_right = _mm512_cmp_ps_mask(pos_x_i_vec, _mm512_set1_ps(1.5f), _CMP_LT_OQ); __mmask16 cond_2 = cond_2_left & cond_2_right; - pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH - 1.0f)); + pos_x_i_vec = _mm512_mask_blend_ps(cond_2, pos_x_i_vec, _mm512_set1_ps(BOARD_LENGTH_MINUS_ONE)); destination_y_i_vec = _mm512_mask_blend_ps(cond_2, destination_y_i_vec, pos_y_i_vec); __m512 neg_desired_v0 = _mm512_xor_ps(desired_v0_i_vec, _mm512_set1_ps(-0.0f)); w_x_i_vec = _mm512_mask_blend_ps(cond_2, w_x_i_vec, neg_desired_v0); @@ -558,16 +662,30 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i_vec = _mm512_max_ps(pos_y_i_vec, _mm512_set1_ps(WALL_EPS)); pos_y_i_vec = _mm512_min_ps(pos_y_i_vec, _mm512_set1_ps(BOARD_WIDTH_MINUS_WALL_EPS)); + // # 32 ADDs __m512 ex_vec = _mm512_sub_ps(destination_x_i_vec, pos_x_i_vec); __m512 ey_vec = _mm512_sub_ps(destination_y_i_vec, pos_y_i_vec); + + // # 32 MULTs, 16 ADDs __m512 e_norm_sq = _mm512_fmadd_ps(ex_vec, ex_vec, _mm512_mul_ps(ey_vec, ey_vec)); + + // # 16 SQRTs __m512 e_norm = _mm512_sqrt_ps(e_norm_sq); + + // # 16 DIVs __m512 e_norm_inv = _mm512_div_ps(_mm512_set1_ps(1.0f), e_norm); + + // # 32 MULTs __m512 e_x_i_vec = _mm512_mul_ps(ex_vec, e_norm_inv); __m512 e_y_i_vec = _mm512_mul_ps(ey_vec, e_norm_inv); + // # 32 MULTs, 16 ADDs __m512 vel_norm_sq = _mm512_fmadd_ps(curr_vel_x_i_vec, curr_vel_x_i_vec, _mm512_mul_ps(curr_vel_y_i_vec, curr_vel_y_i_vec)); + + // # 16 MULTs __m512 sb_sq_i_vec = _mm512_mul_ps(vel_norm_sq, _mm512_set1_ps(delta_t_sq)); + + // # 16 SQRTs __m512 vel_norm_dt = _mm512_sqrt_ps(sb_sq_i_vec); _mm512_storeu_ps(&curr_vel_x[i], curr_vel_x_i_vec); @@ -580,11 +698,12 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c _mm512_storeu_ps(&e_y[i], e_y_i_vec); _mm512_storeu_ps(&destination_y[i], destination_y_i_vec); _mm512_storeu_ps(&sb_sq[i], sb_sq_i_vec); + + // # 32 MULTs _mm512_storeu_ps(&sb_e_x[i], _mm512_mul_ps(e_x_i_vec, vel_norm_dt)); _mm512_storeu_ps(&sb_e_y[i], _mm512_mul_ps(e_y_i_vec, vel_norm_dt)); } - #if !BENCHMARK for (int i = 0; i < n_p; i++) { if (dump_path && (t % dump_interval == 0)) { diff --git a/variants/variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morrsqrt.cpp b/variants/variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp similarity index 100% rename from variants/variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morrsqrt.cpp rename to variants/variant_float_inlining_SoA_NEON_polyexp_moreavx_ilp_2_rsqrt_morersqrt.cpp diff --git a/variants/variant_float_inlining_SoA_symmetry_ILP_16_flopcount.cpp b/variants/variant_float_inlining_SoA_symmetry_ILP_16_flopcount.cpp deleted file mode 100644 index f84bd66..0000000 --- a/variants/variant_float_inlining_SoA_symmetry_ILP_16_flopcount.cpp +++ /dev/null @@ -1,1496 +0,0 @@ -// # Changes: Added ILP for j-loop with K = 16 -// # added flopcount -// FLOPCOUNT = n_iter * ( 69 * n_p ** 2 + 32 * n_p + 55 * n_obstacles * n_p ) -// # Predecessor: variant_float_inlining_SoA_symmetry_ILP_8.cpp - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - pos_x.resize(n_p); - pos_y.resize(n_p); - sb_sq.resize(n_p); - sb_e_x.resize(n_p); - sb_e_y.resize(n_p); - e_x.resize(n_p); - e_y.resize(n_p); - curr_vel_x.resize(n_p); - curr_vel_y.resize(n_p); - w_x.resize(n_p); - w_y.resize(n_p); - desired_v0.resize(n_p); - v_max.resize(n_p); - destination_x.resize(n_p); - destination_y.resize(n_p); - color.resize(n_p); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - vector r_ab_x_lut(n_p * n_p, 0.0); - vector r_ab_y_lut(n_p * n_p, 0.0); - vector r_ab_norm_lut(n_p * n_p, 0.0); - - // FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). - // section costs: overhead 4 ADD 4 MULT (+ reductions 4*K = 64 ADD, K=16) | - // distance pair 3 ADD 2 MULT 1 SQRT | - // ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | - // obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV - - // # ADDs: n_iter * ( - // # n_p * 68 + - // # 0.5 * (n_p*n_p - n_p) * 3 + - // # n_p * (n_p - 1) * 11 + - // # n_p * board.n_obstacles * 10 + - // # n_p * 7 - // # ) - - // # MULTs: n_iter * ( - // # n_p * 4 + - // # 0.5 * (n_p*n_p - n_p) * 2 + - // # n_p * (n_p - 1) * 22 + - // # n_p * board.n_obstacles * 13 + - // # n_p * 17 - // # ) - - // # DIVs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles + - // # n_p * 2 - // # ) - - // # SQRTs: n_iter * ( - // # 0.5 * (n_p*n_p - n_p) + - // # n_p * (n_p - 1) * 2 + - // # n_p * board.n_obstacles + - // # n_p * 3 - // # ) - - // # EXPs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles - // # ) - - // total with EXP = 30: n_iter * ( 69 * n_p ** 2 + 32 * n_p + 55 * n_obstacles * n_p ) - for (int t = 0; t < n_iter; t++) { - for (int i = 0; i < n_p; i++) { - - // # 1 MULT - float desired_velocity_x = e_x[i] * desired_v0[i]; - // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; - // # 1 ADD, 1 MULT - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - // # 1 ADD, 1 MULT - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; - - float e_x_i = e_x[i]; - float e_y_i = e_y[i]; - float pos_x_i = pos_x[i]; - float pos_y_i = pos_y[i]; - - // distance precompute, # each pair: 6 FLOPS, total over all i = 0.5*(n_p*n_p - n_p)*6 - for (int j = i + 1; j < n_p; j++) { - // # 1 ADD - float dx = pos_x_i - pos_x[j]; - // # 1 ADD - float dy = pos_y_i - pos_y[j]; - // # 1 ADD, 2 MULTs, 1 SQRT - float norm = sqrt(dx * dx + dy * dy); - int idx = i * n_p + j; - r_ab_x_lut[idx] = dx; - r_ab_y_lut[idx] = dy; - r_ab_norm_lut[idx] = norm; - } - - int j; - - float acc_x_0 = 0.0f, acc_x_1 = 0.0f, acc_x_2 = 0.0f, acc_x_3 = 0.0f; - float acc_x_4 = 0.0f, acc_x_5 = 0.0f, acc_x_6 = 0.0f, acc_x_7 = 0.0f; - float acc_x_8 = 0.0f, acc_x_9 = 0.0f, acc_x_10 = 0.0f, acc_x_11 = 0.0f; - float acc_x_12 = 0.0f, acc_x_13 = 0.0f, acc_x_14 = 0.0f, acc_x_15 = 0.0f; - float acc_y_0 = 0.0f, acc_y_1 = 0.0f, acc_y_2 = 0.0f, acc_y_3 = 0.0f; - float acc_y_4 = 0.0f, acc_y_5 = 0.0f, acc_y_6 = 0.0f, acc_y_7 = 0.0f; - float acc_y_8 = 0.0f, acc_y_9 = 0.0f, acc_y_10 = 0.0f, acc_y_11 = 0.0f; - float acc_y_12 = 0.0f, acc_y_13 = 0.0f, acc_y_14 = 0.0f, acc_y_15 = 0.0f; - - // ILP over the j < i half. # each lane = 66 FLOPS (per-line breakdown is the - // same as the single-lane remainder loop below), K = 16 lanes per step - for (j = 0; j + 16 < i; j += 16) { - float r_ab_x_0 = -1 * r_ab_x_lut[j * n_p + i]; - float r_ab_x_1 = -1 * r_ab_x_lut[(j + 1) * n_p + i]; - float r_ab_x_2 = -1 * r_ab_x_lut[(j + 2) * n_p + i]; - float r_ab_x_3 = -1 * r_ab_x_lut[(j + 3) * n_p + i]; - float r_ab_x_4 = -1 * r_ab_x_lut[(j + 4) * n_p + i]; - float r_ab_x_5 = -1 * r_ab_x_lut[(j + 5) * n_p + i]; - float r_ab_x_6 = -1 * r_ab_x_lut[(j + 6) * n_p + i]; - float r_ab_x_7 = -1 * r_ab_x_lut[(j + 7) * n_p + i]; - float r_ab_x_8 = -1 * r_ab_x_lut[(j + 8) * n_p + i]; - float r_ab_x_9 = -1 * r_ab_x_lut[(j + 9) * n_p + i]; - float r_ab_x_10 = -1 * r_ab_x_lut[(j + 10) * n_p + i]; - float r_ab_x_11 = -1 * r_ab_x_lut[(j + 11) * n_p + i]; - float r_ab_x_12 = -1 * r_ab_x_lut[(j + 12) * n_p + i]; - float r_ab_x_13 = -1 * r_ab_x_lut[(j + 13) * n_p + i]; - float r_ab_x_14 = -1 * r_ab_x_lut[(j + 14) * n_p + i]; - float r_ab_x_15 = -1 * r_ab_x_lut[(j + 15) * n_p + i]; - - float r_ab_y_0 = -1 * r_ab_y_lut[j * n_p + i]; - float r_ab_y_1 = -1 * r_ab_y_lut[(j + 1) * n_p + i]; - float r_ab_y_2 = -1 * r_ab_y_lut[(j + 2) * n_p + i]; - float r_ab_y_3 = -1 * r_ab_y_lut[(j + 3) * n_p + i]; - float r_ab_y_4 = -1 * r_ab_y_lut[(j + 4) * n_p + i]; - float r_ab_y_5 = -1 * r_ab_y_lut[(j + 5) * n_p + i]; - float r_ab_y_6 = -1 * r_ab_y_lut[(j + 6) * n_p + i]; - float r_ab_y_7 = -1 * r_ab_y_lut[(j + 7) * n_p + i]; - float r_ab_y_8 = -1 * r_ab_y_lut[(j + 8) * n_p + i]; - float r_ab_y_9 = -1 * r_ab_y_lut[(j + 9) * n_p + i]; - float r_ab_y_10 = -1 * r_ab_y_lut[(j + 10) * n_p + i]; - float r_ab_y_11 = -1 * r_ab_y_lut[(j + 11) * n_p + i]; - float r_ab_y_12 = -1 * r_ab_y_lut[(j + 12) * n_p + i]; - float r_ab_y_13 = -1 * r_ab_y_lut[(j + 13) * n_p + i]; - float r_ab_y_14 = -1 * r_ab_y_lut[(j + 14) * n_p + i]; - float r_ab_y_15 = -1 * r_ab_y_lut[(j + 15) * n_p + i]; - - float r_ab_norm_0 = r_ab_norm_lut[j * n_p + i]; - float r_ab_norm_1 = r_ab_norm_lut[(j + 1) * n_p + i]; - float r_ab_norm_2 = r_ab_norm_lut[(j + 2) * n_p + i]; - float r_ab_norm_3 = r_ab_norm_lut[(j + 3) * n_p + i]; - float r_ab_norm_4 = r_ab_norm_lut[(j + 4) * n_p + i]; - float r_ab_norm_5 = r_ab_norm_lut[(j + 5) * n_p + i]; - float r_ab_norm_6 = r_ab_norm_lut[(j + 6) * n_p + i]; - float r_ab_norm_7 = r_ab_norm_lut[(j + 7) * n_p + i]; - float r_ab_norm_8 = r_ab_norm_lut[(j + 8) * n_p + i]; - float r_ab_norm_9 = r_ab_norm_lut[(j + 9) * n_p + i]; - float r_ab_norm_10 = r_ab_norm_lut[(j + 10) * n_p + i]; - float r_ab_norm_11 = r_ab_norm_lut[(j + 11) * n_p + i]; - float r_ab_norm_12 = r_ab_norm_lut[(j + 12) * n_p + i]; - float r_ab_norm_13 = r_ab_norm_lut[(j + 13) * n_p + i]; - float r_ab_norm_14 = r_ab_norm_lut[(j + 14) * n_p + i]; - float r_ab_norm_15 = r_ab_norm_lut[(j + 15) * n_p + i]; - - r_ab_norm_0 = r_ab_norm_0 > EPS ? r_ab_norm_0 : EPS; - r_ab_norm_1 = r_ab_norm_1 > EPS ? r_ab_norm_1 : EPS; - r_ab_norm_2 = r_ab_norm_2 > EPS ? r_ab_norm_2 : EPS; - r_ab_norm_3 = r_ab_norm_3 > EPS ? r_ab_norm_3 : EPS; - r_ab_norm_4 = r_ab_norm_4 > EPS ? r_ab_norm_4 : EPS; - r_ab_norm_5 = r_ab_norm_5 > EPS ? r_ab_norm_5 : EPS; - r_ab_norm_6 = r_ab_norm_6 > EPS ? r_ab_norm_6 : EPS; - r_ab_norm_7 = r_ab_norm_7 > EPS ? r_ab_norm_7 : EPS; - r_ab_norm_8 = r_ab_norm_8 > EPS ? r_ab_norm_8 : EPS; - r_ab_norm_9 = r_ab_norm_9 > EPS ? r_ab_norm_9 : EPS; - r_ab_norm_10 = r_ab_norm_10 > EPS ? r_ab_norm_10 : EPS; - r_ab_norm_11 = r_ab_norm_11 > EPS ? r_ab_norm_11 : EPS; - r_ab_norm_12 = r_ab_norm_12 > EPS ? r_ab_norm_12 : EPS; - r_ab_norm_13 = r_ab_norm_13 > EPS ? r_ab_norm_13 : EPS; - r_ab_norm_14 = r_ab_norm_14 > EPS ? r_ab_norm_14 : EPS; - r_ab_norm_15 = r_ab_norm_15 > EPS ? r_ab_norm_15 : EPS; - - float r_ab2_x_0 = r_ab_x_0 - sb_e_x[j]; - float r_ab2_x_1 = r_ab_x_1 - sb_e_x[j + 1]; - float r_ab2_x_2 = r_ab_x_2 - sb_e_x[j + 2]; - float r_ab2_x_3 = r_ab_x_3 - sb_e_x[j + 3]; - float r_ab2_x_4 = r_ab_x_4 - sb_e_x[j + 4]; - float r_ab2_x_5 = r_ab_x_5 - sb_e_x[j + 5]; - float r_ab2_x_6 = r_ab_x_6 - sb_e_x[j + 6]; - float r_ab2_x_7 = r_ab_x_7 - sb_e_x[j + 7]; - float r_ab2_x_8 = r_ab_x_8 - sb_e_x[j + 8]; - float r_ab2_x_9 = r_ab_x_9 - sb_e_x[j + 9]; - float r_ab2_x_10 = r_ab_x_10 - sb_e_x[j + 10]; - float r_ab2_x_11 = r_ab_x_11 - sb_e_x[j + 11]; - float r_ab2_x_12 = r_ab_x_12 - sb_e_x[j + 12]; - float r_ab2_x_13 = r_ab_x_13 - sb_e_x[j + 13]; - float r_ab2_x_14 = r_ab_x_14 - sb_e_x[j + 14]; - float r_ab2_x_15 = r_ab_x_15 - sb_e_x[j + 15]; - - float r_ab2_y_0 = r_ab_y_0 - sb_e_y[j]; - float r_ab2_y_1 = r_ab_y_1 - sb_e_y[j + 1]; - float r_ab2_y_2 = r_ab_y_2 - sb_e_y[j + 2]; - float r_ab2_y_3 = r_ab_y_3 - sb_e_y[j + 3]; - float r_ab2_y_4 = r_ab_y_4 - sb_e_y[j + 4]; - float r_ab2_y_5 = r_ab_y_5 - sb_e_y[j + 5]; - float r_ab2_y_6 = r_ab_y_6 - sb_e_y[j + 6]; - float r_ab2_y_7 = r_ab_y_7 - sb_e_y[j + 7]; - float r_ab2_y_8 = r_ab_y_8 - sb_e_y[j + 8]; - float r_ab2_y_9 = r_ab_y_9 - sb_e_y[j + 9]; - float r_ab2_y_10 = r_ab_y_10 - sb_e_y[j + 10]; - float r_ab2_y_11 = r_ab_y_11 - sb_e_y[j + 11]; - float r_ab2_y_12 = r_ab_y_12 - sb_e_y[j + 12]; - float r_ab2_y_13 = r_ab_y_13 - sb_e_y[j + 13]; - float r_ab2_y_14 = r_ab_y_14 - sb_e_y[j + 14]; - float r_ab2_y_15 = r_ab_y_15 - sb_e_y[j + 15]; - - float r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - float r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - float r_ab2_norm_2 = sqrt(r_ab2_x_2 * r_ab2_x_2 + r_ab2_y_2 * r_ab2_y_2); - float r_ab2_norm_3 = sqrt(r_ab2_x_3 * r_ab2_x_3 + r_ab2_y_3 * r_ab2_y_3); - float r_ab2_norm_4 = sqrt(r_ab2_x_4 * r_ab2_x_4 + r_ab2_y_4 * r_ab2_y_4); - float r_ab2_norm_5 = sqrt(r_ab2_x_5 * r_ab2_x_5 + r_ab2_y_5 * r_ab2_y_5); - float r_ab2_norm_6 = sqrt(r_ab2_x_6 * r_ab2_x_6 + r_ab2_y_6 * r_ab2_y_6); - float r_ab2_norm_7 = sqrt(r_ab2_x_7 * r_ab2_x_7 + r_ab2_y_7 * r_ab2_y_7); - float r_ab2_norm_8 = sqrt(r_ab2_x_8 * r_ab2_x_8 + r_ab2_y_8 * r_ab2_y_8); - float r_ab2_norm_9 = sqrt(r_ab2_x_9 * r_ab2_x_9 + r_ab2_y_9 * r_ab2_y_9); - float r_ab2_norm_10 = sqrt(r_ab2_x_10 * r_ab2_x_10 + r_ab2_y_10 * r_ab2_y_10); - float r_ab2_norm_11 = sqrt(r_ab2_x_11 * r_ab2_x_11 + r_ab2_y_11 * r_ab2_y_11); - float r_ab2_norm_12 = sqrt(r_ab2_x_12 * r_ab2_x_12 + r_ab2_y_12 * r_ab2_y_12); - float r_ab2_norm_13 = sqrt(r_ab2_x_13 * r_ab2_x_13 + r_ab2_y_13 * r_ab2_y_13); - float r_ab2_norm_14 = sqrt(r_ab2_x_14 * r_ab2_x_14 + r_ab2_y_14 * r_ab2_y_14); - float r_ab2_norm_15 = sqrt(r_ab2_x_15 * r_ab2_x_15 + r_ab2_y_15 * r_ab2_y_15); - - r_ab2_norm_0 = r_ab2_norm_0 > EPS ? r_ab2_norm_0 : EPS; - r_ab2_norm_1 = r_ab2_norm_1 > EPS ? r_ab2_norm_1 : EPS; - r_ab2_norm_2 = r_ab2_norm_2 > EPS ? r_ab2_norm_2 : EPS; - r_ab2_norm_3 = r_ab2_norm_3 > EPS ? r_ab2_norm_3 : EPS; - r_ab2_norm_4 = r_ab2_norm_4 > EPS ? r_ab2_norm_4 : EPS; - r_ab2_norm_5 = r_ab2_norm_5 > EPS ? r_ab2_norm_5 : EPS; - r_ab2_norm_6 = r_ab2_norm_6 > EPS ? r_ab2_norm_6 : EPS; - r_ab2_norm_7 = r_ab2_norm_7 > EPS ? r_ab2_norm_7 : EPS; - r_ab2_norm_8 = r_ab2_norm_8 > EPS ? r_ab2_norm_8 : EPS; - r_ab2_norm_9 = r_ab2_norm_9 > EPS ? r_ab2_norm_9 : EPS; - r_ab2_norm_10 = r_ab2_norm_10 > EPS ? r_ab2_norm_10 : EPS; - r_ab2_norm_11 = r_ab2_norm_11 > EPS ? r_ab2_norm_11 : EPS; - r_ab2_norm_12 = r_ab2_norm_12 > EPS ? r_ab2_norm_12 : EPS; - r_ab2_norm_13 = r_ab2_norm_13 > EPS ? r_ab2_norm_13 : EPS; - r_ab2_norm_14 = r_ab2_norm_14 > EPS ? r_ab2_norm_14 : EPS; - r_ab2_norm_15 = r_ab2_norm_15 > EPS ? r_ab2_norm_15 : EPS; - - float r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - float r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - float r_ab_norm_sum_2 = r_ab_norm_2 + r_ab2_norm_2; - float r_ab_norm_sum_3 = r_ab_norm_3 + r_ab2_norm_3; - float r_ab_norm_sum_4 = r_ab_norm_4 + r_ab2_norm_4; - float r_ab_norm_sum_5 = r_ab_norm_5 + r_ab2_norm_5; - float r_ab_norm_sum_6 = r_ab_norm_6 + r_ab2_norm_6; - float r_ab_norm_sum_7 = r_ab_norm_7 + r_ab2_norm_7; - float r_ab_norm_sum_8 = r_ab_norm_8 + r_ab2_norm_8; - float r_ab_norm_sum_9 = r_ab_norm_9 + r_ab2_norm_9; - float r_ab_norm_sum_10 = r_ab_norm_10 + r_ab2_norm_10; - float r_ab_norm_sum_11 = r_ab_norm_11 + r_ab2_norm_11; - float r_ab_norm_sum_12 = r_ab_norm_12 + r_ab2_norm_12; - float r_ab_norm_sum_13 = r_ab_norm_13 + r_ab2_norm_13; - float r_ab_norm_sum_14 = r_ab_norm_14 + r_ab2_norm_14; - float r_ab_norm_sum_15 = r_ab_norm_15 + r_ab2_norm_15; - - float two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - sb_sq[j]; - float two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - sb_sq[j + 1]; - float two_b_sq_2 = r_ab_norm_sum_2 * r_ab_norm_sum_2 - sb_sq[j + 2]; - float two_b_sq_3 = r_ab_norm_sum_3 * r_ab_norm_sum_3 - sb_sq[j + 3]; - float two_b_sq_4 = r_ab_norm_sum_4 * r_ab_norm_sum_4 - sb_sq[j + 4]; - float two_b_sq_5 = r_ab_norm_sum_5 * r_ab_norm_sum_5 - sb_sq[j + 5]; - float two_b_sq_6 = r_ab_norm_sum_6 * r_ab_norm_sum_6 - sb_sq[j + 6]; - float two_b_sq_7 = r_ab_norm_sum_7 * r_ab_norm_sum_7 - sb_sq[j + 7]; - float two_b_sq_8 = r_ab_norm_sum_8 * r_ab_norm_sum_8 - sb_sq[j + 8]; - float two_b_sq_9 = r_ab_norm_sum_9 * r_ab_norm_sum_9 - sb_sq[j + 9]; - float two_b_sq_10 = r_ab_norm_sum_10 * r_ab_norm_sum_10 - sb_sq[j + 10]; - float two_b_sq_11 = r_ab_norm_sum_11 * r_ab_norm_sum_11 - sb_sq[j + 11]; - float two_b_sq_12 = r_ab_norm_sum_12 * r_ab_norm_sum_12 - sb_sq[j + 12]; - float two_b_sq_13 = r_ab_norm_sum_13 * r_ab_norm_sum_13 - sb_sq[j + 13]; - float two_b_sq_14 = r_ab_norm_sum_14 * r_ab_norm_sum_14 - sb_sq[j + 14]; - float two_b_sq_15 = r_ab_norm_sum_15 * r_ab_norm_sum_15 - sb_sq[j + 15]; - - two_b_sq_0 = two_b_sq_0 > EPS_SQ ? two_b_sq_0 : EPS_SQ; - two_b_sq_1 = two_b_sq_1 > EPS_SQ ? two_b_sq_1 : EPS_SQ; - two_b_sq_2 = two_b_sq_2 > EPS_SQ ? two_b_sq_2 : EPS_SQ; - two_b_sq_3 = two_b_sq_3 > EPS_SQ ? two_b_sq_3 : EPS_SQ; - two_b_sq_4 = two_b_sq_4 > EPS_SQ ? two_b_sq_4 : EPS_SQ; - two_b_sq_5 = two_b_sq_5 > EPS_SQ ? two_b_sq_5 : EPS_SQ; - two_b_sq_6 = two_b_sq_6 > EPS_SQ ? two_b_sq_6 : EPS_SQ; - two_b_sq_7 = two_b_sq_7 > EPS_SQ ? two_b_sq_7 : EPS_SQ; - two_b_sq_8 = two_b_sq_8 > EPS_SQ ? two_b_sq_8 : EPS_SQ; - two_b_sq_9 = two_b_sq_9 > EPS_SQ ? two_b_sq_9 : EPS_SQ; - two_b_sq_10 = two_b_sq_10 > EPS_SQ ? two_b_sq_10 : EPS_SQ; - two_b_sq_11 = two_b_sq_11 > EPS_SQ ? two_b_sq_11 : EPS_SQ; - two_b_sq_12 = two_b_sq_12 > EPS_SQ ? two_b_sq_12 : EPS_SQ; - two_b_sq_13 = two_b_sq_13 > EPS_SQ ? two_b_sq_13 : EPS_SQ; - two_b_sq_14 = two_b_sq_14 > EPS_SQ ? two_b_sq_14 : EPS_SQ; - two_b_sq_15 = two_b_sq_15 > EPS_SQ ? two_b_sq_15 : EPS_SQ; - - float two_b_0 = sqrt(two_b_sq_0); - float two_b_1 = sqrt(two_b_sq_1); - float two_b_2 = sqrt(two_b_sq_2); - float two_b_3 = sqrt(two_b_sq_3); - float two_b_4 = sqrt(two_b_sq_4); - float two_b_5 = sqrt(two_b_sq_5); - float two_b_6 = sqrt(two_b_sq_6); - float two_b_7 = sqrt(two_b_sq_7); - float two_b_8 = sqrt(two_b_sq_8); - float two_b_9 = sqrt(two_b_sq_9); - float two_b_10 = sqrt(two_b_sq_10); - float two_b_11 = sqrt(two_b_sq_11); - float two_b_12 = sqrt(two_b_sq_12); - float two_b_13 = sqrt(two_b_sq_13); - float two_b_14 = sqrt(two_b_sq_14); - float two_b_15 = sqrt(two_b_sq_15); - - float scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_2 = (r_ab_norm_sum_2 / (two_b_2 * r_ab_norm_2 * r_ab2_norm_2)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_2 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_3 = (r_ab_norm_sum_3 / (two_b_3 * r_ab_norm_3 * r_ab2_norm_3)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_3 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_4 = (r_ab_norm_sum_4 / (two_b_4 * r_ab_norm_4 * r_ab2_norm_4)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_4 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_5 = (r_ab_norm_sum_5 / (two_b_5 * r_ab_norm_5 * r_ab2_norm_5)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_5 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_6 = (r_ab_norm_sum_6 / (two_b_6 * r_ab_norm_6 * r_ab2_norm_6)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_6 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_7 = (r_ab_norm_sum_7 / (two_b_7 * r_ab_norm_7 * r_ab2_norm_7)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_7 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_8 = (r_ab_norm_sum_8 / (two_b_8 * r_ab_norm_8 * r_ab2_norm_8)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_8 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_9 = (r_ab_norm_sum_9 / (two_b_9 * r_ab_norm_9 * r_ab2_norm_9)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_9 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_10 = (r_ab_norm_sum_10 / (two_b_10 * r_ab_norm_10 * r_ab2_norm_10)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_10 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_11 = (r_ab_norm_sum_11 / (two_b_11 * r_ab_norm_11 * r_ab2_norm_11)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_11 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_12 = (r_ab_norm_sum_12 / (two_b_12 * r_ab_norm_12 * r_ab2_norm_12)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_12 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_13 = (r_ab_norm_sum_13 / (two_b_13 * r_ab_norm_13 * r_ab2_norm_13)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_13 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_14 = (r_ab_norm_sum_14 / (two_b_14 * r_ab_norm_14 * r_ab2_norm_14)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_14 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_15 = (r_ab_norm_sum_15 / (two_b_15 * r_ab_norm_15 * r_ab2_norm_15)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_15 * NEG_TWO_SIGMA_INV)); - - float scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - float scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - float scalar_mult_r_ab_2 = scalar_mult_common_2 * r_ab2_norm_2; - float scalar_mult_r_ab_3 = scalar_mult_common_3 * r_ab2_norm_3; - float scalar_mult_r_ab_4 = scalar_mult_common_4 * r_ab2_norm_4; - float scalar_mult_r_ab_5 = scalar_mult_common_5 * r_ab2_norm_5; - float scalar_mult_r_ab_6 = scalar_mult_common_6 * r_ab2_norm_6; - float scalar_mult_r_ab_7 = scalar_mult_common_7 * r_ab2_norm_7; - float scalar_mult_r_ab_8 = scalar_mult_common_8 * r_ab2_norm_8; - float scalar_mult_r_ab_9 = scalar_mult_common_9 * r_ab2_norm_9; - float scalar_mult_r_ab_10 = scalar_mult_common_10 * r_ab2_norm_10; - float scalar_mult_r_ab_11 = scalar_mult_common_11 * r_ab2_norm_11; - float scalar_mult_r_ab_12 = scalar_mult_common_12 * r_ab2_norm_12; - float scalar_mult_r_ab_13 = scalar_mult_common_13 * r_ab2_norm_13; - float scalar_mult_r_ab_14 = scalar_mult_common_14 * r_ab2_norm_14; - float scalar_mult_r_ab_15 = scalar_mult_common_15 * r_ab2_norm_15; - - float scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - float scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - float scalar_mult_r_ab2_2 = scalar_mult_common_2 * r_ab_norm_2; - float scalar_mult_r_ab2_3 = scalar_mult_common_3 * r_ab_norm_3; - float scalar_mult_r_ab2_4 = scalar_mult_common_4 * r_ab_norm_4; - float scalar_mult_r_ab2_5 = scalar_mult_common_5 * r_ab_norm_5; - float scalar_mult_r_ab2_6 = scalar_mult_common_6 * r_ab_norm_6; - float scalar_mult_r_ab2_7 = scalar_mult_common_7 * r_ab_norm_7; - float scalar_mult_r_ab2_8 = scalar_mult_common_8 * r_ab_norm_8; - float scalar_mult_r_ab2_9 = scalar_mult_common_9 * r_ab_norm_9; - float scalar_mult_r_ab2_10 = scalar_mult_common_10 * r_ab_norm_10; - float scalar_mult_r_ab2_11 = scalar_mult_common_11 * r_ab_norm_11; - float scalar_mult_r_ab2_12 = scalar_mult_common_12 * r_ab_norm_12; - float scalar_mult_r_ab2_13 = scalar_mult_common_13 * r_ab_norm_13; - float scalar_mult_r_ab2_14 = scalar_mult_common_14 * r_ab_norm_14; - float scalar_mult_r_ab2_15 = scalar_mult_common_15 * r_ab_norm_15; - - float f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_x_2 = r_ab_x_2 * scalar_mult_r_ab_2 + r_ab2_x_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_x_3 = r_ab_x_3 * scalar_mult_r_ab_3 + r_ab2_x_3 * scalar_mult_r_ab2_3; - float f_alpha_beta_neg_x_4 = r_ab_x_4 * scalar_mult_r_ab_4 + r_ab2_x_4 * scalar_mult_r_ab2_4; - float f_alpha_beta_neg_x_5 = r_ab_x_5 * scalar_mult_r_ab_5 + r_ab2_x_5 * scalar_mult_r_ab2_5; - float f_alpha_beta_neg_x_6 = r_ab_x_6 * scalar_mult_r_ab_6 + r_ab2_x_6 * scalar_mult_r_ab2_6; - float f_alpha_beta_neg_x_7 = r_ab_x_7 * scalar_mult_r_ab_7 + r_ab2_x_7 * scalar_mult_r_ab2_7; - float f_alpha_beta_neg_x_8 = r_ab_x_8 * scalar_mult_r_ab_8 + r_ab2_x_8 * scalar_mult_r_ab2_8; - float f_alpha_beta_neg_x_9 = r_ab_x_9 * scalar_mult_r_ab_9 + r_ab2_x_9 * scalar_mult_r_ab2_9; - float f_alpha_beta_neg_x_10 = r_ab_x_10 * scalar_mult_r_ab_10 + r_ab2_x_10 * scalar_mult_r_ab2_10; - float f_alpha_beta_neg_x_11 = r_ab_x_11 * scalar_mult_r_ab_11 + r_ab2_x_11 * scalar_mult_r_ab2_11; - float f_alpha_beta_neg_x_12 = r_ab_x_12 * scalar_mult_r_ab_12 + r_ab2_x_12 * scalar_mult_r_ab2_12; - float f_alpha_beta_neg_x_13 = r_ab_x_13 * scalar_mult_r_ab_13 + r_ab2_x_13 * scalar_mult_r_ab2_13; - float f_alpha_beta_neg_x_14 = r_ab_x_14 * scalar_mult_r_ab_14 + r_ab2_x_14 * scalar_mult_r_ab2_14; - float f_alpha_beta_neg_x_15 = r_ab_x_15 * scalar_mult_r_ab_15 + r_ab2_x_15 * scalar_mult_r_ab2_15; - - float f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_y_2 = r_ab_y_2 * scalar_mult_r_ab_2 + r_ab2_y_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_y_3 = r_ab_y_3 * scalar_mult_r_ab_3 + r_ab2_y_3 * scalar_mult_r_ab2_3; - float f_alpha_beta_neg_y_4 = r_ab_y_4 * scalar_mult_r_ab_4 + r_ab2_y_4 * scalar_mult_r_ab2_4; - float f_alpha_beta_neg_y_5 = r_ab_y_5 * scalar_mult_r_ab_5 + r_ab2_y_5 * scalar_mult_r_ab2_5; - float f_alpha_beta_neg_y_6 = r_ab_y_6 * scalar_mult_r_ab_6 + r_ab2_y_6 * scalar_mult_r_ab2_6; - float f_alpha_beta_neg_y_7 = r_ab_y_7 * scalar_mult_r_ab_7 + r_ab2_y_7 * scalar_mult_r_ab2_7; - float f_alpha_beta_neg_y_8 = r_ab_y_8 * scalar_mult_r_ab_8 + r_ab2_y_8 * scalar_mult_r_ab2_8; - float f_alpha_beta_neg_y_9 = r_ab_y_9 * scalar_mult_r_ab_9 + r_ab2_y_9 * scalar_mult_r_ab2_9; - float f_alpha_beta_neg_y_10 = r_ab_y_10 * scalar_mult_r_ab_10 + r_ab2_y_10 * scalar_mult_r_ab2_10; - float f_alpha_beta_neg_y_11 = r_ab_y_11 * scalar_mult_r_ab_11 + r_ab2_y_11 * scalar_mult_r_ab2_11; - float f_alpha_beta_neg_y_12 = r_ab_y_12 * scalar_mult_r_ab_12 + r_ab2_y_12 * scalar_mult_r_ab2_12; - float f_alpha_beta_neg_y_13 = r_ab_y_13 * scalar_mult_r_ab_13 + r_ab2_y_13 * scalar_mult_r_ab2_13; - float f_alpha_beta_neg_y_14 = r_ab_y_14 * scalar_mult_r_ab_14 + r_ab2_y_14 * scalar_mult_r_ab2_14; - float f_alpha_beta_neg_y_15 = r_ab_y_15 * scalar_mult_r_ab_15 + r_ab2_y_15 * scalar_mult_r_ab2_15; - - float w_val_0, w_val_1, w_val_2, w_val_3, w_val_4, w_val_5, w_val_6, w_val_7; - float w_val_8, w_val_9, w_val_10, w_val_11, w_val_12, w_val_13, w_val_14, w_val_15; - - float f_norm_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - float f_norm_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - float f_norm_sq_2 = f_alpha_beta_neg_x_2 * f_alpha_beta_neg_x_2 + f_alpha_beta_neg_y_2 * f_alpha_beta_neg_y_2; - float f_norm_sq_3 = f_alpha_beta_neg_x_3 * f_alpha_beta_neg_x_3 + f_alpha_beta_neg_y_3 * f_alpha_beta_neg_y_3; - float f_norm_sq_4 = f_alpha_beta_neg_x_4 * f_alpha_beta_neg_x_4 + f_alpha_beta_neg_y_4 * f_alpha_beta_neg_y_4; - float f_norm_sq_5 = f_alpha_beta_neg_x_5 * f_alpha_beta_neg_x_5 + f_alpha_beta_neg_y_5 * f_alpha_beta_neg_y_5; - float f_norm_sq_6 = f_alpha_beta_neg_x_6 * f_alpha_beta_neg_x_6 + f_alpha_beta_neg_y_6 * f_alpha_beta_neg_y_6; - float f_norm_sq_7 = f_alpha_beta_neg_x_7 * f_alpha_beta_neg_x_7 + f_alpha_beta_neg_y_7 * f_alpha_beta_neg_y_7; - float f_norm_sq_8 = f_alpha_beta_neg_x_8 * f_alpha_beta_neg_x_8 + f_alpha_beta_neg_y_8 * f_alpha_beta_neg_y_8; - float f_norm_sq_9 = f_alpha_beta_neg_x_9 * f_alpha_beta_neg_x_9 + f_alpha_beta_neg_y_9 * f_alpha_beta_neg_y_9; - float f_norm_sq_10 = f_alpha_beta_neg_x_10 * f_alpha_beta_neg_x_10 + f_alpha_beta_neg_y_10 * f_alpha_beta_neg_y_10; - float f_norm_sq_11 = f_alpha_beta_neg_x_11 * f_alpha_beta_neg_x_11 + f_alpha_beta_neg_y_11 * f_alpha_beta_neg_y_11; - float f_norm_sq_12 = f_alpha_beta_neg_x_12 * f_alpha_beta_neg_x_12 + f_alpha_beta_neg_y_12 * f_alpha_beta_neg_y_12; - float f_norm_sq_13 = f_alpha_beta_neg_x_13 * f_alpha_beta_neg_x_13 + f_alpha_beta_neg_y_13 * f_alpha_beta_neg_y_13; - float f_norm_sq_14 = f_alpha_beta_neg_x_14 * f_alpha_beta_neg_x_14 + f_alpha_beta_neg_y_14 * f_alpha_beta_neg_y_14; - float f_norm_sq_15 = f_alpha_beta_neg_x_15 * f_alpha_beta_neg_x_15 + f_alpha_beta_neg_y_15 * f_alpha_beta_neg_y_15; - - float rhs_0 = f_norm_sq_0 * cos_phi_sq; - float rhs_1 = f_norm_sq_1 * cos_phi_sq; - float rhs_2 = f_norm_sq_2 * cos_phi_sq; - float rhs_3 = f_norm_sq_3 * cos_phi_sq; - float rhs_4 = f_norm_sq_4 * cos_phi_sq; - float rhs_5 = f_norm_sq_5 * cos_phi_sq; - float rhs_6 = f_norm_sq_6 * cos_phi_sq; - float rhs_7 = f_norm_sq_7 * cos_phi_sq; - float rhs_8 = f_norm_sq_8 * cos_phi_sq; - float rhs_9 = f_norm_sq_9 * cos_phi_sq; - float rhs_10 = f_norm_sq_10 * cos_phi_sq; - float rhs_11 = f_norm_sq_11 * cos_phi_sq; - float rhs_12 = f_norm_sq_12 * cos_phi_sq; - float rhs_13 = f_norm_sq_13 * cos_phi_sq; - float rhs_14 = f_norm_sq_14 * cos_phi_sq; - float rhs_15 = f_norm_sq_15 * cos_phi_sq; - - float e_dot_f_0 = e_x_i * f_alpha_beta_neg_x_0 + e_y_i * f_alpha_beta_neg_y_0; - float e_dot_f_1 = e_x_i * f_alpha_beta_neg_x_1 + e_y_i * f_alpha_beta_neg_y_1; - float e_dot_f_2 = e_x_i * f_alpha_beta_neg_x_2 + e_y_i * f_alpha_beta_neg_y_2; - float e_dot_f_3 = e_x_i * f_alpha_beta_neg_x_3 + e_y_i * f_alpha_beta_neg_y_3; - float e_dot_f_4 = e_x_i * f_alpha_beta_neg_x_4 + e_y_i * f_alpha_beta_neg_y_4; - float e_dot_f_5 = e_x_i * f_alpha_beta_neg_x_5 + e_y_i * f_alpha_beta_neg_y_5; - float e_dot_f_6 = e_x_i * f_alpha_beta_neg_x_6 + e_y_i * f_alpha_beta_neg_y_6; - float e_dot_f_7 = e_x_i * f_alpha_beta_neg_x_7 + e_y_i * f_alpha_beta_neg_y_7; - float e_dot_f_8 = e_x_i * f_alpha_beta_neg_x_8 + e_y_i * f_alpha_beta_neg_y_8; - float e_dot_f_9 = e_x_i * f_alpha_beta_neg_x_9 + e_y_i * f_alpha_beta_neg_y_9; - float e_dot_f_10 = e_x_i * f_alpha_beta_neg_x_10 + e_y_i * f_alpha_beta_neg_y_10; - float e_dot_f_11 = e_x_i * f_alpha_beta_neg_x_11 + e_y_i * f_alpha_beta_neg_y_11; - float e_dot_f_12 = e_x_i * f_alpha_beta_neg_x_12 + e_y_i * f_alpha_beta_neg_y_12; - float e_dot_f_13 = e_x_i * f_alpha_beta_neg_x_13 + e_y_i * f_alpha_beta_neg_y_13; - float e_dot_f_14 = e_x_i * f_alpha_beta_neg_x_14 + e_y_i * f_alpha_beta_neg_y_14; - float e_dot_f_15 = e_x_i * f_alpha_beta_neg_x_15 + e_y_i * f_alpha_beta_neg_y_15; - - float e_dot_f_sq_0 = e_dot_f_0 * e_dot_f_0; - float e_dot_f_sq_1 = e_dot_f_1 * e_dot_f_1; - float e_dot_f_sq_2 = e_dot_f_2 * e_dot_f_2; - float e_dot_f_sq_3 = e_dot_f_3 * e_dot_f_3; - float e_dot_f_sq_4 = e_dot_f_4 * e_dot_f_4; - float e_dot_f_sq_5 = e_dot_f_5 * e_dot_f_5; - float e_dot_f_sq_6 = e_dot_f_6 * e_dot_f_6; - float e_dot_f_sq_7 = e_dot_f_7 * e_dot_f_7; - float e_dot_f_sq_8 = e_dot_f_8 * e_dot_f_8; - float e_dot_f_sq_9 = e_dot_f_9 * e_dot_f_9; - float e_dot_f_sq_10 = e_dot_f_10 * e_dot_f_10; - float e_dot_f_sq_11 = e_dot_f_11 * e_dot_f_11; - float e_dot_f_sq_12 = e_dot_f_12 * e_dot_f_12; - float e_dot_f_sq_13 = e_dot_f_13 * e_dot_f_13; - float e_dot_f_sq_14 = e_dot_f_14 * e_dot_f_14; - float e_dot_f_sq_15 = e_dot_f_15 * e_dot_f_15; - - if (e_dot_f_0 >= 0.0 || e_dot_f_sq_0 <= rhs_0) - w_val_0 = dt_neg; - else - w_val_0 = c_neg_times_dt; - if (e_dot_f_1 >= 0.0 || e_dot_f_sq_1 <= rhs_1) - w_val_1 = dt_neg; - else - w_val_1 = c_neg_times_dt; - if (e_dot_f_2 >= 0.0 || e_dot_f_sq_2 <= rhs_2) - w_val_2 = dt_neg; - else - w_val_2 = c_neg_times_dt; - if (e_dot_f_3 >= 0.0 || e_dot_f_sq_3 <= rhs_3) - w_val_3 = dt_neg; - else - w_val_3 = c_neg_times_dt; - if (e_dot_f_4 >= 0.0 || e_dot_f_sq_4 <= rhs_4) - w_val_4 = dt_neg; - else - w_val_4 = c_neg_times_dt; - if (e_dot_f_5 >= 0.0 || e_dot_f_sq_5 <= rhs_5) - w_val_5 = dt_neg; - else - w_val_5 = c_neg_times_dt; - if (e_dot_f_6 >= 0.0 || e_dot_f_sq_6 <= rhs_6) - w_val_6 = dt_neg; - else - w_val_6 = c_neg_times_dt; - if (e_dot_f_7 >= 0.0 || e_dot_f_sq_7 <= rhs_7) - w_val_7 = dt_neg; - else - w_val_7 = c_neg_times_dt; - if (e_dot_f_8 >= 0.0 || e_dot_f_sq_8 <= rhs_8) - w_val_8 = dt_neg; - else - w_val_8 = c_neg_times_dt; - if (e_dot_f_9 >= 0.0 || e_dot_f_sq_9 <= rhs_9) - w_val_9 = dt_neg; - else - w_val_9 = c_neg_times_dt; - if (e_dot_f_10 >= 0.0 || e_dot_f_sq_10 <= rhs_10) - w_val_10 = dt_neg; - else - w_val_10 = c_neg_times_dt; - if (e_dot_f_11 >= 0.0 || e_dot_f_sq_11 <= rhs_11) - w_val_11 = dt_neg; - else - w_val_11 = c_neg_times_dt; - if (e_dot_f_12 >= 0.0 || e_dot_f_sq_12 <= rhs_12) - w_val_12 = dt_neg; - else - w_val_12 = c_neg_times_dt; - if (e_dot_f_13 >= 0.0 || e_dot_f_sq_13 <= rhs_13) - w_val_13 = dt_neg; - else - w_val_13 = c_neg_times_dt; - if (e_dot_f_14 >= 0.0 || e_dot_f_sq_14 <= rhs_14) - w_val_14 = dt_neg; - else - w_val_14 = c_neg_times_dt; - if (e_dot_f_15 >= 0.0 || e_dot_f_sq_15 <= rhs_15) - w_val_15 = dt_neg; - else - w_val_15 = c_neg_times_dt; - - acc_x_0 += f_alpha_beta_neg_x_0 * w_val_0; - acc_x_1 += f_alpha_beta_neg_x_1 * w_val_1; - acc_x_2 += f_alpha_beta_neg_x_2 * w_val_2; - acc_x_3 += f_alpha_beta_neg_x_3 * w_val_3; - acc_x_4 += f_alpha_beta_neg_x_4 * w_val_4; - acc_x_5 += f_alpha_beta_neg_x_5 * w_val_5; - acc_x_6 += f_alpha_beta_neg_x_6 * w_val_6; - acc_x_7 += f_alpha_beta_neg_x_7 * w_val_7; - acc_x_8 += f_alpha_beta_neg_x_8 * w_val_8; - acc_x_9 += f_alpha_beta_neg_x_9 * w_val_9; - acc_x_10 += f_alpha_beta_neg_x_10 * w_val_10; - acc_x_11 += f_alpha_beta_neg_x_11 * w_val_11; - acc_x_12 += f_alpha_beta_neg_x_12 * w_val_12; - acc_x_13 += f_alpha_beta_neg_x_13 * w_val_13; - acc_x_14 += f_alpha_beta_neg_x_14 * w_val_14; - acc_x_15 += f_alpha_beta_neg_x_15 * w_val_15; - acc_y_0 += f_alpha_beta_neg_y_0 * w_val_0; - acc_y_1 += f_alpha_beta_neg_y_1 * w_val_1; - acc_y_2 += f_alpha_beta_neg_y_2 * w_val_2; - acc_y_3 += f_alpha_beta_neg_y_3 * w_val_3; - acc_y_4 += f_alpha_beta_neg_y_4 * w_val_4; - acc_y_5 += f_alpha_beta_neg_y_5 * w_val_5; - acc_y_6 += f_alpha_beta_neg_y_6 * w_val_6; - acc_y_7 += f_alpha_beta_neg_y_7 * w_val_7; - acc_y_8 += f_alpha_beta_neg_y_8 * w_val_8; - acc_y_9 += f_alpha_beta_neg_y_9 * w_val_9; - acc_y_10 += f_alpha_beta_neg_y_10 * w_val_10; - acc_y_11 += f_alpha_beta_neg_y_11 * w_val_11; - acc_y_12 += f_alpha_beta_neg_y_12 * w_val_12; - acc_y_13 += f_alpha_beta_neg_y_13 * w_val_13; - acc_y_14 += f_alpha_beta_neg_y_14 * w_val_14; - acc_y_15 += f_alpha_beta_neg_y_15 * w_val_15; - } - - // remainder of the j < i half, single lane. # one j = 66 FLOPS - for (; j < i; j++) { - // lut lookup + -1* sign flip: not counted - float r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - float r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - float r_ab_norm = r_ab_norm_lut[j * n_p + i]; - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x += f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y += f_alpha_beta_neg_y * w_val; - } - - // reduction of the j < i accumulators. # K adds per line (K=16) => 32 ADDs - f_alpha_dt_x += acc_x_0 + acc_x_1 + acc_x_2 + acc_x_3 + acc_x_4 + acc_x_5 + acc_x_6 + acc_x_7 + acc_x_8 + acc_x_9 + acc_x_10 + acc_x_11 + acc_x_12 + acc_x_13 + acc_x_14 + acc_x_15; - f_alpha_dt_y += acc_y_0 + acc_y_1 + acc_y_2 + acc_y_3 + acc_y_4 + acc_y_5 + acc_y_6 + acc_y_7 + acc_y_8 + acc_y_9 + acc_y_10 + acc_y_11 + acc_y_12 + acc_y_13 + acc_y_14 + acc_y_15; - - acc_x_0 = 0.0f; - acc_x_1 = 0.0f; - acc_x_2 = 0.0f; - acc_x_3 = 0.0f; - acc_x_4 = 0.0f; - acc_x_5 = 0.0f; - acc_x_6 = 0.0f; - acc_x_7 = 0.0f; - acc_x_8 = 0.0f; - acc_x_9 = 0.0f; - acc_x_10 = 0.0f; - acc_x_11 = 0.0f; - acc_x_12 = 0.0f; - acc_x_13 = 0.0f; - acc_x_14 = 0.0f; - acc_x_15 = 0.0f; - acc_y_0 = 0.0f; - acc_y_1 = 0.0f; - acc_y_2 = 0.0f; - acc_y_3 = 0.0f; - acc_y_4 = 0.0f; - acc_y_5 = 0.0f; - acc_y_6 = 0.0f; - acc_y_7 = 0.0f; - acc_y_8 = 0.0f; - acc_y_9 = 0.0f; - acc_y_10 = 0.0f; - acc_y_11 = 0.0f; - acc_y_12 = 0.0f; - acc_y_13 = 0.0f; - acc_y_14 = 0.0f; - acc_y_15 = 0.0f; - - // ILP over the j > i half. # each lane = 66 FLOPS, K = 16 lanes per step - for (j = i + 1; j + 16 < n_p; j += 16) { - float r_ab_x_0 = r_ab_x_lut[i * n_p + j]; - float r_ab_x_1 = r_ab_x_lut[i * n_p + j + 1]; - float r_ab_x_2 = r_ab_x_lut[i * n_p + j + 2]; - float r_ab_x_3 = r_ab_x_lut[i * n_p + j + 3]; - float r_ab_x_4 = r_ab_x_lut[i * n_p + j + 4]; - float r_ab_x_5 = r_ab_x_lut[i * n_p + j + 5]; - float r_ab_x_6 = r_ab_x_lut[i * n_p + j + 6]; - float r_ab_x_7 = r_ab_x_lut[i * n_p + j + 7]; - float r_ab_x_8 = r_ab_x_lut[i * n_p + j + 8]; - float r_ab_x_9 = r_ab_x_lut[i * n_p + j + 9]; - float r_ab_x_10 = r_ab_x_lut[i * n_p + j + 10]; - float r_ab_x_11 = r_ab_x_lut[i * n_p + j + 11]; - float r_ab_x_12 = r_ab_x_lut[i * n_p + j + 12]; - float r_ab_x_13 = r_ab_x_lut[i * n_p + j + 13]; - float r_ab_x_14 = r_ab_x_lut[i * n_p + j + 14]; - float r_ab_x_15 = r_ab_x_lut[i * n_p + j + 15]; - - float r_ab_y_0 = r_ab_y_lut[i * n_p + j]; - float r_ab_y_1 = r_ab_y_lut[i * n_p + j + 1]; - float r_ab_y_2 = r_ab_y_lut[i * n_p + j + 2]; - float r_ab_y_3 = r_ab_y_lut[i * n_p + j + 3]; - float r_ab_y_4 = r_ab_y_lut[i * n_p + j + 4]; - float r_ab_y_5 = r_ab_y_lut[i * n_p + j + 5]; - float r_ab_y_6 = r_ab_y_lut[i * n_p + j + 6]; - float r_ab_y_7 = r_ab_y_lut[i * n_p + j + 7]; - float r_ab_y_8 = r_ab_y_lut[i * n_p + j + 8]; - float r_ab_y_9 = r_ab_y_lut[i * n_p + j + 9]; - float r_ab_y_10 = r_ab_y_lut[i * n_p + j + 10]; - float r_ab_y_11 = r_ab_y_lut[i * n_p + j + 11]; - float r_ab_y_12 = r_ab_y_lut[i * n_p + j + 12]; - float r_ab_y_13 = r_ab_y_lut[i * n_p + j + 13]; - float r_ab_y_14 = r_ab_y_lut[i * n_p + j + 14]; - float r_ab_y_15 = r_ab_y_lut[i * n_p + j + 15]; - - float r_ab_norm_0 = r_ab_norm_lut[i * n_p + j]; - float r_ab_norm_1 = r_ab_norm_lut[i * n_p + j + 1]; - float r_ab_norm_2 = r_ab_norm_lut[i * n_p + j + 2]; - float r_ab_norm_3 = r_ab_norm_lut[i * n_p + j + 3]; - float r_ab_norm_4 = r_ab_norm_lut[i * n_p + j + 4]; - float r_ab_norm_5 = r_ab_norm_lut[i * n_p + j + 5]; - float r_ab_norm_6 = r_ab_norm_lut[i * n_p + j + 6]; - float r_ab_norm_7 = r_ab_norm_lut[i * n_p + j + 7]; - float r_ab_norm_8 = r_ab_norm_lut[i * n_p + j + 8]; - float r_ab_norm_9 = r_ab_norm_lut[i * n_p + j + 9]; - float r_ab_norm_10 = r_ab_norm_lut[i * n_p + j + 10]; - float r_ab_norm_11 = r_ab_norm_lut[i * n_p + j + 11]; - float r_ab_norm_12 = r_ab_norm_lut[i * n_p + j + 12]; - float r_ab_norm_13 = r_ab_norm_lut[i * n_p + j + 13]; - float r_ab_norm_14 = r_ab_norm_lut[i * n_p + j + 14]; - float r_ab_norm_15 = r_ab_norm_lut[i * n_p + j + 15]; - - r_ab_norm_0 = r_ab_norm_0 > EPS ? r_ab_norm_0 : EPS; - r_ab_norm_1 = r_ab_norm_1 > EPS ? r_ab_norm_1 : EPS; - r_ab_norm_2 = r_ab_norm_2 > EPS ? r_ab_norm_2 : EPS; - r_ab_norm_3 = r_ab_norm_3 > EPS ? r_ab_norm_3 : EPS; - r_ab_norm_4 = r_ab_norm_4 > EPS ? r_ab_norm_4 : EPS; - r_ab_norm_5 = r_ab_norm_5 > EPS ? r_ab_norm_5 : EPS; - r_ab_norm_6 = r_ab_norm_6 > EPS ? r_ab_norm_6 : EPS; - r_ab_norm_7 = r_ab_norm_7 > EPS ? r_ab_norm_7 : EPS; - r_ab_norm_8 = r_ab_norm_8 > EPS ? r_ab_norm_8 : EPS; - r_ab_norm_9 = r_ab_norm_9 > EPS ? r_ab_norm_9 : EPS; - r_ab_norm_10 = r_ab_norm_10 > EPS ? r_ab_norm_10 : EPS; - r_ab_norm_11 = r_ab_norm_11 > EPS ? r_ab_norm_11 : EPS; - r_ab_norm_12 = r_ab_norm_12 > EPS ? r_ab_norm_12 : EPS; - r_ab_norm_13 = r_ab_norm_13 > EPS ? r_ab_norm_13 : EPS; - r_ab_norm_14 = r_ab_norm_14 > EPS ? r_ab_norm_14 : EPS; - r_ab_norm_15 = r_ab_norm_15 > EPS ? r_ab_norm_15 : EPS; - - float r_ab2_x_0 = r_ab_x_0 - sb_e_x[j]; - float r_ab2_x_1 = r_ab_x_1 - sb_e_x[j + 1]; - float r_ab2_x_2 = r_ab_x_2 - sb_e_x[j + 2]; - float r_ab2_x_3 = r_ab_x_3 - sb_e_x[j + 3]; - float r_ab2_x_4 = r_ab_x_4 - sb_e_x[j + 4]; - float r_ab2_x_5 = r_ab_x_5 - sb_e_x[j + 5]; - float r_ab2_x_6 = r_ab_x_6 - sb_e_x[j + 6]; - float r_ab2_x_7 = r_ab_x_7 - sb_e_x[j + 7]; - float r_ab2_x_8 = r_ab_x_8 - sb_e_x[j + 8]; - float r_ab2_x_9 = r_ab_x_9 - sb_e_x[j + 9]; - float r_ab2_x_10 = r_ab_x_10 - sb_e_x[j + 10]; - float r_ab2_x_11 = r_ab_x_11 - sb_e_x[j + 11]; - float r_ab2_x_12 = r_ab_x_12 - sb_e_x[j + 12]; - float r_ab2_x_13 = r_ab_x_13 - sb_e_x[j + 13]; - float r_ab2_x_14 = r_ab_x_14 - sb_e_x[j + 14]; - float r_ab2_x_15 = r_ab_x_15 - sb_e_x[j + 15]; - - float r_ab2_y_0 = r_ab_y_0 - sb_e_y[j]; - float r_ab2_y_1 = r_ab_y_1 - sb_e_y[j + 1]; - float r_ab2_y_2 = r_ab_y_2 - sb_e_y[j + 2]; - float r_ab2_y_3 = r_ab_y_3 - sb_e_y[j + 3]; - float r_ab2_y_4 = r_ab_y_4 - sb_e_y[j + 4]; - float r_ab2_y_5 = r_ab_y_5 - sb_e_y[j + 5]; - float r_ab2_y_6 = r_ab_y_6 - sb_e_y[j + 6]; - float r_ab2_y_7 = r_ab_y_7 - sb_e_y[j + 7]; - float r_ab2_y_8 = r_ab_y_8 - sb_e_y[j + 8]; - float r_ab2_y_9 = r_ab_y_9 - sb_e_y[j + 9]; - float r_ab2_y_10 = r_ab_y_10 - sb_e_y[j + 10]; - float r_ab2_y_11 = r_ab_y_11 - sb_e_y[j + 11]; - float r_ab2_y_12 = r_ab_y_12 - sb_e_y[j + 12]; - float r_ab2_y_13 = r_ab_y_13 - sb_e_y[j + 13]; - float r_ab2_y_14 = r_ab_y_14 - sb_e_y[j + 14]; - float r_ab2_y_15 = r_ab_y_15 - sb_e_y[j + 15]; - - float r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - float r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - float r_ab2_norm_2 = sqrt(r_ab2_x_2 * r_ab2_x_2 + r_ab2_y_2 * r_ab2_y_2); - float r_ab2_norm_3 = sqrt(r_ab2_x_3 * r_ab2_x_3 + r_ab2_y_3 * r_ab2_y_3); - float r_ab2_norm_4 = sqrt(r_ab2_x_4 * r_ab2_x_4 + r_ab2_y_4 * r_ab2_y_4); - float r_ab2_norm_5 = sqrt(r_ab2_x_5 * r_ab2_x_5 + r_ab2_y_5 * r_ab2_y_5); - float r_ab2_norm_6 = sqrt(r_ab2_x_6 * r_ab2_x_6 + r_ab2_y_6 * r_ab2_y_6); - float r_ab2_norm_7 = sqrt(r_ab2_x_7 * r_ab2_x_7 + r_ab2_y_7 * r_ab2_y_7); - float r_ab2_norm_8 = sqrt(r_ab2_x_8 * r_ab2_x_8 + r_ab2_y_8 * r_ab2_y_8); - float r_ab2_norm_9 = sqrt(r_ab2_x_9 * r_ab2_x_9 + r_ab2_y_9 * r_ab2_y_9); - float r_ab2_norm_10 = sqrt(r_ab2_x_10 * r_ab2_x_10 + r_ab2_y_10 * r_ab2_y_10); - float r_ab2_norm_11 = sqrt(r_ab2_x_11 * r_ab2_x_11 + r_ab2_y_11 * r_ab2_y_11); - float r_ab2_norm_12 = sqrt(r_ab2_x_12 * r_ab2_x_12 + r_ab2_y_12 * r_ab2_y_12); - float r_ab2_norm_13 = sqrt(r_ab2_x_13 * r_ab2_x_13 + r_ab2_y_13 * r_ab2_y_13); - float r_ab2_norm_14 = sqrt(r_ab2_x_14 * r_ab2_x_14 + r_ab2_y_14 * r_ab2_y_14); - float r_ab2_norm_15 = sqrt(r_ab2_x_15 * r_ab2_x_15 + r_ab2_y_15 * r_ab2_y_15); - - r_ab2_norm_0 = r_ab2_norm_0 > EPS ? r_ab2_norm_0 : EPS; - r_ab2_norm_1 = r_ab2_norm_1 > EPS ? r_ab2_norm_1 : EPS; - r_ab2_norm_2 = r_ab2_norm_2 > EPS ? r_ab2_norm_2 : EPS; - r_ab2_norm_3 = r_ab2_norm_3 > EPS ? r_ab2_norm_3 : EPS; - r_ab2_norm_4 = r_ab2_norm_4 > EPS ? r_ab2_norm_4 : EPS; - r_ab2_norm_5 = r_ab2_norm_5 > EPS ? r_ab2_norm_5 : EPS; - r_ab2_norm_6 = r_ab2_norm_6 > EPS ? r_ab2_norm_6 : EPS; - r_ab2_norm_7 = r_ab2_norm_7 > EPS ? r_ab2_norm_7 : EPS; - r_ab2_norm_8 = r_ab2_norm_8 > EPS ? r_ab2_norm_8 : EPS; - r_ab2_norm_9 = r_ab2_norm_9 > EPS ? r_ab2_norm_9 : EPS; - r_ab2_norm_10 = r_ab2_norm_10 > EPS ? r_ab2_norm_10 : EPS; - r_ab2_norm_11 = r_ab2_norm_11 > EPS ? r_ab2_norm_11 : EPS; - r_ab2_norm_12 = r_ab2_norm_12 > EPS ? r_ab2_norm_12 : EPS; - r_ab2_norm_13 = r_ab2_norm_13 > EPS ? r_ab2_norm_13 : EPS; - r_ab2_norm_14 = r_ab2_norm_14 > EPS ? r_ab2_norm_14 : EPS; - r_ab2_norm_15 = r_ab2_norm_15 > EPS ? r_ab2_norm_15 : EPS; - - float r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - float r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - float r_ab_norm_sum_2 = r_ab_norm_2 + r_ab2_norm_2; - float r_ab_norm_sum_3 = r_ab_norm_3 + r_ab2_norm_3; - float r_ab_norm_sum_4 = r_ab_norm_4 + r_ab2_norm_4; - float r_ab_norm_sum_5 = r_ab_norm_5 + r_ab2_norm_5; - float r_ab_norm_sum_6 = r_ab_norm_6 + r_ab2_norm_6; - float r_ab_norm_sum_7 = r_ab_norm_7 + r_ab2_norm_7; - float r_ab_norm_sum_8 = r_ab_norm_8 + r_ab2_norm_8; - float r_ab_norm_sum_9 = r_ab_norm_9 + r_ab2_norm_9; - float r_ab_norm_sum_10 = r_ab_norm_10 + r_ab2_norm_10; - float r_ab_norm_sum_11 = r_ab_norm_11 + r_ab2_norm_11; - float r_ab_norm_sum_12 = r_ab_norm_12 + r_ab2_norm_12; - float r_ab_norm_sum_13 = r_ab_norm_13 + r_ab2_norm_13; - float r_ab_norm_sum_14 = r_ab_norm_14 + r_ab2_norm_14; - float r_ab_norm_sum_15 = r_ab_norm_15 + r_ab2_norm_15; - - float two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - sb_sq[j]; - float two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - sb_sq[j + 1]; - float two_b_sq_2 = r_ab_norm_sum_2 * r_ab_norm_sum_2 - sb_sq[j + 2]; - float two_b_sq_3 = r_ab_norm_sum_3 * r_ab_norm_sum_3 - sb_sq[j + 3]; - float two_b_sq_4 = r_ab_norm_sum_4 * r_ab_norm_sum_4 - sb_sq[j + 4]; - float two_b_sq_5 = r_ab_norm_sum_5 * r_ab_norm_sum_5 - sb_sq[j + 5]; - float two_b_sq_6 = r_ab_norm_sum_6 * r_ab_norm_sum_6 - sb_sq[j + 6]; - float two_b_sq_7 = r_ab_norm_sum_7 * r_ab_norm_sum_7 - sb_sq[j + 7]; - float two_b_sq_8 = r_ab_norm_sum_8 * r_ab_norm_sum_8 - sb_sq[j + 8]; - float two_b_sq_9 = r_ab_norm_sum_9 * r_ab_norm_sum_9 - sb_sq[j + 9]; - float two_b_sq_10 = r_ab_norm_sum_10 * r_ab_norm_sum_10 - sb_sq[j + 10]; - float two_b_sq_11 = r_ab_norm_sum_11 * r_ab_norm_sum_11 - sb_sq[j + 11]; - float two_b_sq_12 = r_ab_norm_sum_12 * r_ab_norm_sum_12 - sb_sq[j + 12]; - float two_b_sq_13 = r_ab_norm_sum_13 * r_ab_norm_sum_13 - sb_sq[j + 13]; - float two_b_sq_14 = r_ab_norm_sum_14 * r_ab_norm_sum_14 - sb_sq[j + 14]; - float two_b_sq_15 = r_ab_norm_sum_15 * r_ab_norm_sum_15 - sb_sq[j + 15]; - - two_b_sq_0 = two_b_sq_0 > EPS_SQ ? two_b_sq_0 : EPS_SQ; - two_b_sq_1 = two_b_sq_1 > EPS_SQ ? two_b_sq_1 : EPS_SQ; - two_b_sq_2 = two_b_sq_2 > EPS_SQ ? two_b_sq_2 : EPS_SQ; - two_b_sq_3 = two_b_sq_3 > EPS_SQ ? two_b_sq_3 : EPS_SQ; - two_b_sq_4 = two_b_sq_4 > EPS_SQ ? two_b_sq_4 : EPS_SQ; - two_b_sq_5 = two_b_sq_5 > EPS_SQ ? two_b_sq_5 : EPS_SQ; - two_b_sq_6 = two_b_sq_6 > EPS_SQ ? two_b_sq_6 : EPS_SQ; - two_b_sq_7 = two_b_sq_7 > EPS_SQ ? two_b_sq_7 : EPS_SQ; - two_b_sq_8 = two_b_sq_8 > EPS_SQ ? two_b_sq_8 : EPS_SQ; - two_b_sq_9 = two_b_sq_9 > EPS_SQ ? two_b_sq_9 : EPS_SQ; - two_b_sq_10 = two_b_sq_10 > EPS_SQ ? two_b_sq_10 : EPS_SQ; - two_b_sq_11 = two_b_sq_11 > EPS_SQ ? two_b_sq_11 : EPS_SQ; - two_b_sq_12 = two_b_sq_12 > EPS_SQ ? two_b_sq_12 : EPS_SQ; - two_b_sq_13 = two_b_sq_13 > EPS_SQ ? two_b_sq_13 : EPS_SQ; - two_b_sq_14 = two_b_sq_14 > EPS_SQ ? two_b_sq_14 : EPS_SQ; - two_b_sq_15 = two_b_sq_15 > EPS_SQ ? two_b_sq_15 : EPS_SQ; - - float two_b_0 = sqrt(two_b_sq_0); - float two_b_1 = sqrt(two_b_sq_1); - float two_b_2 = sqrt(two_b_sq_2); - float two_b_3 = sqrt(two_b_sq_3); - float two_b_4 = sqrt(two_b_sq_4); - float two_b_5 = sqrt(two_b_sq_5); - float two_b_6 = sqrt(two_b_sq_6); - float two_b_7 = sqrt(two_b_sq_7); - float two_b_8 = sqrt(two_b_sq_8); - float two_b_9 = sqrt(two_b_sq_9); - float two_b_10 = sqrt(two_b_sq_10); - float two_b_11 = sqrt(two_b_sq_11); - float two_b_12 = sqrt(two_b_sq_12); - float two_b_13 = sqrt(two_b_sq_13); - float two_b_14 = sqrt(two_b_sq_14); - float two_b_15 = sqrt(two_b_sq_15); - - float scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_2 = (r_ab_norm_sum_2 / (two_b_2 * r_ab_norm_2 * r_ab2_norm_2)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_2 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_3 = (r_ab_norm_sum_3 / (two_b_3 * r_ab_norm_3 * r_ab2_norm_3)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_3 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_4 = (r_ab_norm_sum_4 / (two_b_4 * r_ab_norm_4 * r_ab2_norm_4)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_4 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_5 = (r_ab_norm_sum_5 / (two_b_5 * r_ab_norm_5 * r_ab2_norm_5)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_5 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_6 = (r_ab_norm_sum_6 / (two_b_6 * r_ab_norm_6 * r_ab2_norm_6)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_6 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_7 = (r_ab_norm_sum_7 / (two_b_7 * r_ab_norm_7 * r_ab2_norm_7)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_7 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_8 = (r_ab_norm_sum_8 / (two_b_8 * r_ab_norm_8 * r_ab2_norm_8)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_8 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_9 = (r_ab_norm_sum_9 / (two_b_9 * r_ab_norm_9 * r_ab2_norm_9)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_9 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_10 = (r_ab_norm_sum_10 / (two_b_10 * r_ab_norm_10 * r_ab2_norm_10)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_10 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_11 = (r_ab_norm_sum_11 / (two_b_11 * r_ab_norm_11 * r_ab2_norm_11)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_11 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_12 = (r_ab_norm_sum_12 / (two_b_12 * r_ab_norm_12 * r_ab2_norm_12)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_12 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_13 = (r_ab_norm_sum_13 / (two_b_13 * r_ab_norm_13 * r_ab2_norm_13)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_13 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_14 = (r_ab_norm_sum_14 / (two_b_14 * r_ab_norm_14 * r_ab2_norm_14)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_14 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_15 = (r_ab_norm_sum_15 / (two_b_15 * r_ab_norm_15 * r_ab2_norm_15)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_15 * NEG_TWO_SIGMA_INV)); - - float scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - float scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - float scalar_mult_r_ab_2 = scalar_mult_common_2 * r_ab2_norm_2; - float scalar_mult_r_ab_3 = scalar_mult_common_3 * r_ab2_norm_3; - float scalar_mult_r_ab_4 = scalar_mult_common_4 * r_ab2_norm_4; - float scalar_mult_r_ab_5 = scalar_mult_common_5 * r_ab2_norm_5; - float scalar_mult_r_ab_6 = scalar_mult_common_6 * r_ab2_norm_6; - float scalar_mult_r_ab_7 = scalar_mult_common_7 * r_ab2_norm_7; - float scalar_mult_r_ab_8 = scalar_mult_common_8 * r_ab2_norm_8; - float scalar_mult_r_ab_9 = scalar_mult_common_9 * r_ab2_norm_9; - float scalar_mult_r_ab_10 = scalar_mult_common_10 * r_ab2_norm_10; - float scalar_mult_r_ab_11 = scalar_mult_common_11 * r_ab2_norm_11; - float scalar_mult_r_ab_12 = scalar_mult_common_12 * r_ab2_norm_12; - float scalar_mult_r_ab_13 = scalar_mult_common_13 * r_ab2_norm_13; - float scalar_mult_r_ab_14 = scalar_mult_common_14 * r_ab2_norm_14; - float scalar_mult_r_ab_15 = scalar_mult_common_15 * r_ab2_norm_15; - - float scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - float scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - float scalar_mult_r_ab2_2 = scalar_mult_common_2 * r_ab_norm_2; - float scalar_mult_r_ab2_3 = scalar_mult_common_3 * r_ab_norm_3; - float scalar_mult_r_ab2_4 = scalar_mult_common_4 * r_ab_norm_4; - float scalar_mult_r_ab2_5 = scalar_mult_common_5 * r_ab_norm_5; - float scalar_mult_r_ab2_6 = scalar_mult_common_6 * r_ab_norm_6; - float scalar_mult_r_ab2_7 = scalar_mult_common_7 * r_ab_norm_7; - float scalar_mult_r_ab2_8 = scalar_mult_common_8 * r_ab_norm_8; - float scalar_mult_r_ab2_9 = scalar_mult_common_9 * r_ab_norm_9; - float scalar_mult_r_ab2_10 = scalar_mult_common_10 * r_ab_norm_10; - float scalar_mult_r_ab2_11 = scalar_mult_common_11 * r_ab_norm_11; - float scalar_mult_r_ab2_12 = scalar_mult_common_12 * r_ab_norm_12; - float scalar_mult_r_ab2_13 = scalar_mult_common_13 * r_ab_norm_13; - float scalar_mult_r_ab2_14 = scalar_mult_common_14 * r_ab_norm_14; - float scalar_mult_r_ab2_15 = scalar_mult_common_15 * r_ab_norm_15; - - float f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_x_2 = r_ab_x_2 * scalar_mult_r_ab_2 + r_ab2_x_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_x_3 = r_ab_x_3 * scalar_mult_r_ab_3 + r_ab2_x_3 * scalar_mult_r_ab2_3; - float f_alpha_beta_neg_x_4 = r_ab_x_4 * scalar_mult_r_ab_4 + r_ab2_x_4 * scalar_mult_r_ab2_4; - float f_alpha_beta_neg_x_5 = r_ab_x_5 * scalar_mult_r_ab_5 + r_ab2_x_5 * scalar_mult_r_ab2_5; - float f_alpha_beta_neg_x_6 = r_ab_x_6 * scalar_mult_r_ab_6 + r_ab2_x_6 * scalar_mult_r_ab2_6; - float f_alpha_beta_neg_x_7 = r_ab_x_7 * scalar_mult_r_ab_7 + r_ab2_x_7 * scalar_mult_r_ab2_7; - float f_alpha_beta_neg_x_8 = r_ab_x_8 * scalar_mult_r_ab_8 + r_ab2_x_8 * scalar_mult_r_ab2_8; - float f_alpha_beta_neg_x_9 = r_ab_x_9 * scalar_mult_r_ab_9 + r_ab2_x_9 * scalar_mult_r_ab2_9; - float f_alpha_beta_neg_x_10 = r_ab_x_10 * scalar_mult_r_ab_10 + r_ab2_x_10 * scalar_mult_r_ab2_10; - float f_alpha_beta_neg_x_11 = r_ab_x_11 * scalar_mult_r_ab_11 + r_ab2_x_11 * scalar_mult_r_ab2_11; - float f_alpha_beta_neg_x_12 = r_ab_x_12 * scalar_mult_r_ab_12 + r_ab2_x_12 * scalar_mult_r_ab2_12; - float f_alpha_beta_neg_x_13 = r_ab_x_13 * scalar_mult_r_ab_13 + r_ab2_x_13 * scalar_mult_r_ab2_13; - float f_alpha_beta_neg_x_14 = r_ab_x_14 * scalar_mult_r_ab_14 + r_ab2_x_14 * scalar_mult_r_ab2_14; - float f_alpha_beta_neg_x_15 = r_ab_x_15 * scalar_mult_r_ab_15 + r_ab2_x_15 * scalar_mult_r_ab2_15; - - float f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_y_2 = r_ab_y_2 * scalar_mult_r_ab_2 + r_ab2_y_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_y_3 = r_ab_y_3 * scalar_mult_r_ab_3 + r_ab2_y_3 * scalar_mult_r_ab2_3; - float f_alpha_beta_neg_y_4 = r_ab_y_4 * scalar_mult_r_ab_4 + r_ab2_y_4 * scalar_mult_r_ab2_4; - float f_alpha_beta_neg_y_5 = r_ab_y_5 * scalar_mult_r_ab_5 + r_ab2_y_5 * scalar_mult_r_ab2_5; - float f_alpha_beta_neg_y_6 = r_ab_y_6 * scalar_mult_r_ab_6 + r_ab2_y_6 * scalar_mult_r_ab2_6; - float f_alpha_beta_neg_y_7 = r_ab_y_7 * scalar_mult_r_ab_7 + r_ab2_y_7 * scalar_mult_r_ab2_7; - float f_alpha_beta_neg_y_8 = r_ab_y_8 * scalar_mult_r_ab_8 + r_ab2_y_8 * scalar_mult_r_ab2_8; - float f_alpha_beta_neg_y_9 = r_ab_y_9 * scalar_mult_r_ab_9 + r_ab2_y_9 * scalar_mult_r_ab2_9; - float f_alpha_beta_neg_y_10 = r_ab_y_10 * scalar_mult_r_ab_10 + r_ab2_y_10 * scalar_mult_r_ab2_10; - float f_alpha_beta_neg_y_11 = r_ab_y_11 * scalar_mult_r_ab_11 + r_ab2_y_11 * scalar_mult_r_ab2_11; - float f_alpha_beta_neg_y_12 = r_ab_y_12 * scalar_mult_r_ab_12 + r_ab2_y_12 * scalar_mult_r_ab2_12; - float f_alpha_beta_neg_y_13 = r_ab_y_13 * scalar_mult_r_ab_13 + r_ab2_y_13 * scalar_mult_r_ab2_13; - float f_alpha_beta_neg_y_14 = r_ab_y_14 * scalar_mult_r_ab_14 + r_ab2_y_14 * scalar_mult_r_ab2_14; - float f_alpha_beta_neg_y_15 = r_ab_y_15 * scalar_mult_r_ab_15 + r_ab2_y_15 * scalar_mult_r_ab2_15; - - float w_val_0, w_val_1, w_val_2, w_val_3, w_val_4, w_val_5, w_val_6, w_val_7; - float w_val_8, w_val_9, w_val_10, w_val_11, w_val_12, w_val_13, w_val_14, w_val_15; - - float f_norm_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - float f_norm_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - float f_norm_sq_2 = f_alpha_beta_neg_x_2 * f_alpha_beta_neg_x_2 + f_alpha_beta_neg_y_2 * f_alpha_beta_neg_y_2; - float f_norm_sq_3 = f_alpha_beta_neg_x_3 * f_alpha_beta_neg_x_3 + f_alpha_beta_neg_y_3 * f_alpha_beta_neg_y_3; - float f_norm_sq_4 = f_alpha_beta_neg_x_4 * f_alpha_beta_neg_x_4 + f_alpha_beta_neg_y_4 * f_alpha_beta_neg_y_4; - float f_norm_sq_5 = f_alpha_beta_neg_x_5 * f_alpha_beta_neg_x_5 + f_alpha_beta_neg_y_5 * f_alpha_beta_neg_y_5; - float f_norm_sq_6 = f_alpha_beta_neg_x_6 * f_alpha_beta_neg_x_6 + f_alpha_beta_neg_y_6 * f_alpha_beta_neg_y_6; - float f_norm_sq_7 = f_alpha_beta_neg_x_7 * f_alpha_beta_neg_x_7 + f_alpha_beta_neg_y_7 * f_alpha_beta_neg_y_7; - float f_norm_sq_8 = f_alpha_beta_neg_x_8 * f_alpha_beta_neg_x_8 + f_alpha_beta_neg_y_8 * f_alpha_beta_neg_y_8; - float f_norm_sq_9 = f_alpha_beta_neg_x_9 * f_alpha_beta_neg_x_9 + f_alpha_beta_neg_y_9 * f_alpha_beta_neg_y_9; - float f_norm_sq_10 = f_alpha_beta_neg_x_10 * f_alpha_beta_neg_x_10 + f_alpha_beta_neg_y_10 * f_alpha_beta_neg_y_10; - float f_norm_sq_11 = f_alpha_beta_neg_x_11 * f_alpha_beta_neg_x_11 + f_alpha_beta_neg_y_11 * f_alpha_beta_neg_y_11; - float f_norm_sq_12 = f_alpha_beta_neg_x_12 * f_alpha_beta_neg_x_12 + f_alpha_beta_neg_y_12 * f_alpha_beta_neg_y_12; - float f_norm_sq_13 = f_alpha_beta_neg_x_13 * f_alpha_beta_neg_x_13 + f_alpha_beta_neg_y_13 * f_alpha_beta_neg_y_13; - float f_norm_sq_14 = f_alpha_beta_neg_x_14 * f_alpha_beta_neg_x_14 + f_alpha_beta_neg_y_14 * f_alpha_beta_neg_y_14; - float f_norm_sq_15 = f_alpha_beta_neg_x_15 * f_alpha_beta_neg_x_15 + f_alpha_beta_neg_y_15 * f_alpha_beta_neg_y_15; - - float rhs_0 = f_norm_sq_0 * cos_phi_sq; - float rhs_1 = f_norm_sq_1 * cos_phi_sq; - float rhs_2 = f_norm_sq_2 * cos_phi_sq; - float rhs_3 = f_norm_sq_3 * cos_phi_sq; - float rhs_4 = f_norm_sq_4 * cos_phi_sq; - float rhs_5 = f_norm_sq_5 * cos_phi_sq; - float rhs_6 = f_norm_sq_6 * cos_phi_sq; - float rhs_7 = f_norm_sq_7 * cos_phi_sq; - float rhs_8 = f_norm_sq_8 * cos_phi_sq; - float rhs_9 = f_norm_sq_9 * cos_phi_sq; - float rhs_10 = f_norm_sq_10 * cos_phi_sq; - float rhs_11 = f_norm_sq_11 * cos_phi_sq; - float rhs_12 = f_norm_sq_12 * cos_phi_sq; - float rhs_13 = f_norm_sq_13 * cos_phi_sq; - float rhs_14 = f_norm_sq_14 * cos_phi_sq; - float rhs_15 = f_norm_sq_15 * cos_phi_sq; - - float e_dot_f_0 = e_x_i * f_alpha_beta_neg_x_0 + e_y_i * f_alpha_beta_neg_y_0; - float e_dot_f_1 = e_x_i * f_alpha_beta_neg_x_1 + e_y_i * f_alpha_beta_neg_y_1; - float e_dot_f_2 = e_x_i * f_alpha_beta_neg_x_2 + e_y_i * f_alpha_beta_neg_y_2; - float e_dot_f_3 = e_x_i * f_alpha_beta_neg_x_3 + e_y_i * f_alpha_beta_neg_y_3; - float e_dot_f_4 = e_x_i * f_alpha_beta_neg_x_4 + e_y_i * f_alpha_beta_neg_y_4; - float e_dot_f_5 = e_x_i * f_alpha_beta_neg_x_5 + e_y_i * f_alpha_beta_neg_y_5; - float e_dot_f_6 = e_x_i * f_alpha_beta_neg_x_6 + e_y_i * f_alpha_beta_neg_y_6; - float e_dot_f_7 = e_x_i * f_alpha_beta_neg_x_7 + e_y_i * f_alpha_beta_neg_y_7; - float e_dot_f_8 = e_x_i * f_alpha_beta_neg_x_8 + e_y_i * f_alpha_beta_neg_y_8; - float e_dot_f_9 = e_x_i * f_alpha_beta_neg_x_9 + e_y_i * f_alpha_beta_neg_y_9; - float e_dot_f_10 = e_x_i * f_alpha_beta_neg_x_10 + e_y_i * f_alpha_beta_neg_y_10; - float e_dot_f_11 = e_x_i * f_alpha_beta_neg_x_11 + e_y_i * f_alpha_beta_neg_y_11; - float e_dot_f_12 = e_x_i * f_alpha_beta_neg_x_12 + e_y_i * f_alpha_beta_neg_y_12; - float e_dot_f_13 = e_x_i * f_alpha_beta_neg_x_13 + e_y_i * f_alpha_beta_neg_y_13; - float e_dot_f_14 = e_x_i * f_alpha_beta_neg_x_14 + e_y_i * f_alpha_beta_neg_y_14; - float e_dot_f_15 = e_x_i * f_alpha_beta_neg_x_15 + e_y_i * f_alpha_beta_neg_y_15; - - float e_dot_f_sq_0 = e_dot_f_0 * e_dot_f_0; - float e_dot_f_sq_1 = e_dot_f_1 * e_dot_f_1; - float e_dot_f_sq_2 = e_dot_f_2 * e_dot_f_2; - float e_dot_f_sq_3 = e_dot_f_3 * e_dot_f_3; - float e_dot_f_sq_4 = e_dot_f_4 * e_dot_f_4; - float e_dot_f_sq_5 = e_dot_f_5 * e_dot_f_5; - float e_dot_f_sq_6 = e_dot_f_6 * e_dot_f_6; - float e_dot_f_sq_7 = e_dot_f_7 * e_dot_f_7; - float e_dot_f_sq_8 = e_dot_f_8 * e_dot_f_8; - float e_dot_f_sq_9 = e_dot_f_9 * e_dot_f_9; - float e_dot_f_sq_10 = e_dot_f_10 * e_dot_f_10; - float e_dot_f_sq_11 = e_dot_f_11 * e_dot_f_11; - float e_dot_f_sq_12 = e_dot_f_12 * e_dot_f_12; - float e_dot_f_sq_13 = e_dot_f_13 * e_dot_f_13; - float e_dot_f_sq_14 = e_dot_f_14 * e_dot_f_14; - float e_dot_f_sq_15 = e_dot_f_15 * e_dot_f_15; - - if (e_dot_f_0 >= 0.0 || e_dot_f_sq_0 <= rhs_0) - w_val_0 = dt_neg; - else - w_val_0 = c_neg_times_dt; - if (e_dot_f_1 >= 0.0 || e_dot_f_sq_1 <= rhs_1) - w_val_1 = dt_neg; - else - w_val_1 = c_neg_times_dt; - if (e_dot_f_2 >= 0.0 || e_dot_f_sq_2 <= rhs_2) - w_val_2 = dt_neg; - else - w_val_2 = c_neg_times_dt; - if (e_dot_f_3 >= 0.0 || e_dot_f_sq_3 <= rhs_3) - w_val_3 = dt_neg; - else - w_val_3 = c_neg_times_dt; - if (e_dot_f_4 >= 0.0 || e_dot_f_sq_4 <= rhs_4) - w_val_4 = dt_neg; - else - w_val_4 = c_neg_times_dt; - if (e_dot_f_5 >= 0.0 || e_dot_f_sq_5 <= rhs_5) - w_val_5 = dt_neg; - else - w_val_5 = c_neg_times_dt; - if (e_dot_f_6 >= 0.0 || e_dot_f_sq_6 <= rhs_6) - w_val_6 = dt_neg; - else - w_val_6 = c_neg_times_dt; - if (e_dot_f_7 >= 0.0 || e_dot_f_sq_7 <= rhs_7) - w_val_7 = dt_neg; - else - w_val_7 = c_neg_times_dt; - if (e_dot_f_8 >= 0.0 || e_dot_f_sq_8 <= rhs_8) - w_val_8 = dt_neg; - else - w_val_8 = c_neg_times_dt; - if (e_dot_f_9 >= 0.0 || e_dot_f_sq_9 <= rhs_9) - w_val_9 = dt_neg; - else - w_val_9 = c_neg_times_dt; - if (e_dot_f_10 >= 0.0 || e_dot_f_sq_10 <= rhs_10) - w_val_10 = dt_neg; - else - w_val_10 = c_neg_times_dt; - if (e_dot_f_11 >= 0.0 || e_dot_f_sq_11 <= rhs_11) - w_val_11 = dt_neg; - else - w_val_11 = c_neg_times_dt; - if (e_dot_f_12 >= 0.0 || e_dot_f_sq_12 <= rhs_12) - w_val_12 = dt_neg; - else - w_val_12 = c_neg_times_dt; - if (e_dot_f_13 >= 0.0 || e_dot_f_sq_13 <= rhs_13) - w_val_13 = dt_neg; - else - w_val_13 = c_neg_times_dt; - if (e_dot_f_14 >= 0.0 || e_dot_f_sq_14 <= rhs_14) - w_val_14 = dt_neg; - else - w_val_14 = c_neg_times_dt; - if (e_dot_f_15 >= 0.0 || e_dot_f_sq_15 <= rhs_15) - w_val_15 = dt_neg; - else - w_val_15 = c_neg_times_dt; - - acc_x_0 += f_alpha_beta_neg_x_0 * w_val_0; - acc_x_1 += f_alpha_beta_neg_x_1 * w_val_1; - acc_x_2 += f_alpha_beta_neg_x_2 * w_val_2; - acc_x_3 += f_alpha_beta_neg_x_3 * w_val_3; - acc_x_4 += f_alpha_beta_neg_x_4 * w_val_4; - acc_x_5 += f_alpha_beta_neg_x_5 * w_val_5; - acc_x_6 += f_alpha_beta_neg_x_6 * w_val_6; - acc_x_7 += f_alpha_beta_neg_x_7 * w_val_7; - acc_x_8 += f_alpha_beta_neg_x_8 * w_val_8; - acc_x_9 += f_alpha_beta_neg_x_9 * w_val_9; - acc_x_10 += f_alpha_beta_neg_x_10 * w_val_10; - acc_x_11 += f_alpha_beta_neg_x_11 * w_val_11; - acc_x_12 += f_alpha_beta_neg_x_12 * w_val_12; - acc_x_13 += f_alpha_beta_neg_x_13 * w_val_13; - acc_x_14 += f_alpha_beta_neg_x_14 * w_val_14; - acc_x_15 += f_alpha_beta_neg_x_15 * w_val_15; - acc_y_0 += f_alpha_beta_neg_y_0 * w_val_0; - acc_y_1 += f_alpha_beta_neg_y_1 * w_val_1; - acc_y_2 += f_alpha_beta_neg_y_2 * w_val_2; - acc_y_3 += f_alpha_beta_neg_y_3 * w_val_3; - acc_y_4 += f_alpha_beta_neg_y_4 * w_val_4; - acc_y_5 += f_alpha_beta_neg_y_5 * w_val_5; - acc_y_6 += f_alpha_beta_neg_y_6 * w_val_6; - acc_y_7 += f_alpha_beta_neg_y_7 * w_val_7; - acc_y_8 += f_alpha_beta_neg_y_8 * w_val_8; - acc_y_9 += f_alpha_beta_neg_y_9 * w_val_9; - acc_y_10 += f_alpha_beta_neg_y_10 * w_val_10; - acc_y_11 += f_alpha_beta_neg_y_11 * w_val_11; - acc_y_12 += f_alpha_beta_neg_y_12 * w_val_12; - acc_y_13 += f_alpha_beta_neg_y_13 * w_val_13; - acc_y_14 += f_alpha_beta_neg_y_14 * w_val_14; - acc_y_15 += f_alpha_beta_neg_y_15 * w_val_15; - } - - // remainder of the j > i half, single lane. # one j = 66 FLOPS - for (; j < n_p; j++) { - float r_ab_x = r_ab_x_lut[i * n_p + j]; - float r_ab_y = r_ab_y_lut[i * n_p + j]; - float r_ab_norm = r_ab_norm_lut[i * n_p + j]; - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x += f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y += f_alpha_beta_neg_y * w_val; - } - - // reduction of the j > i accumulators. # K adds per line (K=16) => 32 ADDs - f_alpha_dt_x += acc_x_0 + acc_x_1 + acc_x_2 + acc_x_3 + acc_x_4 + acc_x_5 + acc_x_6 + acc_x_7 + acc_x_8 + acc_x_9 + acc_x_10 + acc_x_11 + acc_x_12 + acc_x_13 + acc_x_14 + acc_x_15; - f_alpha_dt_y += acc_y_0 + acc_y_1 + acc_y_2 + acc_y_3 + acc_y_4 + acc_y_5 + acc_y_6 + acc_y_7 + acc_y_8 + acc_y_9 + acc_y_10 + acc_y_11 + acc_y_12 + acc_y_13 + acc_y_14 + acc_y_15; - - // force between pedestrian and obstacles - // # one obstacle = 55 FLOPS (w_val sign-based, no a*a / b_sq) - for (int k = 0; k < board.n_obstacles; k++) { - // # 1 ADD - float v1_x = pos_x[i] - board.obstacles[k].start_x; - // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - // # 1 ADD, 3 MULTs - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - // # 2 ADDs, 1 MULT - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - // # 2 ADDs, 1 MULT - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - // # 2 MULTs, 1 DIV, 1 EXP - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - // # 1 MULT - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; - - float w_val; - - // sign select only, no flops - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 MULT - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // # 1 ADD - f_alpha_dt_x += f_alpha_B_dt_x; - // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // # 1 ADD - w_x[i] += f_alpha_dt_x; - // # 1 ADD - w_y[i] += f_alpha_dt_y; - } - - // update each pedestrian - // # 29 FLOPS per pedestrian - for (int i = 0; i < n_p; i++) { - - // # 1 ADD, 2 MULTs, 1 SQRT - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); - - float w_norm2 = w_norm > EPS ? w_norm : EPS; - - // # 1 DIV - float correction_factor = min(1.0f, v_max[i] / w_norm2); - - // # 1 MULT - curr_vel_x[i] = w_x[i] * correction_factor; - // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; - - // # 1 ADD, 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - // # 1 ADD, 1 MULT - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 1 ADD - float ex = destination_x[i] - pos_x[i]; - // # 1 ADD - float ey = destination_y[i] - pos_y[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float e_norm = sqrt(ex * ex + ey * ey); - float e_norm2 = e_norm > EPS ? e_norm : EPS; - // # 1 DIV - float e_norm_inv = 1.0 / e_norm2; - - // # 1 MULT - e_x[i] = ex * e_norm_inv; - // # 1 MULT - e_y[i] = ey * e_norm_inv; - - // # 1 ADD, 2 MULTs - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; - - // # 1 SQRT - float velocity_norm = sqrt(velocity_norm_sq); - - // # 1 MULT - sb_sq[i] = velocity_norm_sq * delta_t_sq; - - // # 2 MULTs (velocity_norm*delta_t written on both lines, counted as is) - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - // # 2 MULTs - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_symmetry_ILP_2_flopcount.cpp b/variants/variant_float_inlining_SoA_symmetry_ILP_2_flopcount.cpp deleted file mode 100644 index 8a845af..0000000 --- a/variants/variant_float_inlining_SoA_symmetry_ILP_2_flopcount.cpp +++ /dev/null @@ -1,703 +0,0 @@ -// # Changes: Added ILP for j-loop with K = 2 -// # added flopcount -// FLOPCOUNT = n_iter * ( 69 * n_p ** 2 - 24 * n_p + 55 * n_obstacles * n_p ) -// # Predecessor: variant_float_inlining_SoA_symmetry.cpp - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - pos_x.resize(n_p); - pos_y.resize(n_p); - sb_sq.resize(n_p); - sb_e_x.resize(n_p); - sb_e_y.resize(n_p); - e_x.resize(n_p); - e_y.resize(n_p); - curr_vel_x.resize(n_p); - curr_vel_y.resize(n_p); - w_x.resize(n_p); - w_y.resize(n_p); - desired_v0.resize(n_p); - v_max.resize(n_p); - destination_x.resize(n_p); - destination_y.resize(n_p); - color.resize(n_p); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - vector r_ab_x_lut(n_p * n_p, 0.0); - vector r_ab_y_lut(n_p * n_p, 0.0); - vector r_ab_norm_lut(n_p * n_p, 0.0); - - // FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). - // section costs: overhead 4 ADD 4 MULT (+ reductions 4*K = 8 ADD, K=2) | - // distance pair 3 ADD 2 MULT 1 SQRT | - // ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | - // obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV - - // # ADDs: n_iter * ( - // # n_p * 12 + - // # 0.5 * (n_p*n_p - n_p) * 3 + - // # n_p * (n_p - 1) * 11 + - // # n_p * board.n_obstacles * 10 + - // # n_p * 7 - // # ) - - // # MULTs: n_iter * ( - // # n_p * 4 + - // # 0.5 * (n_p*n_p - n_p) * 2 + - // # n_p * (n_p - 1) * 22 + - // # n_p * board.n_obstacles * 13 + - // # n_p * 17 - // # ) - - // # DIVs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles + - // # n_p * 2 - // # ) - - // # SQRTs: n_iter * ( - // # 0.5 * (n_p*n_p - n_p) + - // # n_p * (n_p - 1) * 2 + - // # n_p * board.n_obstacles + - // # n_p * 3 - // # ) - - // # EXPs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles - // # ) - - // total with EXP = 30: n_iter * ( 69 * n_p ** 2 - 24 * n_p + 55 * n_obstacles * n_p ) - for (int t = 0; t < n_iter; t++) { - for (int i = 0; i < n_p; i++) { - - // # 1 MULT - float desired_velocity_x = e_x[i] * desired_v0[i]; - // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; - // # 1 ADD, 1 MULT - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - // # 1 ADD, 1 MULT - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; - - float e_x_i = e_x[i]; - float e_y_i = e_y[i]; - float pos_x_i = pos_x[i]; - float pos_y_i = pos_y[i]; - - // distance precompute, # each pair: 6 FLOPS, total over all i = 0.5*(n_p*n_p - n_p)*6 - for (int j = i + 1; j < n_p; j++) { - // # 1 ADD - float dx = pos_x_i - pos_x[j]; - // # 1 ADD - float dy = pos_y_i - pos_y[j]; - // # 1 ADD, 2 MULTs, 1 SQRT - float norm = sqrt(dx * dx + dy * dy); - int idx = i * n_p + j; - r_ab_x_lut[idx] = dx; - r_ab_y_lut[idx] = dy; - r_ab_norm_lut[idx] = norm; - } - - int j; - - float acc_x_0 = 0.0f, acc_x_1 = 0.0f; - float acc_y_0 = 0.0f, acc_y_1 = 0.0f; - - // ILP over the j < i half. # each lane = 66 FLOPS (same body as the remainder - // loop below where the per-line breakdown is given), K = 2 lanes per step - for (j = 0; j + 2 < i; j += 2) { - float r_ab_x_0 = -1 * r_ab_x_lut[j * n_p + i]; - float r_ab_x_1 = -1 * r_ab_x_lut[(j + 1) * n_p + i]; - - float r_ab_y_0 = -1 * r_ab_y_lut[j * n_p + i]; - float r_ab_y_1 = -1 * r_ab_y_lut[(j + 1) * n_p + i]; - - float r_ab_norm_0 = r_ab_norm_lut[j * n_p + i]; - float r_ab_norm_1 = r_ab_norm_lut[(j + 1) * n_p + i]; - - r_ab_norm_0 = r_ab_norm_0 > EPS ? r_ab_norm_0 : EPS; - r_ab_norm_1 = r_ab_norm_1 > EPS ? r_ab_norm_1 : EPS; - - float r_ab2_x_0 = r_ab_x_0 - sb_e_x[j]; - float r_ab2_x_1 = r_ab_x_1 - sb_e_x[j + 1]; - - float r_ab2_y_0 = r_ab_y_0 - sb_e_y[j]; - float r_ab2_y_1 = r_ab_y_1 - sb_e_y[j + 1]; - - float r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - float r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - - r_ab2_norm_0 = r_ab2_norm_0 > EPS ? r_ab2_norm_0 : EPS; - r_ab2_norm_1 = r_ab2_norm_1 > EPS ? r_ab2_norm_1 : EPS; - - float r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - float r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - - float two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - sb_sq[j]; - float two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - sb_sq[j + 1]; - - two_b_sq_0 = two_b_sq_0 > EPS_SQ ? two_b_sq_0 : EPS_SQ; - two_b_sq_1 = two_b_sq_1 > EPS_SQ ? two_b_sq_1 : EPS_SQ; - - float two_b_0 = sqrt(two_b_sq_0); - float two_b_1 = sqrt(two_b_sq_1); - - float scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - - float scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - float scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - - float scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - float scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - - float f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - - float f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - - float w_val_0, w_val_1; - - float f_norm_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - float f_norm_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - - float rhs_0 = f_norm_sq_0 * cos_phi_sq; - float rhs_1 = f_norm_sq_1 * cos_phi_sq; - - float e_dot_f_0 = e_x_i * f_alpha_beta_neg_x_0 + e_y_i * f_alpha_beta_neg_y_0; - float e_dot_f_1 = e_x_i * f_alpha_beta_neg_x_1 + e_y_i * f_alpha_beta_neg_y_1; - - float e_dot_f_sq_0 = e_dot_f_0 * e_dot_f_0; - float e_dot_f_sq_1 = e_dot_f_1 * e_dot_f_1; - - if (e_dot_f_0 >= 0.0 || e_dot_f_sq_0 <= rhs_0) - w_val_0 = dt_neg; - else - w_val_0 = c_neg_times_dt; - - if (e_dot_f_1 >= 0.0 || e_dot_f_sq_1 <= rhs_1) - w_val_1 = dt_neg; - else - w_val_1 = c_neg_times_dt; - - acc_x_0 += f_alpha_beta_neg_x_0 * w_val_0; - acc_x_1 += f_alpha_beta_neg_x_1 * w_val_1; - acc_y_0 += f_alpha_beta_neg_y_0 * w_val_0; - acc_y_1 += f_alpha_beta_neg_y_1 * w_val_1; - } - - // remainder of the j < i half, single lane. # one j = 66 FLOPS - for (; j < i; j++) { - // lut lookup + -1* sign flip: not counted - float r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - float r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - float r_ab_norm = r_ab_norm_lut[j * n_p + i]; - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x += f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y += f_alpha_beta_neg_y * w_val; - } - - // reduction of the j < i accumulators. # K adds per line (K=2) => 4 ADDs - f_alpha_dt_x += acc_x_0 + acc_x_1; - f_alpha_dt_y += acc_y_0 + acc_y_1; - - acc_x_0 = 0.0f; acc_x_1 = 0.0f; - acc_y_0 = 0.0f; acc_y_1 = 0.0f; - - // ILP over the j > i half. # each lane = 66 FLOPS, K = 2 lanes per step - for (j = i + 1; j + 2 < n_p; j += 2) { - float r_ab_x_0 = r_ab_x_lut[i * n_p + j]; - float r_ab_x_1 = r_ab_x_lut[i * n_p + j + 1]; - - float r_ab_y_0 = r_ab_y_lut[i * n_p + j]; - float r_ab_y_1 = r_ab_y_lut[i * n_p + j + 1]; - - float r_ab_norm_0 = r_ab_norm_lut[i * n_p + j]; - float r_ab_norm_1 = r_ab_norm_lut[i * n_p + j + 1]; - - r_ab_norm_0 = r_ab_norm_0 > EPS ? r_ab_norm_0 : EPS; - r_ab_norm_1 = r_ab_norm_1 > EPS ? r_ab_norm_1 : EPS; - - float r_ab2_x_0 = r_ab_x_0 - sb_e_x[j]; - float r_ab2_x_1 = r_ab_x_1 - sb_e_x[j + 1]; - - float r_ab2_y_0 = r_ab_y_0 - sb_e_y[j]; - float r_ab2_y_1 = r_ab_y_1 - sb_e_y[j + 1]; - - float r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - float r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - - r_ab2_norm_0 = r_ab2_norm_0 > EPS ? r_ab2_norm_0 : EPS; - r_ab2_norm_1 = r_ab2_norm_1 > EPS ? r_ab2_norm_1 : EPS; - - float r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - float r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - - float two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - sb_sq[j]; - float two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - sb_sq[j + 1]; - - two_b_sq_0 = two_b_sq_0 > EPS_SQ ? two_b_sq_0 : EPS_SQ; - two_b_sq_1 = two_b_sq_1 > EPS_SQ ? two_b_sq_1 : EPS_SQ; - - float two_b_0 = sqrt(two_b_sq_0); - float two_b_1 = sqrt(two_b_sq_1); - - float scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - - float scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - float scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - - float scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - float scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - - float f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - - float f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - - float w_val_0, w_val_1; - - float f_norm_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - float f_norm_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - - float rhs_0 = f_norm_sq_0 * cos_phi_sq; - float rhs_1 = f_norm_sq_1 * cos_phi_sq; - - float e_dot_f_0 = e_x_i * f_alpha_beta_neg_x_0 + e_y_i * f_alpha_beta_neg_y_0; - float e_dot_f_1 = e_x_i * f_alpha_beta_neg_x_1 + e_y_i * f_alpha_beta_neg_y_1; - - float e_dot_f_sq_0 = e_dot_f_0 * e_dot_f_0; - float e_dot_f_sq_1 = e_dot_f_1 * e_dot_f_1; - - if (e_dot_f_0 >= 0.0 || e_dot_f_sq_0 <= rhs_0) - w_val_0 = dt_neg; - else - w_val_0 = c_neg_times_dt; - - if (e_dot_f_1 >= 0.0 || e_dot_f_sq_1 <= rhs_1) - w_val_1 = dt_neg; - else - w_val_1 = c_neg_times_dt; - - acc_x_0 += f_alpha_beta_neg_x_0 * w_val_0; - acc_x_1 += f_alpha_beta_neg_x_1 * w_val_1; - acc_y_0 += f_alpha_beta_neg_y_0 * w_val_0; - acc_y_1 += f_alpha_beta_neg_y_1 * w_val_1; - } - - // remainder of the j > i half, single lane. # one j = 66 FLOPS - for (; j < n_p; j++) { - float r_ab_x = r_ab_x_lut[i * n_p + j]; - float r_ab_y = r_ab_y_lut[i * n_p + j]; - float r_ab_norm = r_ab_norm_lut[i * n_p + j]; - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x += f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y += f_alpha_beta_neg_y * w_val; - } - - // reduction of the j > i accumulators. # K adds per line (K=2) => 4 ADDs - f_alpha_dt_x += acc_x_0 + acc_x_1; - f_alpha_dt_y += acc_y_0 + acc_y_1; - - // force between pedestrian and obstacles - // # one obstacle = 55 FLOPS (w_val sign-based, no a*a / b_sq) - for (int k = 0; k < board.n_obstacles; k++) { - // # 1 ADD - float v1_x = pos_x[i] - board.obstacles[k].start_x; - // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - // # 1 ADD, 3 MULTs - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - // # 2 ADDs, 1 MULT - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - // # 2 ADDs, 1 MULT - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - // # 2 MULTs, 1 DIV, 1 EXP - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - // # 1 MULT - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; - - float w_val; - - // sign select only, no flops - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 MULT - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // # 1 ADD - f_alpha_dt_x += f_alpha_B_dt_x; - // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // # 1 ADD - w_x[i] += f_alpha_dt_x; - // # 1 ADD - w_y[i] += f_alpha_dt_y; - } - - // update each pedestrian - // # 29 FLOPS per pedestrian - for (int i = 0; i < n_p; i++) { - // # 1 ADD, 2 MULTs, 1 SQRT - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); - - float w_norm2 = w_norm > EPS ? w_norm : EPS; - - // # 1 DIV - float correction_factor = min(1.0f, v_max[i] / w_norm2); - - // # 1 MULT - curr_vel_x[i] = w_x[i] * correction_factor; - // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; - - // # 1 ADD, 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - // # 1 ADD, 1 MULT - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 1 ADD - float ex = destination_x[i] - pos_x[i]; - // # 1 ADD - float ey = destination_y[i] - pos_y[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float e_norm = sqrt(ex * ex + ey * ey); - float e_norm2 = e_norm > EPS ? e_norm : EPS; - // # 1 DIV - float e_norm_inv = 1.0 / e_norm2; - - // # 1 MULT - e_x[i] = ex * e_norm_inv; - // # 1 MULT - e_y[i] = ey * e_norm_inv; - - // # 1 ADD, 2 MULTs - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; - - // # 1 SQRT - float velocity_norm = sqrt(velocity_norm_sq); - - // # 1 MULT - sb_sq[i] = velocity_norm_sq * delta_t_sq; - - // # 2 MULTs (velocity_norm*delta_t written on both lines, counted as is) - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - // # 2 MULTs - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_symmetry_ILP_4_flopcount.cpp b/variants/variant_float_inlining_SoA_symmetry_ILP_4_flopcount.cpp deleted file mode 100644 index e6d035c..0000000 --- a/variants/variant_float_inlining_SoA_symmetry_ILP_4_flopcount.cpp +++ /dev/null @@ -1,816 +0,0 @@ -// # Changes: Added ILP for j-loop with K = 4 -// # added flopcount -// FLOPCOUNT = n_iter * ( 69 * n_p ** 2 - 16 * n_p + 55 * n_obstacles * n_p ) -// # Predecessor: variant_float_inlining_SoA_symmetry_ILP_2.cpp - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - pos_x.resize(n_p); - pos_y.resize(n_p); - sb_sq.resize(n_p); - sb_e_x.resize(n_p); - sb_e_y.resize(n_p); - e_x.resize(n_p); - e_y.resize(n_p); - curr_vel_x.resize(n_p); - curr_vel_y.resize(n_p); - w_x.resize(n_p); - w_y.resize(n_p); - desired_v0.resize(n_p); - v_max.resize(n_p); - destination_x.resize(n_p); - destination_y.resize(n_p); - color.resize(n_p); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - vector r_ab_x_lut(n_p * n_p, 0.0); - vector r_ab_y_lut(n_p * n_p, 0.0); - vector r_ab_norm_lut(n_p * n_p, 0.0); - - // FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). - // section costs: overhead 4 ADD 4 MULT (+ reductions 4*K = 16 ADD, K=4) | - // distance pair 3 ADD 2 MULT 1 SQRT | - // ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | - // obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV - - // # ADDs: n_iter * ( - // # n_p * 20 + - // # 0.5 * (n_p*n_p - n_p) * 3 + - // # n_p * (n_p - 1) * 11 + - // # n_p * board.n_obstacles * 10 + - // # n_p * 7 - // # ) - - // # MULTs: n_iter * ( - // # n_p * 4 + - // # 0.5 * (n_p*n_p - n_p) * 2 + - // # n_p * (n_p - 1) * 22 + - // # n_p * board.n_obstacles * 13 + - // # n_p * 17 - // # ) - - // # DIVs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles + - // # n_p * 2 - // # ) - - // # SQRTs: n_iter * ( - // # 0.5 * (n_p*n_p - n_p) + - // # n_p * (n_p - 1) * 2 + - // # n_p * board.n_obstacles + - // # n_p * 3 - // # ) - - // # EXPs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles - // # ) - - // total with EXP = 30: n_iter * ( 69 * n_p ** 2 - 16 * n_p + 55 * n_obstacles * n_p ) - for (int t = 0; t < n_iter; t++) { - for (int i = 0; i < n_p; i++) { - - // # 1 MULT - float desired_velocity_x = e_x[i] * desired_v0[i]; - // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; - // # 1 ADD, 1 MULT - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - // # 1 ADD, 1 MULT - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; - - float e_x_i = e_x[i]; - float e_y_i = e_y[i]; - float pos_x_i = pos_x[i]; - float pos_y_i = pos_y[i]; - - // distance precompute, # each pair: 6 FLOPS, total over all i = 0.5*(n_p*n_p - n_p)*6 - for (int j = i + 1; j < n_p; j++) { - // # 1 ADD - float dx = pos_x_i - pos_x[j]; - // # 1 ADD - float dy = pos_y_i - pos_y[j]; - // # 1 ADD, 2 MULTs, 1 SQRT - float norm = sqrt(dx * dx + dy * dy); - int idx = i * n_p + j; - r_ab_x_lut[idx] = dx; - r_ab_y_lut[idx] = dy; - r_ab_norm_lut[idx] = norm; - } - - int j; - - float acc_x_0 = 0.0f, acc_x_1 = 0.0f, acc_x_2 = 0.0f, acc_x_3 = 0.0f; - float acc_y_0 = 0.0f, acc_y_1 = 0.0f, acc_y_2 = 0.0f, acc_y_3 = 0.0f; - - // ILP over the j < i half. # each lane = 66 FLOPS (per-line breakdown is the - // same as the single-lane remainder loop below), K = 4 lanes per step - for (j = 0; j + 4 < i; j += 4) { - float r_ab_x_0 = -1 * r_ab_x_lut[j * n_p + i]; - float r_ab_x_1 = -1 * r_ab_x_lut[(j + 1) * n_p + i]; - float r_ab_x_2 = -1 * r_ab_x_lut[(j + 2) * n_p + i]; - float r_ab_x_3 = -1 * r_ab_x_lut[(j + 3) * n_p + i]; - - float r_ab_y_0 = -1 * r_ab_y_lut[j * n_p + i]; - float r_ab_y_1 = -1 * r_ab_y_lut[(j + 1) * n_p + i]; - float r_ab_y_2 = -1 * r_ab_y_lut[(j + 2) * n_p + i]; - float r_ab_y_3 = -1 * r_ab_y_lut[(j + 3) * n_p + i]; - - float r_ab_norm_0 = r_ab_norm_lut[j * n_p + i]; - float r_ab_norm_1 = r_ab_norm_lut[(j + 1) * n_p + i]; - float r_ab_norm_2 = r_ab_norm_lut[(j + 2) * n_p + i]; - float r_ab_norm_3 = r_ab_norm_lut[(j + 3) * n_p + i]; - - r_ab_norm_0 = r_ab_norm_0 > EPS ? r_ab_norm_0 : EPS; - r_ab_norm_1 = r_ab_norm_1 > EPS ? r_ab_norm_1 : EPS; - r_ab_norm_2 = r_ab_norm_2 > EPS ? r_ab_norm_2 : EPS; - r_ab_norm_3 = r_ab_norm_3 > EPS ? r_ab_norm_3 : EPS; - - float r_ab2_x_0 = r_ab_x_0 - sb_e_x[j]; - float r_ab2_x_1 = r_ab_x_1 - sb_e_x[j + 1]; - float r_ab2_x_2 = r_ab_x_2 - sb_e_x[j + 2]; - float r_ab2_x_3 = r_ab_x_3 - sb_e_x[j + 3]; - - float r_ab2_y_0 = r_ab_y_0 - sb_e_y[j]; - float r_ab2_y_1 = r_ab_y_1 - sb_e_y[j + 1]; - float r_ab2_y_2 = r_ab_y_2 - sb_e_y[j + 2]; - float r_ab2_y_3 = r_ab_y_3 - sb_e_y[j + 3]; - - float r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - float r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - float r_ab2_norm_2 = sqrt(r_ab2_x_2 * r_ab2_x_2 + r_ab2_y_2 * r_ab2_y_2); - float r_ab2_norm_3 = sqrt(r_ab2_x_3 * r_ab2_x_3 + r_ab2_y_3 * r_ab2_y_3); - - r_ab2_norm_0 = r_ab2_norm_0 > EPS ? r_ab2_norm_0 : EPS; - r_ab2_norm_1 = r_ab2_norm_1 > EPS ? r_ab2_norm_1 : EPS; - r_ab2_norm_2 = r_ab2_norm_2 > EPS ? r_ab2_norm_2 : EPS; - r_ab2_norm_3 = r_ab2_norm_3 > EPS ? r_ab2_norm_3 : EPS; - - float r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - float r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - float r_ab_norm_sum_2 = r_ab_norm_2 + r_ab2_norm_2; - float r_ab_norm_sum_3 = r_ab_norm_3 + r_ab2_norm_3; - - float two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - sb_sq[j]; - float two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - sb_sq[j + 1]; - float two_b_sq_2 = r_ab_norm_sum_2 * r_ab_norm_sum_2 - sb_sq[j + 2]; - float two_b_sq_3 = r_ab_norm_sum_3 * r_ab_norm_sum_3 - sb_sq[j + 3]; - - two_b_sq_0 = two_b_sq_0 > EPS_SQ ? two_b_sq_0 : EPS_SQ; - two_b_sq_1 = two_b_sq_1 > EPS_SQ ? two_b_sq_1 : EPS_SQ; - two_b_sq_2 = two_b_sq_2 > EPS_SQ ? two_b_sq_2 : EPS_SQ; - two_b_sq_3 = two_b_sq_3 > EPS_SQ ? two_b_sq_3 : EPS_SQ; - - float two_b_0 = sqrt(two_b_sq_0); - float two_b_1 = sqrt(two_b_sq_1); - float two_b_2 = sqrt(two_b_sq_2); - float two_b_3 = sqrt(two_b_sq_3); - - float scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_2 = (r_ab_norm_sum_2 / (two_b_2 * r_ab_norm_2 * r_ab2_norm_2)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_2 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_3 = (r_ab_norm_sum_3 / (two_b_3 * r_ab_norm_3 * r_ab2_norm_3)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_3 * NEG_TWO_SIGMA_INV)); - - float scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - float scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - float scalar_mult_r_ab_2 = scalar_mult_common_2 * r_ab2_norm_2; - float scalar_mult_r_ab_3 = scalar_mult_common_3 * r_ab2_norm_3; - - float scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - float scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - float scalar_mult_r_ab2_2 = scalar_mult_common_2 * r_ab_norm_2; - float scalar_mult_r_ab2_3 = scalar_mult_common_3 * r_ab_norm_3; - - float f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_x_2 = r_ab_x_2 * scalar_mult_r_ab_2 + r_ab2_x_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_x_3 = r_ab_x_3 * scalar_mult_r_ab_3 + r_ab2_x_3 * scalar_mult_r_ab2_3; - - float f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_y_2 = r_ab_y_2 * scalar_mult_r_ab_2 + r_ab2_y_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_y_3 = r_ab_y_3 * scalar_mult_r_ab_3 + r_ab2_y_3 * scalar_mult_r_ab2_3; - - float w_val_0, w_val_1, w_val_2, w_val_3; - - float f_norm_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - float f_norm_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - float f_norm_sq_2 = f_alpha_beta_neg_x_2 * f_alpha_beta_neg_x_2 + f_alpha_beta_neg_y_2 * f_alpha_beta_neg_y_2; - float f_norm_sq_3 = f_alpha_beta_neg_x_3 * f_alpha_beta_neg_x_3 + f_alpha_beta_neg_y_3 * f_alpha_beta_neg_y_3; - - float rhs_0 = f_norm_sq_0 * cos_phi_sq; - float rhs_1 = f_norm_sq_1 * cos_phi_sq; - float rhs_2 = f_norm_sq_2 * cos_phi_sq; - float rhs_3 = f_norm_sq_3 * cos_phi_sq; - - float e_dot_f_0 = e_x_i * f_alpha_beta_neg_x_0 + e_y_i * f_alpha_beta_neg_y_0; - float e_dot_f_1 = e_x_i * f_alpha_beta_neg_x_1 + e_y_i * f_alpha_beta_neg_y_1; - float e_dot_f_2 = e_x_i * f_alpha_beta_neg_x_2 + e_y_i * f_alpha_beta_neg_y_2; - float e_dot_f_3 = e_x_i * f_alpha_beta_neg_x_3 + e_y_i * f_alpha_beta_neg_y_3; - - float e_dot_f_sq_0 = e_dot_f_0 * e_dot_f_0; - float e_dot_f_sq_1 = e_dot_f_1 * e_dot_f_1; - float e_dot_f_sq_2 = e_dot_f_2 * e_dot_f_2; - float e_dot_f_sq_3 = e_dot_f_3 * e_dot_f_3; - - if (e_dot_f_0 >= 0.0 || e_dot_f_sq_0 <= rhs_0) - w_val_0 = dt_neg; - else - w_val_0 = c_neg_times_dt; - if (e_dot_f_1 >= 0.0 || e_dot_f_sq_1 <= rhs_1) - w_val_1 = dt_neg; - else - w_val_1 = c_neg_times_dt; - if (e_dot_f_2 >= 0.0 || e_dot_f_sq_2 <= rhs_2) - w_val_2 = dt_neg; - else - w_val_2 = c_neg_times_dt; - if (e_dot_f_3 >= 0.0 || e_dot_f_sq_3 <= rhs_3) - w_val_3 = dt_neg; - else - w_val_3 = c_neg_times_dt; - - acc_x_0 += f_alpha_beta_neg_x_0 * w_val_0; - acc_x_1 += f_alpha_beta_neg_x_1 * w_val_1; - acc_x_2 += f_alpha_beta_neg_x_2 * w_val_2; - acc_x_3 += f_alpha_beta_neg_x_3 * w_val_3; - acc_y_0 += f_alpha_beta_neg_y_0 * w_val_0; - acc_y_1 += f_alpha_beta_neg_y_1 * w_val_1; - acc_y_2 += f_alpha_beta_neg_y_2 * w_val_2; - acc_y_3 += f_alpha_beta_neg_y_3 * w_val_3; - } - - // remainder of the j < i half, single lane. # one j = 66 FLOPS - for (; j < i; j++) { - // lut lookup + -1* sign flip: not counted - float r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - float r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - float r_ab_norm = r_ab_norm_lut[j * n_p + i]; - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x += f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y += f_alpha_beta_neg_y * w_val; - } - - // reduction of the j < i accumulators. # K adds per line (K=4) => 8 ADDs - f_alpha_dt_x += acc_x_0 + acc_x_1 + acc_x_2 + acc_x_3; - f_alpha_dt_y += acc_y_0 + acc_y_1 + acc_y_2 + acc_y_3; - - acc_x_0 = 0.0f; - acc_x_1 = 0.0f; - acc_x_2 = 0.0f; - acc_x_3 = 0.0f; - acc_y_0 = 0.0f; - acc_y_1 = 0.0f; - acc_y_2 = 0.0f; - acc_y_3 = 0.0f; - - // ILP over the j > i half. # each lane = 66 FLOPS, K = 4 lanes per step - for (j = i + 1; j + 4 < n_p; j += 4) { - float r_ab_x_0 = r_ab_x_lut[i * n_p + j]; - float r_ab_x_1 = r_ab_x_lut[i * n_p + j + 1]; - float r_ab_x_2 = r_ab_x_lut[i * n_p + j + 2]; - float r_ab_x_3 = r_ab_x_lut[i * n_p + j + 3]; - - float r_ab_y_0 = r_ab_y_lut[i * n_p + j]; - float r_ab_y_1 = r_ab_y_lut[i * n_p + j + 1]; - float r_ab_y_2 = r_ab_y_lut[i * n_p + j + 2]; - float r_ab_y_3 = r_ab_y_lut[i * n_p + j + 3]; - - float r_ab_norm_0 = r_ab_norm_lut[i * n_p + j]; - float r_ab_norm_1 = r_ab_norm_lut[i * n_p + j + 1]; - float r_ab_norm_2 = r_ab_norm_lut[i * n_p + j + 2]; - float r_ab_norm_3 = r_ab_norm_lut[i * n_p + j + 3]; - - r_ab_norm_0 = r_ab_norm_0 > EPS ? r_ab_norm_0 : EPS; - r_ab_norm_1 = r_ab_norm_1 > EPS ? r_ab_norm_1 : EPS; - r_ab_norm_2 = r_ab_norm_2 > EPS ? r_ab_norm_2 : EPS; - r_ab_norm_3 = r_ab_norm_3 > EPS ? r_ab_norm_3 : EPS; - - float r_ab2_x_0 = r_ab_x_0 - sb_e_x[j]; - float r_ab2_x_1 = r_ab_x_1 - sb_e_x[j + 1]; - float r_ab2_x_2 = r_ab_x_2 - sb_e_x[j + 2]; - float r_ab2_x_3 = r_ab_x_3 - sb_e_x[j + 3]; - - float r_ab2_y_0 = r_ab_y_0 - sb_e_y[j]; - float r_ab2_y_1 = r_ab_y_1 - sb_e_y[j + 1]; - float r_ab2_y_2 = r_ab_y_2 - sb_e_y[j + 2]; - float r_ab2_y_3 = r_ab_y_3 - sb_e_y[j + 3]; - - float r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - float r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - float r_ab2_norm_2 = sqrt(r_ab2_x_2 * r_ab2_x_2 + r_ab2_y_2 * r_ab2_y_2); - float r_ab2_norm_3 = sqrt(r_ab2_x_3 * r_ab2_x_3 + r_ab2_y_3 * r_ab2_y_3); - - r_ab2_norm_0 = r_ab2_norm_0 > EPS ? r_ab2_norm_0 : EPS; - r_ab2_norm_1 = r_ab2_norm_1 > EPS ? r_ab2_norm_1 : EPS; - r_ab2_norm_2 = r_ab2_norm_2 > EPS ? r_ab2_norm_2 : EPS; - r_ab2_norm_3 = r_ab2_norm_3 > EPS ? r_ab2_norm_3 : EPS; - - float r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - float r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - float r_ab_norm_sum_2 = r_ab_norm_2 + r_ab2_norm_2; - float r_ab_norm_sum_3 = r_ab_norm_3 + r_ab2_norm_3; - - float two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - sb_sq[j]; - float two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - sb_sq[j + 1]; - float two_b_sq_2 = r_ab_norm_sum_2 * r_ab_norm_sum_2 - sb_sq[j + 2]; - float two_b_sq_3 = r_ab_norm_sum_3 * r_ab_norm_sum_3 - sb_sq[j + 3]; - - two_b_sq_0 = two_b_sq_0 > EPS_SQ ? two_b_sq_0 : EPS_SQ; - two_b_sq_1 = two_b_sq_1 > EPS_SQ ? two_b_sq_1 : EPS_SQ; - two_b_sq_2 = two_b_sq_2 > EPS_SQ ? two_b_sq_2 : EPS_SQ; - two_b_sq_3 = two_b_sq_3 > EPS_SQ ? two_b_sq_3 : EPS_SQ; - - float two_b_0 = sqrt(two_b_sq_0); - float two_b_1 = sqrt(two_b_sq_1); - float two_b_2 = sqrt(two_b_sq_2); - float two_b_3 = sqrt(two_b_sq_3); - - float scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_2 = (r_ab_norm_sum_2 / (two_b_2 * r_ab_norm_2 * r_ab2_norm_2)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_2 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_3 = (r_ab_norm_sum_3 / (two_b_3 * r_ab_norm_3 * r_ab2_norm_3)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_3 * NEG_TWO_SIGMA_INV)); - - float scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - float scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - float scalar_mult_r_ab_2 = scalar_mult_common_2 * r_ab2_norm_2; - float scalar_mult_r_ab_3 = scalar_mult_common_3 * r_ab2_norm_3; - - float scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - float scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - float scalar_mult_r_ab2_2 = scalar_mult_common_2 * r_ab_norm_2; - float scalar_mult_r_ab2_3 = scalar_mult_common_3 * r_ab_norm_3; - - float f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_x_2 = r_ab_x_2 * scalar_mult_r_ab_2 + r_ab2_x_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_x_3 = r_ab_x_3 * scalar_mult_r_ab_3 + r_ab2_x_3 * scalar_mult_r_ab2_3; - - float f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_y_2 = r_ab_y_2 * scalar_mult_r_ab_2 + r_ab2_y_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_y_3 = r_ab_y_3 * scalar_mult_r_ab_3 + r_ab2_y_3 * scalar_mult_r_ab2_3; - - float w_val_0, w_val_1, w_val_2, w_val_3; - - float f_norm_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - float f_norm_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - float f_norm_sq_2 = f_alpha_beta_neg_x_2 * f_alpha_beta_neg_x_2 + f_alpha_beta_neg_y_2 * f_alpha_beta_neg_y_2; - float f_norm_sq_3 = f_alpha_beta_neg_x_3 * f_alpha_beta_neg_x_3 + f_alpha_beta_neg_y_3 * f_alpha_beta_neg_y_3; - - float rhs_0 = f_norm_sq_0 * cos_phi_sq; - float rhs_1 = f_norm_sq_1 * cos_phi_sq; - float rhs_2 = f_norm_sq_2 * cos_phi_sq; - float rhs_3 = f_norm_sq_3 * cos_phi_sq; - - float e_dot_f_0 = e_x_i * f_alpha_beta_neg_x_0 + e_y_i * f_alpha_beta_neg_y_0; - float e_dot_f_1 = e_x_i * f_alpha_beta_neg_x_1 + e_y_i * f_alpha_beta_neg_y_1; - float e_dot_f_2 = e_x_i * f_alpha_beta_neg_x_2 + e_y_i * f_alpha_beta_neg_y_2; - float e_dot_f_3 = e_x_i * f_alpha_beta_neg_x_3 + e_y_i * f_alpha_beta_neg_y_3; - - float e_dot_f_sq_0 = e_dot_f_0 * e_dot_f_0; - float e_dot_f_sq_1 = e_dot_f_1 * e_dot_f_1; - float e_dot_f_sq_2 = e_dot_f_2 * e_dot_f_2; - float e_dot_f_sq_3 = e_dot_f_3 * e_dot_f_3; - - if (e_dot_f_0 >= 0.0 || e_dot_f_sq_0 <= rhs_0) - w_val_0 = dt_neg; - else - w_val_0 = c_neg_times_dt; - if (e_dot_f_1 >= 0.0 || e_dot_f_sq_1 <= rhs_1) - w_val_1 = dt_neg; - else - w_val_1 = c_neg_times_dt; - if (e_dot_f_2 >= 0.0 || e_dot_f_sq_2 <= rhs_2) - w_val_2 = dt_neg; - else - w_val_2 = c_neg_times_dt; - if (e_dot_f_3 >= 0.0 || e_dot_f_sq_3 <= rhs_3) - w_val_3 = dt_neg; - else - w_val_3 = c_neg_times_dt; - - acc_x_0 += f_alpha_beta_neg_x_0 * w_val_0; - acc_x_1 += f_alpha_beta_neg_x_1 * w_val_1; - acc_x_2 += f_alpha_beta_neg_x_2 * w_val_2; - acc_x_3 += f_alpha_beta_neg_x_3 * w_val_3; - acc_y_0 += f_alpha_beta_neg_y_0 * w_val_0; - acc_y_1 += f_alpha_beta_neg_y_1 * w_val_1; - acc_y_2 += f_alpha_beta_neg_y_2 * w_val_2; - acc_y_3 += f_alpha_beta_neg_y_3 * w_val_3; - } - - // remainder of the j > i half, single lane. # one j = 66 FLOPS - for (; j < n_p; j++) { - float r_ab_x = r_ab_x_lut[i * n_p + j]; - float r_ab_y = r_ab_y_lut[i * n_p + j]; - float r_ab_norm = r_ab_norm_lut[i * n_p + j]; - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x += f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y += f_alpha_beta_neg_y * w_val; - } - - // reduction of the j > i accumulators. # K adds per line (K=4) => 8 ADDs - f_alpha_dt_x += acc_x_0 + acc_x_1 + acc_x_2 + acc_x_3; - f_alpha_dt_y += acc_y_0 + acc_y_1 + acc_y_2 + acc_y_3; - - // force between pedestrian and obstacles - // # one obstacle = 55 FLOPS (w_val sign-based, no a*a / b_sq) - for (int k = 0; k < board.n_obstacles; k++) { - // # 1 ADD - float v1_x = pos_x[i] - board.obstacles[k].start_x; - // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - // # 1 ADD, 3 MULTs - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - // # 2 ADDs, 1 MULT - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - // # 2 ADDs, 1 MULT - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - // # 2 MULTs, 1 DIV, 1 EXP - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - // # 1 MULT - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; - - float w_val; - - // sign select only, no flops - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 MULT - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // # 1 ADD - f_alpha_dt_x += f_alpha_B_dt_x; - // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // # 1 ADD - w_x[i] += f_alpha_dt_x; - // # 1 ADD - w_y[i] += f_alpha_dt_y; - } - - // update each pedestrian - // # 29 FLOPS per pedestrian - for (int i = 0; i < n_p; i++) { - - // # 1 ADD, 2 MULTs, 1 SQRT - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); - - float w_norm2 = w_norm > EPS ? w_norm : EPS; - - // # 1 DIV - float correction_factor = min(1.0f, v_max[i] / w_norm2); - - // # 1 MULT - curr_vel_x[i] = w_x[i] * correction_factor; - // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; - - // # 1 ADD, 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - // # 1 ADD, 1 MULT - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 1 ADD - float ex = destination_x[i] - pos_x[i]; - // # 1 ADD - float ey = destination_y[i] - pos_y[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float e_norm = sqrt(ex * ex + ey * ey); - float e_norm2 = e_norm > EPS ? e_norm : EPS; - // # 1 DIV - float e_norm_inv = 1.0 / e_norm2; - - // # 1 MULT - e_x[i] = ex * e_norm_inv; - // # 1 MULT - e_y[i] = ey * e_norm_inv; - - // # 1 ADD, 2 MULTs - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; - - // # 1 SQRT - float velocity_norm = sqrt(velocity_norm_sq); - - // # 1 MULT - sb_sq[i] = velocity_norm_sq * delta_t_sq; - - // # 2 MULTs (velocity_norm*delta_t written on both lines, counted as is) - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - // # 2 MULTs - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_symmetry_ILP_8_flopcount.cpp b/variants/variant_float_inlining_SoA_symmetry_ILP_8_flopcount.cpp deleted file mode 100644 index 0ef7642..0000000 --- a/variants/variant_float_inlining_SoA_symmetry_ILP_8_flopcount.cpp +++ /dev/null @@ -1,1042 +0,0 @@ -// # Changes: Added ILP for j-loop with K = 8 -// # added flopcount -// FLOPCOUNT = n_iter * ( 69 * n_p ** 2 + 55 * n_obstacles * n_p ) // the linear n_p term cancels (4*K - 32 = 0) -// # Predecessor: variant_float_inlining_SoA_symmetry_ILP_4.cpp - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - pos_x.resize(n_p); - pos_y.resize(n_p); - sb_sq.resize(n_p); - sb_e_x.resize(n_p); - sb_e_y.resize(n_p); - e_x.resize(n_p); - e_y.resize(n_p); - curr_vel_x.resize(n_p); - curr_vel_y.resize(n_p); - w_x.resize(n_p); - w_y.resize(n_p); - desired_v0.resize(n_p); - v_max.resize(n_p); - destination_x.resize(n_p); - destination_y.resize(n_p); - color.resize(n_p); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - vector r_ab_x_lut(n_p * n_p, 0.0); - vector r_ab_y_lut(n_p * n_p, 0.0); - vector r_ab_norm_lut(n_p * n_p, 0.0); - - // FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). - // section costs: overhead 4 ADD 4 MULT (+ reductions 4*K = 32 ADD, K=8) | - // distance pair 3 ADD 2 MULT 1 SQRT | - // ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | - // obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV - - // # ADDs: n_iter * ( - // # n_p * 36 + - // # 0.5 * (n_p*n_p - n_p) * 3 + - // # n_p * (n_p - 1) * 11 + - // # n_p * board.n_obstacles * 10 + - // # n_p * 7 - // # ) - - // # MULTs: n_iter * ( - // # n_p * 4 + - // # 0.5 * (n_p*n_p - n_p) * 2 + - // # n_p * (n_p - 1) * 22 + - // # n_p * board.n_obstacles * 13 + - // # n_p * 17 - // # ) - - // # DIVs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles + - // # n_p * 2 - // # ) - - // # SQRTs: n_iter * ( - // # 0.5 * (n_p*n_p - n_p) + - // # n_p * (n_p - 1) * 2 + - // # n_p * board.n_obstacles + - // # n_p * 3 - // # ) - - // # EXPs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles - // # ) - - // total with EXP = 30: n_iter * ( 69 * n_p ** 2 + 55 * n_obstacles * n_p ) // linear term cancels - for (int t = 0; t < n_iter; t++) { - for (int i = 0; i < n_p; i++) { - - // # 1 MULT - float desired_velocity_x = e_x[i] * desired_v0[i]; - // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; - // # 1 ADD, 1 MULT - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - // # 1 ADD, 1 MULT - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; - - float e_x_i = e_x[i]; - float e_y_i = e_y[i]; - float pos_x_i = pos_x[i]; - float pos_y_i = pos_y[i]; - - // distance precompute, # each pair: 6 FLOPS, total over all i = 0.5*(n_p*n_p - n_p)*6 - for (int j = i + 1; j < n_p; j++) { - // # 1 ADD - float dx = pos_x_i - pos_x[j]; - // # 1 ADD - float dy = pos_y_i - pos_y[j]; - // # 1 ADD, 2 MULTs, 1 SQRT - float norm = sqrt(dx * dx + dy * dy); - int idx = i * n_p + j; - r_ab_x_lut[idx] = dx; - r_ab_y_lut[idx] = dy; - r_ab_norm_lut[idx] = norm; - } - - int j; - - float acc_x_0 = 0.0f, acc_x_1 = 0.0f, acc_x_2 = 0.0f, acc_x_3 = 0.0f; - float acc_x_4 = 0.0f, acc_x_5 = 0.0f, acc_x_6 = 0.0f, acc_x_7 = 0.0f; - float acc_y_0 = 0.0f, acc_y_1 = 0.0f, acc_y_2 = 0.0f, acc_y_3 = 0.0f; - float acc_y_4 = 0.0f, acc_y_5 = 0.0f, acc_y_6 = 0.0f, acc_y_7 = 0.0f; - - // ILP over the j < i half. # each lane = 66 FLOPS (per-line breakdown is the - // same as the single-lane remainder loop below), K = 8 lanes per step - for (j = 0; j + 8 < i; j += 8) { - float r_ab_x_0 = -1 * r_ab_x_lut[j * n_p + i]; - float r_ab_x_1 = -1 * r_ab_x_lut[(j + 1) * n_p + i]; - float r_ab_x_2 = -1 * r_ab_x_lut[(j + 2) * n_p + i]; - float r_ab_x_3 = -1 * r_ab_x_lut[(j + 3) * n_p + i]; - float r_ab_x_4 = -1 * r_ab_x_lut[(j + 4) * n_p + i]; - float r_ab_x_5 = -1 * r_ab_x_lut[(j + 5) * n_p + i]; - float r_ab_x_6 = -1 * r_ab_x_lut[(j + 6) * n_p + i]; - float r_ab_x_7 = -1 * r_ab_x_lut[(j + 7) * n_p + i]; - - float r_ab_y_0 = -1 * r_ab_y_lut[j * n_p + i]; - float r_ab_y_1 = -1 * r_ab_y_lut[(j + 1) * n_p + i]; - float r_ab_y_2 = -1 * r_ab_y_lut[(j + 2) * n_p + i]; - float r_ab_y_3 = -1 * r_ab_y_lut[(j + 3) * n_p + i]; - float r_ab_y_4 = -1 * r_ab_y_lut[(j + 4) * n_p + i]; - float r_ab_y_5 = -1 * r_ab_y_lut[(j + 5) * n_p + i]; - float r_ab_y_6 = -1 * r_ab_y_lut[(j + 6) * n_p + i]; - float r_ab_y_7 = -1 * r_ab_y_lut[(j + 7) * n_p + i]; - - float r_ab_norm_0 = r_ab_norm_lut[j * n_p + i]; - float r_ab_norm_1 = r_ab_norm_lut[(j + 1) * n_p + i]; - float r_ab_norm_2 = r_ab_norm_lut[(j + 2) * n_p + i]; - float r_ab_norm_3 = r_ab_norm_lut[(j + 3) * n_p + i]; - float r_ab_norm_4 = r_ab_norm_lut[(j + 4) * n_p + i]; - float r_ab_norm_5 = r_ab_norm_lut[(j + 5) * n_p + i]; - float r_ab_norm_6 = r_ab_norm_lut[(j + 6) * n_p + i]; - float r_ab_norm_7 = r_ab_norm_lut[(j + 7) * n_p + i]; - - r_ab_norm_0 = r_ab_norm_0 > EPS ? r_ab_norm_0 : EPS; - r_ab_norm_1 = r_ab_norm_1 > EPS ? r_ab_norm_1 : EPS; - r_ab_norm_2 = r_ab_norm_2 > EPS ? r_ab_norm_2 : EPS; - r_ab_norm_3 = r_ab_norm_3 > EPS ? r_ab_norm_3 : EPS; - r_ab_norm_4 = r_ab_norm_4 > EPS ? r_ab_norm_4 : EPS; - r_ab_norm_5 = r_ab_norm_5 > EPS ? r_ab_norm_5 : EPS; - r_ab_norm_6 = r_ab_norm_6 > EPS ? r_ab_norm_6 : EPS; - r_ab_norm_7 = r_ab_norm_7 > EPS ? r_ab_norm_7 : EPS; - - float r_ab2_x_0 = r_ab_x_0 - sb_e_x[j]; - float r_ab2_x_1 = r_ab_x_1 - sb_e_x[j + 1]; - float r_ab2_x_2 = r_ab_x_2 - sb_e_x[j + 2]; - float r_ab2_x_3 = r_ab_x_3 - sb_e_x[j + 3]; - float r_ab2_x_4 = r_ab_x_4 - sb_e_x[j + 4]; - float r_ab2_x_5 = r_ab_x_5 - sb_e_x[j + 5]; - float r_ab2_x_6 = r_ab_x_6 - sb_e_x[j + 6]; - float r_ab2_x_7 = r_ab_x_7 - sb_e_x[j + 7]; - - float r_ab2_y_0 = r_ab_y_0 - sb_e_y[j]; - float r_ab2_y_1 = r_ab_y_1 - sb_e_y[j + 1]; - float r_ab2_y_2 = r_ab_y_2 - sb_e_y[j + 2]; - float r_ab2_y_3 = r_ab_y_3 - sb_e_y[j + 3]; - float r_ab2_y_4 = r_ab_y_4 - sb_e_y[j + 4]; - float r_ab2_y_5 = r_ab_y_5 - sb_e_y[j + 5]; - float r_ab2_y_6 = r_ab_y_6 - sb_e_y[j + 6]; - float r_ab2_y_7 = r_ab_y_7 - sb_e_y[j + 7]; - - float r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - float r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - float r_ab2_norm_2 = sqrt(r_ab2_x_2 * r_ab2_x_2 + r_ab2_y_2 * r_ab2_y_2); - float r_ab2_norm_3 = sqrt(r_ab2_x_3 * r_ab2_x_3 + r_ab2_y_3 * r_ab2_y_3); - float r_ab2_norm_4 = sqrt(r_ab2_x_4 * r_ab2_x_4 + r_ab2_y_4 * r_ab2_y_4); - float r_ab2_norm_5 = sqrt(r_ab2_x_5 * r_ab2_x_5 + r_ab2_y_5 * r_ab2_y_5); - float r_ab2_norm_6 = sqrt(r_ab2_x_6 * r_ab2_x_6 + r_ab2_y_6 * r_ab2_y_6); - float r_ab2_norm_7 = sqrt(r_ab2_x_7 * r_ab2_x_7 + r_ab2_y_7 * r_ab2_y_7); - - r_ab2_norm_0 = r_ab2_norm_0 > EPS ? r_ab2_norm_0 : EPS; - r_ab2_norm_1 = r_ab2_norm_1 > EPS ? r_ab2_norm_1 : EPS; - r_ab2_norm_2 = r_ab2_norm_2 > EPS ? r_ab2_norm_2 : EPS; - r_ab2_norm_3 = r_ab2_norm_3 > EPS ? r_ab2_norm_3 : EPS; - r_ab2_norm_4 = r_ab2_norm_4 > EPS ? r_ab2_norm_4 : EPS; - r_ab2_norm_5 = r_ab2_norm_5 > EPS ? r_ab2_norm_5 : EPS; - r_ab2_norm_6 = r_ab2_norm_6 > EPS ? r_ab2_norm_6 : EPS; - r_ab2_norm_7 = r_ab2_norm_7 > EPS ? r_ab2_norm_7 : EPS; - - float r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - float r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - float r_ab_norm_sum_2 = r_ab_norm_2 + r_ab2_norm_2; - float r_ab_norm_sum_3 = r_ab_norm_3 + r_ab2_norm_3; - float r_ab_norm_sum_4 = r_ab_norm_4 + r_ab2_norm_4; - float r_ab_norm_sum_5 = r_ab_norm_5 + r_ab2_norm_5; - float r_ab_norm_sum_6 = r_ab_norm_6 + r_ab2_norm_6; - float r_ab_norm_sum_7 = r_ab_norm_7 + r_ab2_norm_7; - - float two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - sb_sq[j]; - float two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - sb_sq[j + 1]; - float two_b_sq_2 = r_ab_norm_sum_2 * r_ab_norm_sum_2 - sb_sq[j + 2]; - float two_b_sq_3 = r_ab_norm_sum_3 * r_ab_norm_sum_3 - sb_sq[j + 3]; - float two_b_sq_4 = r_ab_norm_sum_4 * r_ab_norm_sum_4 - sb_sq[j + 4]; - float two_b_sq_5 = r_ab_norm_sum_5 * r_ab_norm_sum_5 - sb_sq[j + 5]; - float two_b_sq_6 = r_ab_norm_sum_6 * r_ab_norm_sum_6 - sb_sq[j + 6]; - float two_b_sq_7 = r_ab_norm_sum_7 * r_ab_norm_sum_7 - sb_sq[j + 7]; - - two_b_sq_0 = two_b_sq_0 > EPS_SQ ? two_b_sq_0 : EPS_SQ; - two_b_sq_1 = two_b_sq_1 > EPS_SQ ? two_b_sq_1 : EPS_SQ; - two_b_sq_2 = two_b_sq_2 > EPS_SQ ? two_b_sq_2 : EPS_SQ; - two_b_sq_3 = two_b_sq_3 > EPS_SQ ? two_b_sq_3 : EPS_SQ; - two_b_sq_4 = two_b_sq_4 > EPS_SQ ? two_b_sq_4 : EPS_SQ; - two_b_sq_5 = two_b_sq_5 > EPS_SQ ? two_b_sq_5 : EPS_SQ; - two_b_sq_6 = two_b_sq_6 > EPS_SQ ? two_b_sq_6 : EPS_SQ; - two_b_sq_7 = two_b_sq_7 > EPS_SQ ? two_b_sq_7 : EPS_SQ; - - float two_b_0 = sqrt(two_b_sq_0); - float two_b_1 = sqrt(two_b_sq_1); - float two_b_2 = sqrt(two_b_sq_2); - float two_b_3 = sqrt(two_b_sq_3); - float two_b_4 = sqrt(two_b_sq_4); - float two_b_5 = sqrt(two_b_sq_5); - float two_b_6 = sqrt(two_b_sq_6); - float two_b_7 = sqrt(two_b_sq_7); - - float scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_2 = (r_ab_norm_sum_2 / (two_b_2 * r_ab_norm_2 * r_ab2_norm_2)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_2 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_3 = (r_ab_norm_sum_3 / (two_b_3 * r_ab_norm_3 * r_ab2_norm_3)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_3 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_4 = (r_ab_norm_sum_4 / (two_b_4 * r_ab_norm_4 * r_ab2_norm_4)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_4 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_5 = (r_ab_norm_sum_5 / (two_b_5 * r_ab_norm_5 * r_ab2_norm_5)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_5 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_6 = (r_ab_norm_sum_6 / (two_b_6 * r_ab_norm_6 * r_ab2_norm_6)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_6 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_7 = (r_ab_norm_sum_7 / (two_b_7 * r_ab_norm_7 * r_ab2_norm_7)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_7 * NEG_TWO_SIGMA_INV)); - - float scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - float scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - float scalar_mult_r_ab_2 = scalar_mult_common_2 * r_ab2_norm_2; - float scalar_mult_r_ab_3 = scalar_mult_common_3 * r_ab2_norm_3; - float scalar_mult_r_ab_4 = scalar_mult_common_4 * r_ab2_norm_4; - float scalar_mult_r_ab_5 = scalar_mult_common_5 * r_ab2_norm_5; - float scalar_mult_r_ab_6 = scalar_mult_common_6 * r_ab2_norm_6; - float scalar_mult_r_ab_7 = scalar_mult_common_7 * r_ab2_norm_7; - - float scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - float scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - float scalar_mult_r_ab2_2 = scalar_mult_common_2 * r_ab_norm_2; - float scalar_mult_r_ab2_3 = scalar_mult_common_3 * r_ab_norm_3; - float scalar_mult_r_ab2_4 = scalar_mult_common_4 * r_ab_norm_4; - float scalar_mult_r_ab2_5 = scalar_mult_common_5 * r_ab_norm_5; - float scalar_mult_r_ab2_6 = scalar_mult_common_6 * r_ab_norm_6; - float scalar_mult_r_ab2_7 = scalar_mult_common_7 * r_ab_norm_7; - - float f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_x_2 = r_ab_x_2 * scalar_mult_r_ab_2 + r_ab2_x_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_x_3 = r_ab_x_3 * scalar_mult_r_ab_3 + r_ab2_x_3 * scalar_mult_r_ab2_3; - float f_alpha_beta_neg_x_4 = r_ab_x_4 * scalar_mult_r_ab_4 + r_ab2_x_4 * scalar_mult_r_ab2_4; - float f_alpha_beta_neg_x_5 = r_ab_x_5 * scalar_mult_r_ab_5 + r_ab2_x_5 * scalar_mult_r_ab2_5; - float f_alpha_beta_neg_x_6 = r_ab_x_6 * scalar_mult_r_ab_6 + r_ab2_x_6 * scalar_mult_r_ab2_6; - float f_alpha_beta_neg_x_7 = r_ab_x_7 * scalar_mult_r_ab_7 + r_ab2_x_7 * scalar_mult_r_ab2_7; - - float f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_y_2 = r_ab_y_2 * scalar_mult_r_ab_2 + r_ab2_y_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_y_3 = r_ab_y_3 * scalar_mult_r_ab_3 + r_ab2_y_3 * scalar_mult_r_ab2_3; - float f_alpha_beta_neg_y_4 = r_ab_y_4 * scalar_mult_r_ab_4 + r_ab2_y_4 * scalar_mult_r_ab2_4; - float f_alpha_beta_neg_y_5 = r_ab_y_5 * scalar_mult_r_ab_5 + r_ab2_y_5 * scalar_mult_r_ab2_5; - float f_alpha_beta_neg_y_6 = r_ab_y_6 * scalar_mult_r_ab_6 + r_ab2_y_6 * scalar_mult_r_ab2_6; - float f_alpha_beta_neg_y_7 = r_ab_y_7 * scalar_mult_r_ab_7 + r_ab2_y_7 * scalar_mult_r_ab2_7; - - float w_val_0, w_val_1, w_val_2, w_val_3, w_val_4, w_val_5, w_val_6, w_val_7; - - float f_norm_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - float f_norm_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - float f_norm_sq_2 = f_alpha_beta_neg_x_2 * f_alpha_beta_neg_x_2 + f_alpha_beta_neg_y_2 * f_alpha_beta_neg_y_2; - float f_norm_sq_3 = f_alpha_beta_neg_x_3 * f_alpha_beta_neg_x_3 + f_alpha_beta_neg_y_3 * f_alpha_beta_neg_y_3; - float f_norm_sq_4 = f_alpha_beta_neg_x_4 * f_alpha_beta_neg_x_4 + f_alpha_beta_neg_y_4 * f_alpha_beta_neg_y_4; - float f_norm_sq_5 = f_alpha_beta_neg_x_5 * f_alpha_beta_neg_x_5 + f_alpha_beta_neg_y_5 * f_alpha_beta_neg_y_5; - float f_norm_sq_6 = f_alpha_beta_neg_x_6 * f_alpha_beta_neg_x_6 + f_alpha_beta_neg_y_6 * f_alpha_beta_neg_y_6; - float f_norm_sq_7 = f_alpha_beta_neg_x_7 * f_alpha_beta_neg_x_7 + f_alpha_beta_neg_y_7 * f_alpha_beta_neg_y_7; - - float rhs_0 = f_norm_sq_0 * cos_phi_sq; - float rhs_1 = f_norm_sq_1 * cos_phi_sq; - float rhs_2 = f_norm_sq_2 * cos_phi_sq; - float rhs_3 = f_norm_sq_3 * cos_phi_sq; - float rhs_4 = f_norm_sq_4 * cos_phi_sq; - float rhs_5 = f_norm_sq_5 * cos_phi_sq; - float rhs_6 = f_norm_sq_6 * cos_phi_sq; - float rhs_7 = f_norm_sq_7 * cos_phi_sq; - - float e_dot_f_0 = e_x_i * f_alpha_beta_neg_x_0 + e_y_i * f_alpha_beta_neg_y_0; - float e_dot_f_1 = e_x_i * f_alpha_beta_neg_x_1 + e_y_i * f_alpha_beta_neg_y_1; - float e_dot_f_2 = e_x_i * f_alpha_beta_neg_x_2 + e_y_i * f_alpha_beta_neg_y_2; - float e_dot_f_3 = e_x_i * f_alpha_beta_neg_x_3 + e_y_i * f_alpha_beta_neg_y_3; - float e_dot_f_4 = e_x_i * f_alpha_beta_neg_x_4 + e_y_i * f_alpha_beta_neg_y_4; - float e_dot_f_5 = e_x_i * f_alpha_beta_neg_x_5 + e_y_i * f_alpha_beta_neg_y_5; - float e_dot_f_6 = e_x_i * f_alpha_beta_neg_x_6 + e_y_i * f_alpha_beta_neg_y_6; - float e_dot_f_7 = e_x_i * f_alpha_beta_neg_x_7 + e_y_i * f_alpha_beta_neg_y_7; - - float e_dot_f_sq_0 = e_dot_f_0 * e_dot_f_0; - float e_dot_f_sq_1 = e_dot_f_1 * e_dot_f_1; - float e_dot_f_sq_2 = e_dot_f_2 * e_dot_f_2; - float e_dot_f_sq_3 = e_dot_f_3 * e_dot_f_3; - float e_dot_f_sq_4 = e_dot_f_4 * e_dot_f_4; - float e_dot_f_sq_5 = e_dot_f_5 * e_dot_f_5; - float e_dot_f_sq_6 = e_dot_f_6 * e_dot_f_6; - float e_dot_f_sq_7 = e_dot_f_7 * e_dot_f_7; - - if (e_dot_f_0 >= 0.0 || e_dot_f_sq_0 <= rhs_0) - w_val_0 = dt_neg; - else - w_val_0 = c_neg_times_dt; - if (e_dot_f_1 >= 0.0 || e_dot_f_sq_1 <= rhs_1) - w_val_1 = dt_neg; - else - w_val_1 = c_neg_times_dt; - if (e_dot_f_2 >= 0.0 || e_dot_f_sq_2 <= rhs_2) - w_val_2 = dt_neg; - else - w_val_2 = c_neg_times_dt; - if (e_dot_f_3 >= 0.0 || e_dot_f_sq_3 <= rhs_3) - w_val_3 = dt_neg; - else - w_val_3 = c_neg_times_dt; - if (e_dot_f_4 >= 0.0 || e_dot_f_sq_4 <= rhs_4) - w_val_4 = dt_neg; - else - w_val_4 = c_neg_times_dt; - if (e_dot_f_5 >= 0.0 || e_dot_f_sq_5 <= rhs_5) - w_val_5 = dt_neg; - else - w_val_5 = c_neg_times_dt; - if (e_dot_f_6 >= 0.0 || e_dot_f_sq_6 <= rhs_6) - w_val_6 = dt_neg; - else - w_val_6 = c_neg_times_dt; - if (e_dot_f_7 >= 0.0 || e_dot_f_sq_7 <= rhs_7) - w_val_7 = dt_neg; - else - w_val_7 = c_neg_times_dt; - - acc_x_0 += f_alpha_beta_neg_x_0 * w_val_0; - acc_x_1 += f_alpha_beta_neg_x_1 * w_val_1; - acc_x_2 += f_alpha_beta_neg_x_2 * w_val_2; - acc_x_3 += f_alpha_beta_neg_x_3 * w_val_3; - acc_x_4 += f_alpha_beta_neg_x_4 * w_val_4; - acc_x_5 += f_alpha_beta_neg_x_5 * w_val_5; - acc_x_6 += f_alpha_beta_neg_x_6 * w_val_6; - acc_x_7 += f_alpha_beta_neg_x_7 * w_val_7; - acc_y_0 += f_alpha_beta_neg_y_0 * w_val_0; - acc_y_1 += f_alpha_beta_neg_y_1 * w_val_1; - acc_y_2 += f_alpha_beta_neg_y_2 * w_val_2; - acc_y_3 += f_alpha_beta_neg_y_3 * w_val_3; - acc_y_4 += f_alpha_beta_neg_y_4 * w_val_4; - acc_y_5 += f_alpha_beta_neg_y_5 * w_val_5; - acc_y_6 += f_alpha_beta_neg_y_6 * w_val_6; - acc_y_7 += f_alpha_beta_neg_y_7 * w_val_7; - } - - // remainder of the j < i half, single lane. # one j = 66 FLOPS - for (; j < i; j++) { - // lut lookup + -1* sign flip: not counted - float r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - float r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - float r_ab_norm = r_ab_norm_lut[j * n_p + i]; - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP (= 36 FLOPS with EXP=30) - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x += f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y += f_alpha_beta_neg_y * w_val; - } - - // reduction of the j < i accumulators. # K adds per line (K=8) => 16 ADDs - f_alpha_dt_x += acc_x_0 + acc_x_1 + acc_x_2 + acc_x_3 + acc_x_4 + acc_x_5 + acc_x_6 + acc_x_7; - f_alpha_dt_y += acc_y_0 + acc_y_1 + acc_y_2 + acc_y_3 + acc_y_4 + acc_y_5 + acc_y_6 + acc_y_7; - - acc_x_0 = 0.0f; - acc_x_1 = 0.0f; - acc_x_2 = 0.0f; - acc_x_3 = 0.0f; - acc_x_4 = 0.0f; - acc_x_5 = 0.0f; - acc_x_6 = 0.0f; - acc_x_7 = 0.0f; - acc_y_0 = 0.0f; - acc_y_1 = 0.0f; - acc_y_2 = 0.0f; - acc_y_3 = 0.0f; - acc_y_4 = 0.0f; - acc_y_5 = 0.0f; - acc_y_6 = 0.0f; - acc_y_7 = 0.0f; - - // ILP over the j > i half. # each lane = 66 FLOPS, K = 8 lanes per step - for (j = i + 1; j + 8 < n_p; j += 8) { - float r_ab_x_0 = r_ab_x_lut[i * n_p + j]; - float r_ab_x_1 = r_ab_x_lut[i * n_p + j + 1]; - float r_ab_x_2 = r_ab_x_lut[i * n_p + j + 2]; - float r_ab_x_3 = r_ab_x_lut[i * n_p + j + 3]; - float r_ab_x_4 = r_ab_x_lut[i * n_p + j + 4]; - float r_ab_x_5 = r_ab_x_lut[i * n_p + j + 5]; - float r_ab_x_6 = r_ab_x_lut[i * n_p + j + 6]; - float r_ab_x_7 = r_ab_x_lut[i * n_p + j + 7]; - - float r_ab_y_0 = r_ab_y_lut[i * n_p + j]; - float r_ab_y_1 = r_ab_y_lut[i * n_p + j + 1]; - float r_ab_y_2 = r_ab_y_lut[i * n_p + j + 2]; - float r_ab_y_3 = r_ab_y_lut[i * n_p + j + 3]; - float r_ab_y_4 = r_ab_y_lut[i * n_p + j + 4]; - float r_ab_y_5 = r_ab_y_lut[i * n_p + j + 5]; - float r_ab_y_6 = r_ab_y_lut[i * n_p + j + 6]; - float r_ab_y_7 = r_ab_y_lut[i * n_p + j + 7]; - - float r_ab_norm_0 = r_ab_norm_lut[i * n_p + j]; - float r_ab_norm_1 = r_ab_norm_lut[i * n_p + j + 1]; - float r_ab_norm_2 = r_ab_norm_lut[i * n_p + j + 2]; - float r_ab_norm_3 = r_ab_norm_lut[i * n_p + j + 3]; - float r_ab_norm_4 = r_ab_norm_lut[i * n_p + j + 4]; - float r_ab_norm_5 = r_ab_norm_lut[i * n_p + j + 5]; - float r_ab_norm_6 = r_ab_norm_lut[i * n_p + j + 6]; - float r_ab_norm_7 = r_ab_norm_lut[i * n_p + j + 7]; - - r_ab_norm_0 = r_ab_norm_0 > EPS ? r_ab_norm_0 : EPS; - r_ab_norm_1 = r_ab_norm_1 > EPS ? r_ab_norm_1 : EPS; - r_ab_norm_2 = r_ab_norm_2 > EPS ? r_ab_norm_2 : EPS; - r_ab_norm_3 = r_ab_norm_3 > EPS ? r_ab_norm_3 : EPS; - r_ab_norm_4 = r_ab_norm_4 > EPS ? r_ab_norm_4 : EPS; - r_ab_norm_5 = r_ab_norm_5 > EPS ? r_ab_norm_5 : EPS; - r_ab_norm_6 = r_ab_norm_6 > EPS ? r_ab_norm_6 : EPS; - r_ab_norm_7 = r_ab_norm_7 > EPS ? r_ab_norm_7 : EPS; - - float r_ab2_x_0 = r_ab_x_0 - sb_e_x[j]; - float r_ab2_x_1 = r_ab_x_1 - sb_e_x[j + 1]; - float r_ab2_x_2 = r_ab_x_2 - sb_e_x[j + 2]; - float r_ab2_x_3 = r_ab_x_3 - sb_e_x[j + 3]; - float r_ab2_x_4 = r_ab_x_4 - sb_e_x[j + 4]; - float r_ab2_x_5 = r_ab_x_5 - sb_e_x[j + 5]; - float r_ab2_x_6 = r_ab_x_6 - sb_e_x[j + 6]; - float r_ab2_x_7 = r_ab_x_7 - sb_e_x[j + 7]; - - float r_ab2_y_0 = r_ab_y_0 - sb_e_y[j]; - float r_ab2_y_1 = r_ab_y_1 - sb_e_y[j + 1]; - float r_ab2_y_2 = r_ab_y_2 - sb_e_y[j + 2]; - float r_ab2_y_3 = r_ab_y_3 - sb_e_y[j + 3]; - float r_ab2_y_4 = r_ab_y_4 - sb_e_y[j + 4]; - float r_ab2_y_5 = r_ab_y_5 - sb_e_y[j + 5]; - float r_ab2_y_6 = r_ab_y_6 - sb_e_y[j + 6]; - float r_ab2_y_7 = r_ab_y_7 - sb_e_y[j + 7]; - - float r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - float r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - float r_ab2_norm_2 = sqrt(r_ab2_x_2 * r_ab2_x_2 + r_ab2_y_2 * r_ab2_y_2); - float r_ab2_norm_3 = sqrt(r_ab2_x_3 * r_ab2_x_3 + r_ab2_y_3 * r_ab2_y_3); - float r_ab2_norm_4 = sqrt(r_ab2_x_4 * r_ab2_x_4 + r_ab2_y_4 * r_ab2_y_4); - float r_ab2_norm_5 = sqrt(r_ab2_x_5 * r_ab2_x_5 + r_ab2_y_5 * r_ab2_y_5); - float r_ab2_norm_6 = sqrt(r_ab2_x_6 * r_ab2_x_6 + r_ab2_y_6 * r_ab2_y_6); - float r_ab2_norm_7 = sqrt(r_ab2_x_7 * r_ab2_x_7 + r_ab2_y_7 * r_ab2_y_7); - - r_ab2_norm_0 = r_ab2_norm_0 > EPS ? r_ab2_norm_0 : EPS; - r_ab2_norm_1 = r_ab2_norm_1 > EPS ? r_ab2_norm_1 : EPS; - r_ab2_norm_2 = r_ab2_norm_2 > EPS ? r_ab2_norm_2 : EPS; - r_ab2_norm_3 = r_ab2_norm_3 > EPS ? r_ab2_norm_3 : EPS; - r_ab2_norm_4 = r_ab2_norm_4 > EPS ? r_ab2_norm_4 : EPS; - r_ab2_norm_5 = r_ab2_norm_5 > EPS ? r_ab2_norm_5 : EPS; - r_ab2_norm_6 = r_ab2_norm_6 > EPS ? r_ab2_norm_6 : EPS; - r_ab2_norm_7 = r_ab2_norm_7 > EPS ? r_ab2_norm_7 : EPS; - - float r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - float r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - float r_ab_norm_sum_2 = r_ab_norm_2 + r_ab2_norm_2; - float r_ab_norm_sum_3 = r_ab_norm_3 + r_ab2_norm_3; - float r_ab_norm_sum_4 = r_ab_norm_4 + r_ab2_norm_4; - float r_ab_norm_sum_5 = r_ab_norm_5 + r_ab2_norm_5; - float r_ab_norm_sum_6 = r_ab_norm_6 + r_ab2_norm_6; - float r_ab_norm_sum_7 = r_ab_norm_7 + r_ab2_norm_7; - - float two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - sb_sq[j]; - float two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - sb_sq[j + 1]; - float two_b_sq_2 = r_ab_norm_sum_2 * r_ab_norm_sum_2 - sb_sq[j + 2]; - float two_b_sq_3 = r_ab_norm_sum_3 * r_ab_norm_sum_3 - sb_sq[j + 3]; - float two_b_sq_4 = r_ab_norm_sum_4 * r_ab_norm_sum_4 - sb_sq[j + 4]; - float two_b_sq_5 = r_ab_norm_sum_5 * r_ab_norm_sum_5 - sb_sq[j + 5]; - float two_b_sq_6 = r_ab_norm_sum_6 * r_ab_norm_sum_6 - sb_sq[j + 6]; - float two_b_sq_7 = r_ab_norm_sum_7 * r_ab_norm_sum_7 - sb_sq[j + 7]; - - two_b_sq_0 = two_b_sq_0 > EPS_SQ ? two_b_sq_0 : EPS_SQ; - two_b_sq_1 = two_b_sq_1 > EPS_SQ ? two_b_sq_1 : EPS_SQ; - two_b_sq_2 = two_b_sq_2 > EPS_SQ ? two_b_sq_2 : EPS_SQ; - two_b_sq_3 = two_b_sq_3 > EPS_SQ ? two_b_sq_3 : EPS_SQ; - two_b_sq_4 = two_b_sq_4 > EPS_SQ ? two_b_sq_4 : EPS_SQ; - two_b_sq_5 = two_b_sq_5 > EPS_SQ ? two_b_sq_5 : EPS_SQ; - two_b_sq_6 = two_b_sq_6 > EPS_SQ ? two_b_sq_6 : EPS_SQ; - two_b_sq_7 = two_b_sq_7 > EPS_SQ ? two_b_sq_7 : EPS_SQ; - - float two_b_0 = sqrt(two_b_sq_0); - float two_b_1 = sqrt(two_b_sq_1); - float two_b_2 = sqrt(two_b_sq_2); - float two_b_3 = sqrt(two_b_sq_3); - float two_b_4 = sqrt(two_b_sq_4); - float two_b_5 = sqrt(two_b_sq_5); - float two_b_6 = sqrt(two_b_sq_6); - float two_b_7 = sqrt(two_b_sq_7); - - float scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_2 = (r_ab_norm_sum_2 / (two_b_2 * r_ab_norm_2 * r_ab2_norm_2)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_2 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_3 = (r_ab_norm_sum_3 / (two_b_3 * r_ab_norm_3 * r_ab2_norm_3)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_3 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_4 = (r_ab_norm_sum_4 / (two_b_4 * r_ab_norm_4 * r_ab2_norm_4)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_4 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_5 = (r_ab_norm_sum_5 / (two_b_5 * r_ab_norm_5 * r_ab2_norm_5)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_5 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_6 = (r_ab_norm_sum_6 / (two_b_6 * r_ab_norm_6 * r_ab2_norm_6)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_6 * NEG_TWO_SIGMA_INV)); - float scalar_mult_common_7 = (r_ab_norm_sum_7 / (two_b_7 * r_ab_norm_7 * r_ab2_norm_7)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_7 * NEG_TWO_SIGMA_INV)); - - float scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - float scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - float scalar_mult_r_ab_2 = scalar_mult_common_2 * r_ab2_norm_2; - float scalar_mult_r_ab_3 = scalar_mult_common_3 * r_ab2_norm_3; - float scalar_mult_r_ab_4 = scalar_mult_common_4 * r_ab2_norm_4; - float scalar_mult_r_ab_5 = scalar_mult_common_5 * r_ab2_norm_5; - float scalar_mult_r_ab_6 = scalar_mult_common_6 * r_ab2_norm_6; - float scalar_mult_r_ab_7 = scalar_mult_common_7 * r_ab2_norm_7; - - float scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - float scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - float scalar_mult_r_ab2_2 = scalar_mult_common_2 * r_ab_norm_2; - float scalar_mult_r_ab2_3 = scalar_mult_common_3 * r_ab_norm_3; - float scalar_mult_r_ab2_4 = scalar_mult_common_4 * r_ab_norm_4; - float scalar_mult_r_ab2_5 = scalar_mult_common_5 * r_ab_norm_5; - float scalar_mult_r_ab2_6 = scalar_mult_common_6 * r_ab_norm_6; - float scalar_mult_r_ab2_7 = scalar_mult_common_7 * r_ab_norm_7; - - float f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_x_2 = r_ab_x_2 * scalar_mult_r_ab_2 + r_ab2_x_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_x_3 = r_ab_x_3 * scalar_mult_r_ab_3 + r_ab2_x_3 * scalar_mult_r_ab2_3; - float f_alpha_beta_neg_x_4 = r_ab_x_4 * scalar_mult_r_ab_4 + r_ab2_x_4 * scalar_mult_r_ab2_4; - float f_alpha_beta_neg_x_5 = r_ab_x_5 * scalar_mult_r_ab_5 + r_ab2_x_5 * scalar_mult_r_ab2_5; - float f_alpha_beta_neg_x_6 = r_ab_x_6 * scalar_mult_r_ab_6 + r_ab2_x_6 * scalar_mult_r_ab2_6; - float f_alpha_beta_neg_x_7 = r_ab_x_7 * scalar_mult_r_ab_7 + r_ab2_x_7 * scalar_mult_r_ab2_7; - - float f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - float f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - float f_alpha_beta_neg_y_2 = r_ab_y_2 * scalar_mult_r_ab_2 + r_ab2_y_2 * scalar_mult_r_ab2_2; - float f_alpha_beta_neg_y_3 = r_ab_y_3 * scalar_mult_r_ab_3 + r_ab2_y_3 * scalar_mult_r_ab2_3; - float f_alpha_beta_neg_y_4 = r_ab_y_4 * scalar_mult_r_ab_4 + r_ab2_y_4 * scalar_mult_r_ab2_4; - float f_alpha_beta_neg_y_5 = r_ab_y_5 * scalar_mult_r_ab_5 + r_ab2_y_5 * scalar_mult_r_ab2_5; - float f_alpha_beta_neg_y_6 = r_ab_y_6 * scalar_mult_r_ab_6 + r_ab2_y_6 * scalar_mult_r_ab2_6; - float f_alpha_beta_neg_y_7 = r_ab_y_7 * scalar_mult_r_ab_7 + r_ab2_y_7 * scalar_mult_r_ab2_7; - - float w_val_0, w_val_1, w_val_2, w_val_3, w_val_4, w_val_5, w_val_6, w_val_7; - - float f_norm_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - float f_norm_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - float f_norm_sq_2 = f_alpha_beta_neg_x_2 * f_alpha_beta_neg_x_2 + f_alpha_beta_neg_y_2 * f_alpha_beta_neg_y_2; - float f_norm_sq_3 = f_alpha_beta_neg_x_3 * f_alpha_beta_neg_x_3 + f_alpha_beta_neg_y_3 * f_alpha_beta_neg_y_3; - float f_norm_sq_4 = f_alpha_beta_neg_x_4 * f_alpha_beta_neg_x_4 + f_alpha_beta_neg_y_4 * f_alpha_beta_neg_y_4; - float f_norm_sq_5 = f_alpha_beta_neg_x_5 * f_alpha_beta_neg_x_5 + f_alpha_beta_neg_y_5 * f_alpha_beta_neg_y_5; - float f_norm_sq_6 = f_alpha_beta_neg_x_6 * f_alpha_beta_neg_x_6 + f_alpha_beta_neg_y_6 * f_alpha_beta_neg_y_6; - float f_norm_sq_7 = f_alpha_beta_neg_x_7 * f_alpha_beta_neg_x_7 + f_alpha_beta_neg_y_7 * f_alpha_beta_neg_y_7; - - float rhs_0 = f_norm_sq_0 * cos_phi_sq; - float rhs_1 = f_norm_sq_1 * cos_phi_sq; - float rhs_2 = f_norm_sq_2 * cos_phi_sq; - float rhs_3 = f_norm_sq_3 * cos_phi_sq; - float rhs_4 = f_norm_sq_4 * cos_phi_sq; - float rhs_5 = f_norm_sq_5 * cos_phi_sq; - float rhs_6 = f_norm_sq_6 * cos_phi_sq; - float rhs_7 = f_norm_sq_7 * cos_phi_sq; - - float e_dot_f_0 = e_x_i * f_alpha_beta_neg_x_0 + e_y_i * f_alpha_beta_neg_y_0; - float e_dot_f_1 = e_x_i * f_alpha_beta_neg_x_1 + e_y_i * f_alpha_beta_neg_y_1; - float e_dot_f_2 = e_x_i * f_alpha_beta_neg_x_2 + e_y_i * f_alpha_beta_neg_y_2; - float e_dot_f_3 = e_x_i * f_alpha_beta_neg_x_3 + e_y_i * f_alpha_beta_neg_y_3; - float e_dot_f_4 = e_x_i * f_alpha_beta_neg_x_4 + e_y_i * f_alpha_beta_neg_y_4; - float e_dot_f_5 = e_x_i * f_alpha_beta_neg_x_5 + e_y_i * f_alpha_beta_neg_y_5; - float e_dot_f_6 = e_x_i * f_alpha_beta_neg_x_6 + e_y_i * f_alpha_beta_neg_y_6; - float e_dot_f_7 = e_x_i * f_alpha_beta_neg_x_7 + e_y_i * f_alpha_beta_neg_y_7; - - float e_dot_f_sq_0 = e_dot_f_0 * e_dot_f_0; - float e_dot_f_sq_1 = e_dot_f_1 * e_dot_f_1; - float e_dot_f_sq_2 = e_dot_f_2 * e_dot_f_2; - float e_dot_f_sq_3 = e_dot_f_3 * e_dot_f_3; - float e_dot_f_sq_4 = e_dot_f_4 * e_dot_f_4; - float e_dot_f_sq_5 = e_dot_f_5 * e_dot_f_5; - float e_dot_f_sq_6 = e_dot_f_6 * e_dot_f_6; - float e_dot_f_sq_7 = e_dot_f_7 * e_dot_f_7; - - if (e_dot_f_0 >= 0.0 || e_dot_f_sq_0 <= rhs_0) - w_val_0 = dt_neg; - else - w_val_0 = c_neg_times_dt; - if (e_dot_f_1 >= 0.0 || e_dot_f_sq_1 <= rhs_1) - w_val_1 = dt_neg; - else - w_val_1 = c_neg_times_dt; - if (e_dot_f_2 >= 0.0 || e_dot_f_sq_2 <= rhs_2) - w_val_2 = dt_neg; - else - w_val_2 = c_neg_times_dt; - if (e_dot_f_3 >= 0.0 || e_dot_f_sq_3 <= rhs_3) - w_val_3 = dt_neg; - else - w_val_3 = c_neg_times_dt; - if (e_dot_f_4 >= 0.0 || e_dot_f_sq_4 <= rhs_4) - w_val_4 = dt_neg; - else - w_val_4 = c_neg_times_dt; - if (e_dot_f_5 >= 0.0 || e_dot_f_sq_5 <= rhs_5) - w_val_5 = dt_neg; - else - w_val_5 = c_neg_times_dt; - if (e_dot_f_6 >= 0.0 || e_dot_f_sq_6 <= rhs_6) - w_val_6 = dt_neg; - else - w_val_6 = c_neg_times_dt; - if (e_dot_f_7 >= 0.0 || e_dot_f_sq_7 <= rhs_7) - w_val_7 = dt_neg; - else - w_val_7 = c_neg_times_dt; - - acc_x_0 += f_alpha_beta_neg_x_0 * w_val_0; - acc_x_1 += f_alpha_beta_neg_x_1 * w_val_1; - acc_x_2 += f_alpha_beta_neg_x_2 * w_val_2; - acc_x_3 += f_alpha_beta_neg_x_3 * w_val_3; - acc_x_4 += f_alpha_beta_neg_x_4 * w_val_4; - acc_x_5 += f_alpha_beta_neg_x_5 * w_val_5; - acc_x_6 += f_alpha_beta_neg_x_6 * w_val_6; - acc_x_7 += f_alpha_beta_neg_x_7 * w_val_7; - acc_y_0 += f_alpha_beta_neg_y_0 * w_val_0; - acc_y_1 += f_alpha_beta_neg_y_1 * w_val_1; - acc_y_2 += f_alpha_beta_neg_y_2 * w_val_2; - acc_y_3 += f_alpha_beta_neg_y_3 * w_val_3; - acc_y_4 += f_alpha_beta_neg_y_4 * w_val_4; - acc_y_5 += f_alpha_beta_neg_y_5 * w_val_5; - acc_y_6 += f_alpha_beta_neg_y_6 * w_val_6; - acc_y_7 += f_alpha_beta_neg_y_7 * w_val_7; - } - - // remainder of the j > i half, single lane. # one j = 66 FLOPS - for (; j < n_p; j++) { - float r_ab_x = r_ab_x_lut[i * n_p + j]; - float r_ab_y = r_ab_y_lut[i * n_p + j]; - float r_ab_norm = r_ab_norm_lut[i * n_p + j]; - - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x += f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y += f_alpha_beta_neg_y * w_val; - } - - // reduction of the j > i accumulators. # K adds per line (K=8) => 16 ADDs - f_alpha_dt_x += acc_x_0 + acc_x_1 + acc_x_2 + acc_x_3 + acc_x_4 + acc_x_5 + acc_x_6 + acc_x_7; - f_alpha_dt_y += acc_y_0 + acc_y_1 + acc_y_2 + acc_y_3 + acc_y_4 + acc_y_5 + acc_y_6 + acc_y_7; - - // force between pedestrian and obstacles - // # one obstacle = 55 FLOPS (w_val sign-based, no a*a / b_sq) - for (int k = 0; k < board.n_obstacles; k++) { - // # 1 ADD - float v1_x = pos_x[i] - board.obstacles[k].start_x; - // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - // # 1 ADD, 3 MULTs - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - // # 2 ADDs, 1 MULT - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - // # 2 ADDs, 1 MULT - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - // # 2 MULTs, 1 DIV, 1 EXP - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - // # 1 MULT - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; - - float w_val; - - // sign select only, no flops - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 MULT - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // # 1 ADD - f_alpha_dt_x += f_alpha_B_dt_x; - // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // # 1 ADD - w_x[i] += f_alpha_dt_x; - // # 1 ADD - w_y[i] += f_alpha_dt_y; - } - - // update each pedestrian - // # 29 FLOPS per pedestrian - for (int i = 0; i < n_p; i++) { - - // # 1 ADD, 2 MULTs, 1 SQRT - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); - - float w_norm2 = w_norm > EPS ? w_norm : EPS; - - // # 1 DIV - float correction_factor = min(1.0f, v_max[i] / w_norm2); - - // # 1 MULT - curr_vel_x[i] = w_x[i] * correction_factor; - // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; - - // # 1 ADD, 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - // # 1 ADD, 1 MULT - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 1 ADD - float ex = destination_x[i] - pos_x[i]; - // # 1 ADD - float ey = destination_y[i] - pos_y[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float e_norm = sqrt(ex * ex + ey * ey); - float e_norm2 = e_norm > EPS ? e_norm : EPS; - // # 1 DIV - float e_norm_inv = 1.0 / e_norm2; - - // # 1 MULT - e_x[i] = ex * e_norm_inv; - // # 1 MULT - e_y[i] = ey * e_norm_inv; - - // # 1 ADD, 2 MULTs - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; - - // # 1 SQRT - float velocity_norm = sqrt(velocity_norm_sq); - - // # 1 MULT - sb_sq[i] = velocity_norm_sq * delta_t_sq; - - // # 2 MULTs (velocity_norm*delta_t written on both lines, counted as is) - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - // # 2 MULTs - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_float_inlining_SoA_symmetry_flopcount.cpp b/variants/variant_float_inlining_SoA_symmetry_flopcount.cpp deleted file mode 100644 index c50f977..0000000 --- a/variants/variant_float_inlining_SoA_symmetry_flopcount.cpp +++ /dev/null @@ -1,489 +0,0 @@ -// # Changes: SoA layout for pedestrians (file-scope arrays instead of struct fields) -// # added flopcount -// FLOPCOUNT = n_iter * ( 69 * n_p ** 2 - 32 * n_p + 55 * n_obstacles * n_p ) -// # Predecessor: variant_float_inlining.cpp - -#include "../headers/board_reduced_float.hpp" -#include "../headers/constants_float.hpp" -#include "../headers/pedestrian_reduced_float.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0f -#define BOARD_LENGTH 50.0f -#define WALL_EPS 1e-3f -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999f - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// we wanna use arrays so we can implement SoA, one array per pedestrian field -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - // resize all arrays for this run - pos_x.resize(n_p); - pos_y.resize(n_p); - sb_sq.resize(n_p); - sb_e_x.resize(n_p); - sb_e_y.resize(n_p); - e_x.resize(n_p); - e_y.resize(n_p); - curr_vel_x.resize(n_p); - curr_vel_y.resize(n_p); - w_x.resize(n_p); - w_y.resize(n_p); - desired_v0.resize(n_p); - v_max.resize(n_p); - destination_x.resize(n_p); - destination_y.resize(n_p); - color.resize(n_p); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - float x = x_gen_left(gen); - float y = y_gen(gen); - - float dest_x = BOARD_LENGTH + 30.0; // renamed to avoid shadowing destination_x[] - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - // Fields the Pedestrian constructor would have zero-initialized (c_v_x=c_v_y=0): - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - for (int i = n_p_half; i < n_p; i++) { - float x = x_gen_right(gen); - float y = y_gen(gen); - - float dest_x = -30; - float dest_y = y; - - float init_e_x = dest_x - x; - float init_e_y = dest_y - y; - float init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - float init_e_unit_x = init_e_x / init_e_norm; - float init_e_unit_y = init_e_y / init_e_norm; - - float d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - float v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - w_x[i] = 0.0f; - w_y[i] = 0.0f; - curr_vel_x[i] = 0.0f; - curr_vel_y[i] = 0.0f; - sb_sq[i] = 0.0f; - sb_e_x[i] = 0.0f; - sb_e_y[i] = 0.0f; - } - - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - (void)pedestrians; -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - // here we have real (simple) symmetry - std::vector r_ab_x_lut(n_p * n_p, 0.0); - std::vector r_ab_y_lut(n_p * n_p, 0.0); - std::vector r_ab_norm_lut(n_p * n_p, 0.0); - // use NAME[i * n_p + j] - - // FLOPCOUNT (everything inside the t-loop). EXP kept as its own op (one real exp() per call). - // section costs: overhead 4 ADD 4 MULT | distance pair 3 ADD 2 MULT 1 SQRT | - // ped-ped j 11 ADD 22 MULT 2 SQRT 1 DIV 1 EXP | - // obstacle 10 ADD 13 MULT 1 SQRT 1 DIV 1 EXP | update 7 ADD 17 MULT 3 SQRT 2 DIV - - // # ADDs: n_iter * ( - // # n_p * 4 + - // # 0.5 * (n_p*n_p - n_p) * 3 + - // # n_p * (n_p - 1) * 11 + - // # n_p * board.n_obstacles * 10 + - // # n_p * 7 - // # ) - - // # MULTs: n_iter * ( - // # n_p * 4 + - // # 0.5 * (n_p*n_p - n_p) * 2 + - // # n_p * (n_p - 1) * 22 + - // # n_p * board.n_obstacles * 13 + - // # n_p * 17 - // # ) - - // # DIVs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles + - // # n_p * 2 - // # ) - - // # SQRTs: n_iter * ( - // # 0.5 * (n_p*n_p - n_p) + - // # n_p * (n_p - 1) * 2 + - // # n_p * board.n_obstacles + - // # n_p * 3 - // # ) - - // # EXPs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles - // # ) - - // total with EXP = 30: n_iter * ( 69 * n_p ** 2 - 32 * n_p + 55 * n_obstacles * n_p ) - for (int t = 0; t < n_iter; t++) { - for (int i = 0; i < n_p; i++) { - - // # 1 MULT - float desired_velocity_x = e_x[i] * desired_v0[i]; - // # 1 MULT - float desired_velocity_y = e_y[i] * desired_v0[i]; - // # 1 ADD, 1 MULT - float f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - // # 1 ADD, 1 MULT - float f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; - - // lut for distances - // get all distances first - // # each pair: 6 FLOPS, total over all i = 0.5*(n_p*n_p - n_p) * 6 - for (int j = i + 1; j < n_p; j++) { - // # 1 ADD - float dx = pos_x[i] - pos_x[j]; - // # 1 ADD - float dy = pos_y[i] - pos_y[j]; - // # 1 ADD, 2 MULTs, 1 SQRT - float norm = sqrt(dx * dx + dy * dy); - int idx = i * n_p + j; - r_ab_x_lut[idx] = dx; - r_ab_y_lut[idx] = dy; - r_ab_norm_lut[idx] = norm; - } - - - // force between pedestrians - // # one j (the i==j case is skipped) = 66 FLOPS, runs (n_p-1) times per i - for (int j = 0; j < n_p; j++) { - if (i == j) continue; - - // symmetry: lut lookup is for free, the -1* sign flip is not counted - float r_ab_x, r_ab_y, r_ab_norm; - if (i < j) { - r_ab_x = r_ab_x_lut[i * n_p + j]; - r_ab_y = r_ab_y_lut[i * n_p + j]; - r_ab_norm = r_ab_norm_lut[i * n_p + j]; - } else { - // other direction - r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - r_ab_norm = r_ab_norm_lut[j * n_p + i]; - } - - - - - //float r_ab_x = pos_x[i] - pos_x[j]; - //float r_ab_y = pos_y[i] - pos_y[j]; - - //float r_ab_norm = sqrt(r_ab_x * r_ab_x + r_ab_y * r_ab_y); - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; - - // # 1 ADD - float r_ab2_x = r_ab_x - sb_e_x[j]; - // # 1 ADD - float r_ab2_y = r_ab_y - sb_e_y[j]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; - - // # 1 ADD - float r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULT - float two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; - // # 1 SQRT - float two_b = sqrt(two_b_sq); - // # 5 MULTs, 1 DIV, 1 EXP - float scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // # 1 MULT - float scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // # 1 MULT - float scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // # 1 ADD, 2 MULTs - float f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - float w_val; - - // # 1 ADD, 2 MULTs - float f_norm_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - // # 1 MULT - float rhs = f_norm_sq * cos_phi_sq; - // # 1 ADD, 2 MULTs - float e_dot_f = e_x[i] * f_alpha_beta_neg_x + e_y[i] * f_alpha_beta_neg_y; - // # 1 MULT - float e_dot_f_sq = e_dot_f * e_dot_f; - if (e_dot_f >= 0.0 || e_dot_f_sq <= rhs) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 ADD, 1 MULT - f_alpha_dt_x = f_alpha_dt_x + f_alpha_beta_neg_x * w_val; - // # 1 ADD, 1 MULT - f_alpha_dt_y = f_alpha_dt_y + f_alpha_beta_neg_y * w_val; - } - - // force between pedestrian and obstacles - // # one obstacle = 55 FLOPS (w_val here is sign-based, no a*a / b_sq) - for (int k = 0; k < board.n_obstacles; k++) { - - // # 1 ADD - float v1_x = pos_x[i] - board.obstacles[k].start_x; - // # 1 ADD - float v1_y = pos_y[i] - board.obstacles[k].start_y; - float v2_x = board.obstacles[k].obstacle_v_x; - float v2_y = board.obstacles[k].obstacle_v_y; - - // # 1 ADD, 3 MULTs - float projection = (v1_x * v2_x + v1_y * v2_y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - // # 2 ADDs, 1 MULT - float r_aB_x = pos_x[i] - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); - // # 2 ADDs, 1 MULT - float r_aB_y = pos_y[i] - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); - - // # 1 ADD, 2 MULTs, 1 SQRT - float r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; - - // # 2 MULTs, 1 DIV, 1 EXP - float scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - // # 1 MULT - float f_alpha_B_neg_x = r_aB_x * scalar_mult; - // # 1 MULT - float f_alpha_B_neg_y = r_aB_y * scalar_mult; - - float w_val; - - // sign select only, no flops - float e_dot_f = (f_alpha_B_neg_y == 0) ? 0 : (f_alpha_B_neg_y < 0 ? -e_y[i] : e_y[i]); - - if (e_dot_f >= cos_phi) - w_val = dt_neg; - else - w_val = c_neg_times_dt; - - // # 1 MULT - float f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - // # 1 MULT - float f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // # 1 ADD - f_alpha_dt_x += f_alpha_B_dt_x; - // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // # 1 ADD - w_x[i] += f_alpha_dt_x; - // # 1 ADD - w_y[i] += f_alpha_dt_y; - } - - // update each pedestrian - // # 29 FLOPS per pedestrian - for (int i = 0; i < n_p; i++) { - - // # 1 ADD, 2 MULTs, 1 SQRT - float w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); - - float w_norm2 = w_norm > EPS ? w_norm : EPS; - - // # 1 DIV - float correction_factor = min(1.0f, v_max[i] / w_norm2); - - // # 1 MULT - curr_vel_x[i] = w_x[i] * correction_factor; - // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; - - // # 1 ADD, 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - // # 1 ADD, 1 MULT - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 1 ADD - float ex = destination_x[i] - pos_x[i]; - // # 1 ADD - float ey = destination_y[i] - pos_y[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - float e_norm = sqrt(ex * ex + ey * ey); - float e_norm2 = e_norm > EPS ? e_norm : EPS; - // # 1 DIV - float e_norm_inv = 1.0 / e_norm2; - - // # 1 MULT - e_x[i] = ex * e_norm_inv; - // # 1 MULT - e_y[i] = ey * e_norm_inv; - - // # 1 ADD, 2 MULTs - float velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; - - // # 1 SQRT - float velocity_norm = sqrt(velocity_norm_sq); - - // # 1 MULT - sb_sq[i] = velocity_norm_sq * delta_t_sq; - - // # 2 MULTs (velocity_norm*delta_t written on both lines, counted as is) - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - // # 2 MULTs - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_inlining_7_SoA_AVX2_flopcount.cpp b/variants/variant_inlining_7_SoA_AVX2_flopcount.cpp deleted file mode 100644 index 1445034..0000000 --- a/variants/variant_inlining_7_SoA_AVX2_flopcount.cpp +++ /dev/null @@ -1,458 +0,0 @@ -// # This file uses avx2 -// # This is the first variant and should serve as a base for further avx2 optimizations - -// Okay our first step is to use a SoA instead of an AoS -// We only do SoA for the pedestrian struct, there we wanna optimize such that we access for the ped-ped forces adjacent pedestrians (in the arrays) -// Now in a second step we wanna really use AVX2 for the mainloop (where we wana optimize the most innerloop first) - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian_reduced.hpp" - -#include // to use avx2 - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// we wanna use arrays so we can implement SOA, we create arrays for all the data we need and access -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { - -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - const int n_p_pad = (n_p + 3) & ~3; // round up to a multiple of 4 (so its easy to use avx) - - // resize all our arrays for this run - pos_x.resize(n_p_pad); - pos_y.resize(n_p_pad); - sb_sq.resize(n_p_pad); - sb_e_x.resize(n_p_pad); - sb_e_y.resize(n_p_pad); - e_x.resize(n_p_pad); - e_y.resize(n_p_pad); - curr_vel_x.resize(n_p_pad); - curr_vel_y.resize(n_p_pad); - w_x.resize(n_p_pad); - w_y.resize(n_p_pad); - desired_v0.resize(n_p_pad); - v_max.resize(n_p_pad); - destination_x.resize(n_p_pad); - destination_y.resize(n_p_pad); - color.resize(n_p_pad); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - double dest_x = BOARD_LENGTH + 30.0; // renamed to avoid shadowing destination_x[] - double dest_y = y; // same idea for destination_y[] - - double init_e_x = dest_x - x; - double init_e_y = dest_y - y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - double v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - // Fields the Pedestrian constructor would have zero-initialized: - w_x[i] = 0.0; - w_y[i] = 0.0; - curr_vel_x[i] = 0.0; - curr_vel_y[i] = 0.0; - sb_sq[i] = 0.0; - sb_e_x[i] = 0.0; - sb_e_y[i] = 0.0; - } - - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - double dest_x = -30; - double dest_y = y; - - double init_e_x = dest_x - x; - double init_e_y = dest_y - y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - double v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - // Fields the Pedestrian constructor would have zero-initialized: - w_x[i] = 0.0; - w_y[i] = 0.0; - curr_vel_x[i] = 0.0; - curr_vel_y[i] = 0.0; - sb_sq[i] = 0.0; - sb_e_x[i] = 0.0; - sb_e_y[i] = 0.0; - } - - // Padding lanes: positions far away, everything else zero. Any unmasked SIMD - // contribution from these lanes will vanish through exp(very negative) roughly 0. - for (int j = n_p; j < n_p_pad; j++) { - pos_x[j] = 1e9; - pos_y[j] = 1e9; - sb_sq[j] = 0.0; - sb_e_x[j] = 0.0; - sb_e_y[j] = 0.0; - } - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - // Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - // all the constants for the AVX2 setup - - const int n_p_pad = (n_p + 3) & ~3; - - alignas(32) const double lane_offset_arr[4] = {0.0, 1.0, 2.0, 3.0}; - const __m256d lane_off = _mm256_load_pd(lane_offset_arr); - - const __m256d v_neg_v0_div_2s = _mm256_set1_pd(NEG_V0_AB_div_TWO_SIGMA); - const __m256d v_neg_2s_inv = _mm256_set1_pd(NEG_TWO_SIGMA_INV); - const __m256d v_cos_phi_inv = _mm256_set1_pd(cos_phi_inv); - const __m256d v_dt_neg = _mm256_set1_pd(dt_neg); - const __m256d v_c_neg_dt = _mm256_set1_pd(c_neg_times_dt); - const __m256d v_zero = _mm256_setzero_pd(); - const __m256d v_np = _mm256_set1_pd((double)n_p); - - // # flops per iteration (one pass of the outer time loop): - // # ADDs: n_p * 12 + n_p * n_p_pad * 15 + n_p * board.n_obstacles * 12 + n_p * 7 - // # MULTs: n_p * 4 + n_p * n_p_pad * 24 + n_p * board.n_obstacles * 19 + n_p * 16 - // # DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 - // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 - // # EXPs: n_p * n_p_pad * 1 + n_p * board.n_obstacles - for (int t = 0; t < n_iter; t++) { - - for (int i = 0; i < n_p; i++) { - - double desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT - double desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - - double f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - double f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - - // since i is fixed for the innermost loops we now wanna set all those variables - const __m256d pix = _mm256_set1_pd(pos_x[i]); - const __m256d piy = _mm256_set1_pd(pos_y[i]); - const __m256d eix = _mm256_set1_pd(e_x[i]); - const __m256d eiy = _mm256_set1_pd(e_y[i]); - const __m256d v_i = _mm256_set1_pd((double)i); - - __m256d acc_x = _mm256_setzero_pd(); - __m256d acc_y = _mm256_setzero_pd(); - - for (int j = 0; j < n_p_pad; j += 4) { - // 4 lanes at once, lane k maps to pedestrian j+k - __m256d j_idx = _mm256_add_pd(_mm256_set1_pd((double)j), lane_off); // # 4 ADDs - - // active = (j+k != i) AND (j+k < n_p), zero in lanes we wanna skip - __m256d m_neq_i = _mm256_cmp_pd(j_idx, v_i, _CMP_NEQ_OQ); - __m256d m_in_range = _mm256_cmp_pd(j_idx, v_np, _CMP_LT_OQ); - __m256d active = _mm256_and_pd(m_neq_i, m_in_range); - - // j-side loads, SoA contiguous. loadu because std::vector only guarantees 16B align. - __m256d pjx = _mm256_loadu_pd(&pos_x[j]); - __m256d pjy = _mm256_loadu_pd(&pos_y[j]); - __m256d sbe_jx = _mm256_loadu_pd(&sb_e_x[j]); - __m256d sbe_jy = _mm256_loadu_pd(&sb_e_y[j]); - __m256d sb_jsq = _mm256_loadu_pd(&sb_sq[j]); - - // double r_ab_x = pos_x[i] - pos_x[j]; - // double r_ab_y = pos_y[i] - pos_y[j]; - // - // double r_ab_norm = sqrt(r_ab_x * r_ab_x + r_ab_y * r_ab_y); - - // r_ab = pos_i - pos_j ; |r_ab| - __m256d r_ab_x = _mm256_sub_pd(pix, pjx); // # 4 ADDs - __m256d r_ab_y = _mm256_sub_pd(piy, pjy); // # 4 ADDs - __m256d r_ab_n2 = _mm256_fmadd_pd(r_ab_x, r_ab_x, _mm256_mul_pd(r_ab_y, r_ab_y)); // # 8 MULTs, 4 ADDs - __m256d r_ab_n = _mm256_sqrt_pd(r_ab_n2); // # 4 SQRTs - - // double r_ab2_x = r_ab_x - sb_e_x[j]; - // double r_ab2_y = r_ab_y - sb_e_y[j]; - // - // double r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - - // r_ab2 = r_ab - sb_e_j ; |r_ab2| - __m256d r_ab2_x = _mm256_sub_pd(r_ab_x, sbe_jx); // # 4 ADDs - __m256d r_ab2_y = _mm256_sub_pd(r_ab_y, sbe_jy); // # 4 ADDs - __m256d r_ab2_n2 = _mm256_fmadd_pd(r_ab2_x, r_ab2_x, _mm256_mul_pd(r_ab2_y, r_ab2_y)); // # 8 MULTs, 4 ADDs - __m256d r_ab2_n = _mm256_sqrt_pd(r_ab2_n2); // # 4 SQRTs - - // double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - // double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - // double two_b = sqrt(two_b_sq); - - // 2b = sqrt((|r_ab| + |r_ab2|)^2 - sb_sq_j) - __m256d r_sum = _mm256_add_pd(r_ab_n, r_ab2_n); // # 4 ADDs - __m256d two_b_sq = _mm256_fmsub_pd(r_sum, r_sum, sb_jsq); // # 4 MULTs, 4 ADDs - __m256d two_b = _mm256_sqrt_pd(two_b_sq); // # 4 SQRTs - - // exp scalar fallback for now, we vectorize in a later step. probably the biggest perf win left. - __m256d e_arg = _mm256_mul_pd(two_b, v_neg_2s_inv); // # 4 MULTs - alignas(32) double tmp_arg[4], tmp_val[4]; - _mm256_store_pd(tmp_arg, e_arg); - tmp_val[0] = exp(tmp_arg[0]); - tmp_val[1] = exp(tmp_arg[1]); - tmp_val[2] = exp(tmp_arg[2]); - tmp_val[3] = exp(tmp_arg[3]); // # 4 EXPs (1 per lane, scalar) - __m256d e_val = _mm256_load_pd(tmp_val); - - // double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - - // smc = (r_sum * NEG_V0_AB_div_TWO_SIGMA * e_val) / (two_b * r_ab_n * r_ab2_n) - // the i==j lane collapses denom to 0, so we swap in 1.0 there to keep smc finite. - // the AND with active zeroes the contribution from that lane anyway. - __m256d numer = _mm256_mul_pd(_mm256_mul_pd(r_sum, v_neg_v0_div_2s), e_val); // # 8 MULTs - __m256d denom = _mm256_mul_pd(_mm256_mul_pd(two_b, r_ab_n), r_ab2_n); // # 8 MULTs - __m256d denom_safe = _mm256_blendv_pd(_mm256_set1_pd(1.0), denom, active); // bitwise, no flop - __m256d smc = _mm256_div_pd(numer, denom_safe); // # 4 DIVs - - // double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - __m256d sm_r_ab = _mm256_mul_pd(smc, r_ab2_n); // # 4 MULTs - __m256d sm_r_ab2 = _mm256_mul_pd(smc, r_ab_n); // # 4 MULTs - - // double f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // double f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - // scalar wants r_ab * sm_r_ab + r_ab2 * sm_r_ab2, the previous fma had the layout wrong - __m256d f_x = _mm256_fmadd_pd(r_ab2_x, sm_r_ab2, _mm256_mul_pd(r_ab_x, sm_r_ab)); // # 8 MULTs, 4 ADDs - __m256d f_y = _mm256_fmadd_pd(r_ab2_y, sm_r_ab2, _mm256_mul_pd(r_ab_y, sm_r_ab)); // # 8 MULTs, 4 ADDs - - // w_val = dt_neg by default, c_neg_times_dt when (a >= 0 && a*a > b_sq) - __m256d a = _mm256_mul_pd(_mm256_fmadd_pd(eix, f_x, _mm256_mul_pd(eiy, f_y)), v_cos_phi_inv); // # 12 MULTs, 4 ADDs - __m256d b_sq = _mm256_fmadd_pd(f_x, f_x, _mm256_mul_pd(f_y, f_y)); // # 8 MULTs, 4 ADDs - __m256d a_sq = _mm256_mul_pd(a, a); // # 4 MULTs - - __m256d c1 = _mm256_cmp_pd(a, v_zero, _CMP_GE_OQ); - __m256d c2 = _mm256_cmp_pd(a_sq, b_sq, _CMP_GT_OQ); - __m256d cond = _mm256_and_pd(c1, c2); - __m256d w_val = _mm256_blendv_pd(v_dt_neg, v_c_neg_dt, cond); - - // zero out inactive lanes (i==j and padding) before accumulating. - // AND with all-zero mask = 0, AND with all-one mask passes through. cheaper than blendv here. - __m256d f_x_safe = _mm256_and_pd(f_x, active); // bitwise, no flop - __m256d f_y_safe = _mm256_and_pd(f_y, active); // bitwise, no flop - acc_x = _mm256_fmadd_pd(f_x_safe, w_val, acc_x); // # 4 MULTs, 4 ADDs - acc_y = _mm256_fmadd_pd(f_y_safe, w_val, acc_y); // # 4 MULTs, 4 ADDs - } - - // horizontal sum acc_x, acc_y into the scalar f_alpha_dt - __m256d hsum_xy = _mm256_hadd_pd(acc_x, acc_y); // [x0+x1, y0+y1, x2+x3, y2+y3] // # 4 ADDs - __m128d lo = _mm256_castpd256_pd128(hsum_xy); - __m128d hi = _mm256_extractf128_pd(hsum_xy, 1); - __m128d sum_xy = _mm_add_pd(lo, hi); // [x_total, y_total] // # 2 ADDs - alignas(16) double sum_out[2]; - _mm_store_pd(sum_out, sum_xy); - f_alpha_dt_x += sum_out[0]; // # 1 ADD - f_alpha_dt_y += sum_out[1]; // # 1 ADD - - for (int k = 0; k < board.n_obstacles; k++) { - - double v1_x = pos_x[i] - board.obstacles[k].start.x; // # 1 ADD - double v1_y = pos_y[i] - board.obstacles[k].start.y; // # 1 ADD - - double projection = (v1_x * board.obstacles[k].obstacle_v.x + v1_y * board.obstacles[k].obstacle_v.y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - double r_aB_x = pos_x[i] - (board.obstacles[k].start.x + board.obstacles[k].obstacle_v.x * projection); // # 1 MULT, 2 ADDs - double r_aB_y = pos_y[i] - (board.obstacles[k].start.y + board.obstacles[k].obstacle_v.y * projection); // # 1 MULT, 2 ADDs - - double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV - double f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT - double f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT - - double w_val; - double a = (e_x[i] * f_alpha_B_neg_x + e_y[i] * f_alpha_B_neg_y) * cos_phi_inv; // # 3 MULTs, 1 ADD - double b_sq = f_alpha_B_neg_x * f_alpha_B_neg_x + f_alpha_B_neg_y * f_alpha_B_neg_y; // # 2 MULTs, 1 ADD - - w_val = dt_neg; - if (a >= 0 && (a * a > b_sq)) { // # 1 MULT for a*a - w_val = c_neg_times_dt; - } - - double f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT - double f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - - f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD - } - - w_x[i] += f_alpha_dt_x; // 1 ADD - w_y[i] += f_alpha_dt_y; // 1 ADD - } - - for (int i = 0; i < n_p; i++) { - - double w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT - - double correction_factor = min(1.0, v_max[i] / w_norm); // # 1 DIV - - curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT,1 ADD - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - double ex = destination_x[i] - pos_x[i]; // # 1 ADD - double ey = destination_y[i] - pos_y[i]; // # 1 ADD - - double e_norm_inv = 1.0 / sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT, 1 DIV - - e_x[i] = ex * e_norm_inv; // # 1 MULT - e_y[i] = ey * e_norm_inv; // # 1 MULT - - double velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD - - double velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - - sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT - - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_inlining_7_SoA_AVX2_polyexp.cpp b/variants/variant_inlining_7_SoA_AVX2_polyexp.cpp index 717cd71..003e984 100644 --- a/variants/variant_inlining_7_SoA_AVX2_polyexp.cpp +++ b/variants/variant_inlining_7_SoA_AVX2_polyexp.cpp @@ -180,35 +180,41 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } -// vectorized exp for a __m256d, no libm call. -// idea: exp(x) = 2^n * exp(r), where n = round(x / ln2) and r = x - n*ln2. -// after the range reduce, r lives in [-ln(2)/2, ln(2)/2] ~ [-0.347, 0.347], small enough -// that degree-7 taylor is accurate to ~1e-9 absolute, way under the 1e-3 verify.py tolerance. -// the 2^n part is free: in ieee 754 doubles, 2^n is just (n + 1023) << 52 in the bit pattern. -// Schraudolph 1999 for the 2^n bit trick. -// ieee 754 double layout (bias 1023, exp slot at bit 52): https://en.wikipedia.org/wiki/Double-precision_floating-point_format -// Horner's method for the poly eval: https://en.wikipedia.org/wiki/Horner%27s_method +// simd expd, degree-12 taylor on the [-ln2/2, ln2/2] reduced range, +// schraudolph 2^n bit trick on the exponent slot. +// # 64 MULTs, 56 ADDs static inline __m256d exp256_pd(__m256d x) { - // n = round(x / ln(2)) - const __m256d log2e = _mm256_set1_pd(1.4426950408889634); // 1/ln(2) - __m256d nf = _mm256_round_pd(_mm256_mul_pd(x, log2e), - _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); + const __m256d log2e = _mm256_set1_pd(1.4426950408889634); + __m256d nf = _mm256_round_pd(_mm256_mul_pd(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); // # 4 MULTs - // r = x - n * ln(2) const __m256d ln2 = _mm256_set1_pd(0.6931471805599453); - __m256d r = _mm256_fnmadd_pd(nf, ln2, x); // r = x - nf*ln2 + __m256d r = _mm256_fnmadd_pd(nf, ln2, x); // # 4 MULTs, 4 ADDs - // poly(r) ~ exp(r). degree-7 taylor, horner from highest coeff downwards. - const __m256d c7 = _mm256_set1_pd(1.0 / 5040.0); - const __m256d c6 = _mm256_set1_pd(1.0 / 720.0); - const __m256d c5 = _mm256_set1_pd(1.0 / 120.0); - const __m256d c4 = _mm256_set1_pd(1.0 / 24.0); - const __m256d c3 = _mm256_set1_pd(1.0 / 6.0); - const __m256d c2 = _mm256_set1_pd(0.5); - const __m256d c1 = _mm256_set1_pd(1.0); - const __m256d c0 = _mm256_set1_pd(1.0); + const __m256d c13 = _mm256_set1_pd(c13_scalar); + const __m256d c12 = _mm256_set1_pd(c12_scalar); + const __m256d c11 = _mm256_set1_pd(c11_scalar); + const __m256d c10 = _mm256_set1_pd(c10_scalar); + const __m256d c9 = _mm256_set1_pd(c9_scalar); + const __m256d c8 = _mm256_set1_pd(c8_scalar); + const __m256d c7 = _mm256_set1_pd(c7_scalar); + const __m256d c6 = _mm256_set1_pd(c6_scalar); + const __m256d c5 = _mm256_set1_pd(c5_scalar); + const __m256d c4 = _mm256_set1_pd(c4_scalar); + const __m256d c3 = _mm256_set1_pd(c3_scalar); + const __m256d c2 = _mm256_set1_pd(c2_scalar); + const __m256d c1 = _mm256_set1_pd(c1_scalar); + const __m256d c0 = _mm256_set1_pd(c0_scalar); - __m256d p = c7; + __m256d p = c13; + + // # 13 * (4 MULTs, 4 ADDs) + // # = 52 MULTs, 52 ADDs + p = _mm256_fmadd_pd(p, r, c12); + p = _mm256_fmadd_pd(p, r, c11); + p = _mm256_fmadd_pd(p, r, c10); + p = _mm256_fmadd_pd(p, r, c9); + p = _mm256_fmadd_pd(p, r, c8); + p = _mm256_fmadd_pd(p, r, c7); p = _mm256_fmadd_pd(p, r, c6); p = _mm256_fmadd_pd(p, r, c5); p = _mm256_fmadd_pd(p, r, c4); @@ -217,18 +223,13 @@ static inline __m256d exp256_pd(__m256d x) { p = _mm256_fmadd_pd(p, r, c1); p = _mm256_fmadd_pd(p, r, c0); - // scale by 2^n via bit trick on the exponent field. - // nf is already integer-valued from the round above, cvt to int32, sign-extend to int64, - // add ieee bias 1023, shift left 52 to land in the exponent slot of a double. - // note: for padding lanes (sb_e=0, pos=1e9) n can overflow int32, the resulting pow2n is garbage - // but the active mask in the j loop zeroes that lane out before accumulation so it doesn't matter. - __m128i ni32 = _mm256_cvtpd_epi32(nf); - __m256i ni64 = _mm256_cvtepi32_epi64(ni32); + __m128i ni32 = _mm256_cvtpd_epi32(nf); + __m256i ni64 = _mm256_cvtepi32_epi64(ni32); __m256i biased = _mm256_add_epi64(ni64, _mm256_set1_epi64x(1023)); __m256i scaled = _mm256_slli_epi64(biased, 52); - __m256d pow2n = _mm256_castsi256_pd(scaled); + __m256d pow2n = _mm256_castsi256_pd(scaled); - return _mm256_mul_pd(p, pow2n); + return _mm256_mul_pd(p, pow2n); // # 4 MULTs } int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { @@ -267,15 +268,21 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m256d v_zero = _mm256_setzero_pd(); const __m256d v_np = _mm256_set1_pd((double)n_p); + // # flops per iteration (one pass of the outer time loop): + // # ADDs: n_p * 12 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 12 + n_p * 7 + // # MULTs: n_p * 4 + n_p * n_p_pad * 40 + n_p * board.n_obstacles * 19 + n_p * 16 + // # DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 + // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 + // # EXPs: n_p * board.n_obstacles for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { - double desired_velocity_x = e_x[i] * desired_v0[i]; - double desired_velocity_y = e_y[i] * desired_v0[i]; + double desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT + double desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - double f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; - double f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; + double f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD + double f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD // since i is fixed for the innermost loops we now wanna set all those variables const __m256d pix = _mm256_set1_pd(pos_x[i]); @@ -289,16 +296,16 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c for (int j = 0; j < n_p_pad; j += 4) { // 4 lanes at once, lane k maps to pedestrian j+k - __m256d j_idx = _mm256_add_pd(_mm256_set1_pd((double)j), lane_off); + __m256d j_idx = _mm256_add_pd(_mm256_set1_pd((double)j), lane_off); // # 4 ADDs // active = (j+k != i) AND (j+k < n_p), zero in lanes we wanna skip - __m256d m_neq_i = _mm256_cmp_pd(j_idx, v_i, _CMP_NEQ_OQ); + __m256d m_neq_i = _mm256_cmp_pd(j_idx, v_i, _CMP_NEQ_OQ); __m256d m_in_range = _mm256_cmp_pd(j_idx, v_np, _CMP_LT_OQ); - __m256d active = _mm256_and_pd(m_neq_i, m_in_range); + __m256d active = _mm256_and_pd(m_neq_i, m_in_range); // j-side loads, SoA contiguous. loadu because std::vector only guarantees 16B align. - __m256d pjx = _mm256_loadu_pd(&pos_x[j]); - __m256d pjy = _mm256_loadu_pd(&pos_y[j]); + __m256d pjx = _mm256_loadu_pd(&pos_x[j]); + __m256d pjy = _mm256_loadu_pd(&pos_y[j]); __m256d sbe_jx = _mm256_loadu_pd(&sb_e_x[j]); __m256d sbe_jy = _mm256_loadu_pd(&sb_e_y[j]); __m256d sb_jsq = _mm256_loadu_pd(&sb_sq[j]); @@ -309,10 +316,10 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c // double r_ab_norm = sqrt(r_ab_x * r_ab_x + r_ab_y * r_ab_y); // r_ab = pos_i - pos_j ; |r_ab| - __m256d r_ab_x = _mm256_sub_pd(pix, pjx); - __m256d r_ab_y = _mm256_sub_pd(piy, pjy); - __m256d r_ab_n2 = _mm256_fmadd_pd(r_ab_x, r_ab_x, _mm256_mul_pd(r_ab_y, r_ab_y)); - __m256d r_ab_n = _mm256_sqrt_pd(r_ab_n2); + __m256d r_ab_x = _mm256_sub_pd(pix, pjx); // # 4 ADDs + __m256d r_ab_y = _mm256_sub_pd(piy, pjy); // # 4 ADDs + __m256d r_ab_n2 = _mm256_fmadd_pd(r_ab_x, r_ab_x, _mm256_mul_pd(r_ab_y, r_ab_y)); // # 8 MULTs, 4 ADDs + __m256d r_ab_n = _mm256_sqrt_pd(r_ab_n2); // # 4 SQRTs // double r_ab2_x = r_ab_x - sb_e_x[j]; // double r_ab2_y = r_ab_y - sb_e_y[j]; @@ -320,21 +327,21 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c // double r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); // r_ab2 = r_ab - sb_e_j ; |r_ab2| - __m256d r_ab2_x = _mm256_sub_pd(r_ab_x, sbe_jx); - __m256d r_ab2_y = _mm256_sub_pd(r_ab_y, sbe_jy); - __m256d r_ab2_n2 = _mm256_fmadd_pd(r_ab2_x, r_ab2_x, _mm256_mul_pd(r_ab2_y, r_ab2_y)); - __m256d r_ab2_n = _mm256_sqrt_pd(r_ab2_n2); + __m256d r_ab2_x = _mm256_sub_pd(r_ab_x, sbe_jx); // # 4 ADDs + __m256d r_ab2_y = _mm256_sub_pd(r_ab_y, sbe_jy); // # 4 ADDs + __m256d r_ab2_n2 = _mm256_fmadd_pd(r_ab2_x, r_ab2_x, _mm256_mul_pd(r_ab2_y, r_ab2_y)); // # 8 MULTs, 4 ADDs + __m256d r_ab2_n = _mm256_sqrt_pd(r_ab2_n2); // # 4 SQRTs // double r_ab_norm_sum = r_ab_norm + r_ab2_norm; // double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; // double two_b = sqrt(two_b_sq); // 2b = sqrt((|r_ab| + |r_ab2|)^2 - sb_sq_j) - __m256d r_sum = _mm256_add_pd(r_ab_n, r_ab2_n); - __m256d two_b_sq = _mm256_fmsub_pd(r_sum, r_sum, sb_jsq); - __m256d two_b = _mm256_sqrt_pd(two_b_sq); + __m256d r_sum = _mm256_add_pd(r_ab_n, r_ab2_n); // # 4 ADDs + __m256d two_b_sq = _mm256_fmsub_pd(r_sum, r_sum, sb_jsq); // # 4 MULTs, 4 ADDs + __m256d two_b = _mm256_sqrt_pd(two_b_sq); // # 4 SQRTs - __m256d e_arg = _mm256_mul_pd(two_b, v_neg_2s_inv); + __m256d e_arg = _mm256_mul_pd(two_b, v_neg_2s_inv); // # 4 MULTs // we used to call libm exp() 4 times into a scratch array here, now do it in simd // alignas(32) double tmp_arg[4], tmp_val[4]; // _mm256_store_pd(tmp_arg, e_arg); @@ -343,109 +350,109 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c // tmp_val[2] = exp(tmp_arg[2]); // tmp_val[3] = exp(tmp_arg[3]); // __m256d e_val = _mm256_load_pd(tmp_val); - __m256d e_val = exp256_pd(e_arg); + __m256d e_val = exp256_pd(e_arg); // # 64 MULTs, 56 ADDs // double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); // smc = (r_sum * NEG_V0_AB_div_TWO_SIGMA * e_val) / (two_b * r_ab_n * r_ab2_n) // the i==j lane collapses denom to 0, so we swap in 1.0 there to keep smc finite. // the AND with active zeroes the contribution from that lane anyway. - __m256d numer = _mm256_mul_pd(_mm256_mul_pd(r_sum, v_neg_v0_div_2s), e_val); - __m256d denom = _mm256_mul_pd(_mm256_mul_pd(two_b, r_ab_n), r_ab2_n); - __m256d denom_safe = _mm256_blendv_pd(_mm256_set1_pd(1.0), denom, active); - __m256d smc = _mm256_div_pd(numer, denom_safe); + __m256d numer = _mm256_mul_pd(_mm256_mul_pd(r_sum, v_neg_v0_div_2s), e_val); // # 8 MULTs + __m256d denom = _mm256_mul_pd(_mm256_mul_pd(two_b, r_ab_n), r_ab2_n); // # 8 MULTs + __m256d denom_safe = _mm256_blendv_pd(_mm256_set1_pd(1.0), denom, active); // bitwise, no flop + __m256d smc = _mm256_div_pd(numer, denom_safe); // # 4 DIVs // double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; // double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - __m256d sm_r_ab = _mm256_mul_pd(smc, r_ab2_n); - __m256d sm_r_ab2 = _mm256_mul_pd(smc, r_ab_n); + __m256d sm_r_ab = _mm256_mul_pd(smc, r_ab2_n); // # 4 MULTs + __m256d sm_r_ab2 = _mm256_mul_pd(smc, r_ab_n); // # 4 MULTs // double f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; // double f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; // scalar wants r_ab * sm_r_ab + r_ab2 * sm_r_ab2, the previous fma had the layout wrong - __m256d f_x = _mm256_fmadd_pd(r_ab2_x, sm_r_ab2, _mm256_mul_pd(r_ab_x, sm_r_ab)); - __m256d f_y = _mm256_fmadd_pd(r_ab2_y, sm_r_ab2, _mm256_mul_pd(r_ab_y, sm_r_ab)); + __m256d f_x = _mm256_fmadd_pd(r_ab2_x, sm_r_ab2, _mm256_mul_pd(r_ab_x, sm_r_ab)); // # 8 MULTs, 4 ADDs + __m256d f_y = _mm256_fmadd_pd(r_ab2_y, sm_r_ab2, _mm256_mul_pd(r_ab_y, sm_r_ab)); // # 8 MULTs, 4 ADDs // w_val = dt_neg by default, c_neg_times_dt when (a >= 0 && a*a > b_sq) - __m256d a = _mm256_mul_pd(_mm256_fmadd_pd(eix, f_x, _mm256_mul_pd(eiy, f_y)), v_cos_phi_inv); - __m256d b_sq = _mm256_fmadd_pd(f_x, f_x, _mm256_mul_pd(f_y, f_y)); - __m256d a_sq = _mm256_mul_pd(a, a); + __m256d a = _mm256_mul_pd(_mm256_fmadd_pd(eix, f_x, _mm256_mul_pd(eiy, f_y)), v_cos_phi_inv); // # 12 MULTs, 4 ADDs + __m256d b_sq = _mm256_fmadd_pd(f_x, f_x, _mm256_mul_pd(f_y, f_y)); // # 8 MULTs, 4 ADDs + __m256d a_sq = _mm256_mul_pd(a, a); // # 4 MULTs - __m256d c1 = _mm256_cmp_pd(a, v_zero, _CMP_GE_OQ); - __m256d c2 = _mm256_cmp_pd(a_sq, b_sq, _CMP_GT_OQ); - __m256d cond = _mm256_and_pd(c1, c2); + __m256d c1 = _mm256_cmp_pd(a, v_zero, _CMP_GE_OQ); + __m256d c2 = _mm256_cmp_pd(a_sq, b_sq, _CMP_GT_OQ); + __m256d cond = _mm256_and_pd(c1, c2); __m256d w_val = _mm256_blendv_pd(v_dt_neg, v_c_neg_dt, cond); // zero out inactive lanes (i==j and padding) before accumulating. // AND with all-zero mask = 0, AND with all-one mask passes through. cheaper than blendv here. - __m256d f_x_safe = _mm256_and_pd(f_x, active); - __m256d f_y_safe = _mm256_and_pd(f_y, active); - acc_x = _mm256_fmadd_pd(f_x_safe, w_val, acc_x); - acc_y = _mm256_fmadd_pd(f_y_safe, w_val, acc_y); + __m256d f_x_safe = _mm256_and_pd(f_x, active); // bitwise, no flop + __m256d f_y_safe = _mm256_and_pd(f_y, active); // bitwise, no flop + acc_x = _mm256_fmadd_pd(f_x_safe, w_val, acc_x); // # 4 MULTs, 4 ADDs + acc_y = _mm256_fmadd_pd(f_y_safe, w_val, acc_y); // # 4 MULTs, 4 ADDs } // horizontal sum acc_x, acc_y into the scalar f_alpha_dt - __m256d hsum_xy = _mm256_hadd_pd(acc_x, acc_y); // [x0+x1, y0+y1, x2+x3, y2+y3] - __m128d lo = _mm256_castpd256_pd128(hsum_xy); - __m128d hi = _mm256_extractf128_pd(hsum_xy, 1); - __m128d sum_xy = _mm_add_pd(lo, hi); // [x_total, y_total] + __m256d hsum_xy = _mm256_hadd_pd(acc_x, acc_y); // [x0+x1, y0+y1, x2+x3, y2+y3] // # 4 ADDs + __m128d lo = _mm256_castpd256_pd128(hsum_xy); + __m128d hi = _mm256_extractf128_pd(hsum_xy, 1); + __m128d sum_xy = _mm_add_pd(lo, hi); // [x_total, y_total] // # 2 ADDs alignas(16) double sum_out[2]; _mm_store_pd(sum_out, sum_xy); - f_alpha_dt_x += sum_out[0]; - f_alpha_dt_y += sum_out[1]; + f_alpha_dt_x += sum_out[0]; // # 1 ADD + f_alpha_dt_y += sum_out[1]; // # 1 ADD for (int k = 0; k < board.n_obstacles; k++) { - double v1_x = pos_x[i] - board.obstacles[k].start.x; - double v1_y = pos_y[i] - board.obstacles[k].start.y; + double v1_x = pos_x[i] - board.obstacles[k].start.x; // # 1 ADD + double v1_y = pos_y[i] - board.obstacles[k].start.y; // # 1 ADD - double projection = (v1_x * board.obstacles[k].obstacle_v.x + v1_y * board.obstacles[k].obstacle_v.y) * board.obstacles[k].length_sq_inv; + double projection = (v1_x * board.obstacles[k].obstacle_v.x + v1_y * board.obstacles[k].obstacle_v.y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD if (projection < 0.0) projection = 0.0; else if (projection > 1.0) projection = 1.0; - double r_aB_x = pos_x[i] - (board.obstacles[k].start.x + board.obstacles[k].obstacle_v.x * projection); - double r_aB_y = pos_y[i] - (board.obstacles[k].start.y + board.obstacles[k].obstacle_v.y * projection); + double r_aB_x = pos_x[i] - (board.obstacles[k].start.x + board.obstacles[k].obstacle_v.x * projection); // # 1 MULT, 2 ADDs + double r_aB_y = pos_y[i] - (board.obstacles[k].start.y + board.obstacles[k].obstacle_v.y * projection); // # 1 MULT, 2 ADDs - double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - double f_alpha_B_neg_x = r_aB_x * scalar_mult; - double f_alpha_B_neg_y = r_aB_y * scalar_mult; + double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT + double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV + double f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT + double f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT double w_val; - double a = (e_x[i] * f_alpha_B_neg_x + e_y[i] * f_alpha_B_neg_y) * cos_phi_inv; - double b_sq = f_alpha_B_neg_x * f_alpha_B_neg_x + f_alpha_B_neg_y * f_alpha_B_neg_y; + double a = (e_x[i] * f_alpha_B_neg_x + e_y[i] * f_alpha_B_neg_y) * cos_phi_inv; // # 3 MULTs, 1 ADD + double b_sq = f_alpha_B_neg_x * f_alpha_B_neg_x + f_alpha_B_neg_y * f_alpha_B_neg_y; // # 2 MULTs, 1 ADD w_val = dt_neg; - if (a >= 0 && (a * a > b_sq)) { + if (a >= 0 && (a * a > b_sq)) { // # 1 MULT for a*a w_val = c_neg_times_dt; } - double f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - double f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; + double f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT + double f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - f_alpha_dt_x += f_alpha_B_dt_x; - f_alpha_dt_y += f_alpha_B_dt_y; + f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD + f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD } - w_x[i] += f_alpha_dt_x; - w_y[i] += f_alpha_dt_y; + w_x[i] += f_alpha_dt_x; // # 1 ADD + w_y[i] += f_alpha_dt_y; // # 1 ADD } for (int i = 0; i < n_p; i++) { - double w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); + double w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT - double correction_factor = min(1.0, v_max[i] / w_norm); + double correction_factor = min(1.0, v_max[i] / w_norm); // # 1 DIV - curr_vel_x[i] = w_x[i] * correction_factor; - curr_vel_y[i] = w_y[i] * correction_factor; + curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT + curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; + pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD + pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { pos_x[i] = 1.0; @@ -466,22 +473,22 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - double ex = destination_x[i] - pos_x[i]; - double ey = destination_y[i] - pos_y[i]; + double ex = destination_x[i] - pos_x[i]; // # 1 ADD + double ey = destination_y[i] - pos_y[i]; // # 1 ADD - double e_norm_inv = 1.0 / sqrt(ex * ex + ey * ey); + double e_norm_inv = 1.0 / sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT, 1 DIV - e_x[i] = ex * e_norm_inv; - e_y[i] = ey * e_norm_inv; + e_x[i] = ex * e_norm_inv; // # 1 MULT + e_y[i] = ey * e_norm_inv; // # 1 MULT - double velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; + double velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD - double velocity_norm = sqrt(velocity_norm_sq); + double velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - sb_sq[i] = velocity_norm_sq * delta_t_sq; + sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); + sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) + sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT #if !BENCHMARK if (dump_path && (t % dump_interval == 0)) { diff --git a/variants/variant_inlining_7_SoA_AVX2_polyexp_expfix.cpp b/variants/variant_inlining_7_SoA_AVX2_polyexp_expfix.cpp deleted file mode 100644 index a5c93b8..0000000 --- a/variants/variant_inlining_7_SoA_AVX2_polyexp_expfix.cpp +++ /dev/null @@ -1,513 +0,0 @@ -// # This file uses avx2 -// # builds on variant_inlining_7_SoA_AVX2 by killing the 4 scalar libm exp() calls -// # in the j loop and doing it in simd with a polynomial approximation instead. - -// Okay our first step is to use a SoA instead of an AoS -// We only do SoA for the pedestrian struct, there we wanna optimize such that we access for the ped-ped forces adjacent pedestrians (in the arrays) -// Now in a second step we wanna really use AVX2 for the mainloop (where we wana optimize the most innerloop first) -// next step now: the scalar exp() inside the j loop was ~54% of inner-loop cycles, we replace with one simd call. - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian_reduced.hpp" - -#include // to use avx2 - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// we wanna use arrays so we can implement SOA, we create arrays for all the data we need and access -vector pos_x, pos_y; -vector sb_sq; -vector sb_e_x, sb_e_y; -vector e_x, e_y; -vector curr_vel_x, curr_vel_y; -vector w_x, w_y; -vector desired_v0; -vector v_max; -vector destination_x, destination_y; -vector color; - -Pedestrian *init_pedestrians(int n_p) { - -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - const int n_p_pad = (n_p + 3) & ~3; // round up to a multiple of 4 (so its easy to use avx) - - // resize all our arrays for this run - pos_x.resize(n_p_pad); - pos_y.resize(n_p_pad); - sb_sq.resize(n_p_pad); - sb_e_x.resize(n_p_pad); - sb_e_y.resize(n_p_pad); - e_x.resize(n_p_pad); - e_y.resize(n_p_pad); - curr_vel_x.resize(n_p_pad); - curr_vel_y.resize(n_p_pad); - w_x.resize(n_p_pad); - w_y.resize(n_p_pad); - desired_v0.resize(n_p_pad); - v_max.resize(n_p_pad); - destination_x.resize(n_p_pad); - destination_y.resize(n_p_pad); - color.resize(n_p_pad); - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - double dest_x = BOARD_LENGTH + 30.0; // renamed to avoid shadowing destination_x[] - double dest_y = y; // same idea for destination_y[] - - double init_e_x = dest_x - x; - double init_e_y = dest_y - y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - double v_m = 1.3 * d_v0; - - color[i] = 0; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - // Fields the Pedestrian constructor would have zero-initialized: - w_x[i] = 0.0; - w_y[i] = 0.0; - curr_vel_x[i] = 0.0; - curr_vel_y[i] = 0.0; - sb_sq[i] = 0.0; - sb_e_x[i] = 0.0; - sb_e_y[i] = 0.0; - } - - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - double dest_x = -30; - double dest_y = y; - - double init_e_x = dest_x - x; - double init_e_y = dest_y - y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double d_v0; - do { - d_v0 = velocity_gen(gen); - } while (d_v0 <= 0.1); - - double v_m = 1.3 * d_v0; - - color[i] = 1; - pos_x[i] = x; - pos_y[i] = y; - e_x[i] = init_e_unit_x; - e_y[i] = init_e_unit_y; - v_max[i] = v_m; - desired_v0[i] = d_v0; - destination_x[i] = dest_x; - destination_y[i] = dest_y; - - // Fields the Pedestrian constructor would have zero-initialized: - w_x[i] = 0.0; - w_y[i] = 0.0; - curr_vel_x[i] = 0.0; - curr_vel_y[i] = 0.0; - sb_sq[i] = 0.0; - sb_e_x[i] = 0.0; - sb_e_y[i] = 0.0; - } - - // Padding lanes: positions far away, everything else zero. Any unmasked SIMD - // contribution from these lanes will vanish through exp(very negative) roughly 0. - for (int j = n_p; j < n_p_pad; j++) { - pos_x[j] = 1e9; - pos_y[j] = 1e9; - sb_sq[j] = 0.0; - sb_e_x[j] = 0.0; - sb_e_y[j] = 0.0; - } - return nullptr; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -// simd expd, degree-12 taylor on the [-ln2/2, ln2/2] reduced range, -// schraudolph 2^n bit trick on the exponent slot. -// # 64 MULTs, 56 ADDs -static inline __m256d exp256_pd(__m256d x) { - const __m256d log2e = _mm256_set1_pd(1.4426950408889634); - __m256d nf = _mm256_round_pd(_mm256_mul_pd(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); // # 4 MULTs - - const __m256d ln2 = _mm256_set1_pd(0.6931471805599453); - __m256d r = _mm256_fnmadd_pd(nf, ln2, x); // # 4 MULTs, 4 ADDs - - const __m256d c13 = _mm256_set1_pd(c13_scalar); - const __m256d c12 = _mm256_set1_pd(c12_scalar); - const __m256d c11 = _mm256_set1_pd(c11_scalar); - const __m256d c10 = _mm256_set1_pd(c10_scalar); - const __m256d c9 = _mm256_set1_pd(c9_scalar); - const __m256d c8 = _mm256_set1_pd(c8_scalar); - const __m256d c7 = _mm256_set1_pd(c7_scalar); - const __m256d c6 = _mm256_set1_pd(c6_scalar); - const __m256d c5 = _mm256_set1_pd(c5_scalar); - const __m256d c4 = _mm256_set1_pd(c4_scalar); - const __m256d c3 = _mm256_set1_pd(c3_scalar); - const __m256d c2 = _mm256_set1_pd(c2_scalar); - const __m256d c1 = _mm256_set1_pd(c1_scalar); - const __m256d c0 = _mm256_set1_pd(c0_scalar); - - __m256d p = c13; - - // # 13 * (4 MULTs, 4 ADDs) - // # = 52 MULTs, 52 ADDs - p = _mm256_fmadd_pd(p, r, c12); - p = _mm256_fmadd_pd(p, r, c11); - p = _mm256_fmadd_pd(p, r, c10); - p = _mm256_fmadd_pd(p, r, c9); - p = _mm256_fmadd_pd(p, r, c8); - p = _mm256_fmadd_pd(p, r, c7); - p = _mm256_fmadd_pd(p, r, c6); - p = _mm256_fmadd_pd(p, r, c5); - p = _mm256_fmadd_pd(p, r, c4); - p = _mm256_fmadd_pd(p, r, c3); - p = _mm256_fmadd_pd(p, r, c2); - p = _mm256_fmadd_pd(p, r, c1); - p = _mm256_fmadd_pd(p, r, c0); - - __m128i ni32 = _mm256_cvtpd_epi32(nf); - __m256i ni64 = _mm256_cvtepi32_epi64(ni32); - __m256i biased = _mm256_add_epi64(ni64, _mm256_set1_epi64x(1023)); - __m256i scaled = _mm256_slli_epi64(biased, 52); - __m256d pow2n = _mm256_castsi256_pd(scaled); - - return _mm256_mul_pd(p, pow2n); // # 4 MULTs -} - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - // Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } - } -#endif - - // all the constants for the AVX2 setup - - const int n_p_pad = (n_p + 3) & ~3; - - alignas(32) const double lane_offset_arr[4] = {0.0, 1.0, 2.0, 3.0}; - const __m256d lane_off = _mm256_load_pd(lane_offset_arr); - - const __m256d v_neg_v0_div_2s = _mm256_set1_pd(NEG_V0_AB_div_TWO_SIGMA); - const __m256d v_neg_2s_inv = _mm256_set1_pd(NEG_TWO_SIGMA_INV); - const __m256d v_cos_phi_inv = _mm256_set1_pd(cos_phi_inv); - const __m256d v_dt_neg = _mm256_set1_pd(dt_neg); - const __m256d v_c_neg_dt = _mm256_set1_pd(c_neg_times_dt); - const __m256d v_zero = _mm256_setzero_pd(); - const __m256d v_np = _mm256_set1_pd((double)n_p); - - // # flops per iteration (one pass of the outer time loop): - // # ADDs: n_p * 12 + n_p * n_p_pad * 29 + n_p * board.n_obstacles * 12 + n_p * 7 - // # MULTs: n_p * 4 + n_p * n_p_pad * 40 + n_p * board.n_obstacles * 19 + n_p * 16 - // # DIVs: n_p * n_p_pad * 1 + n_p * board.n_obstacles + n_p * 2 - // # SQRTs: n_p * n_p_pad * 3 + n_p * board.n_obstacles + n_p * 3 - // # EXPs: n_p * board.n_obstacles - for (int t = 0; t < n_iter; t++) { - - for (int i = 0; i < n_p; i++) { - - double desired_velocity_x = e_x[i] * desired_v0[i]; // # 1 MULT - double desired_velocity_y = e_y[i] * desired_v0[i]; // # 1 MULT - - double f_alpha_dt_x = (desired_velocity_x - curr_vel_x[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - double f_alpha_dt_y = (desired_velocity_y - curr_vel_y[i]) * dt_times_tau_alpha_inv; // # 1 MULT, 1 ADD - - // since i is fixed for the innermost loops we now wanna set all those variables - const __m256d pix = _mm256_set1_pd(pos_x[i]); - const __m256d piy = _mm256_set1_pd(pos_y[i]); - const __m256d eix = _mm256_set1_pd(e_x[i]); - const __m256d eiy = _mm256_set1_pd(e_y[i]); - const __m256d v_i = _mm256_set1_pd((double)i); - - __m256d acc_x = _mm256_setzero_pd(); - __m256d acc_y = _mm256_setzero_pd(); - - for (int j = 0; j < n_p_pad; j += 4) { - // 4 lanes at once, lane k maps to pedestrian j+k - __m256d j_idx = _mm256_add_pd(_mm256_set1_pd((double)j), lane_off); // # 4 ADDs - - // active = (j+k != i) AND (j+k < n_p), zero in lanes we wanna skip - __m256d m_neq_i = _mm256_cmp_pd(j_idx, v_i, _CMP_NEQ_OQ); - __m256d m_in_range = _mm256_cmp_pd(j_idx, v_np, _CMP_LT_OQ); - __m256d active = _mm256_and_pd(m_neq_i, m_in_range); - - // j-side loads, SoA contiguous. loadu because std::vector only guarantees 16B align. - __m256d pjx = _mm256_loadu_pd(&pos_x[j]); - __m256d pjy = _mm256_loadu_pd(&pos_y[j]); - __m256d sbe_jx = _mm256_loadu_pd(&sb_e_x[j]); - __m256d sbe_jy = _mm256_loadu_pd(&sb_e_y[j]); - __m256d sb_jsq = _mm256_loadu_pd(&sb_sq[j]); - - // double r_ab_x = pos_x[i] - pos_x[j]; - // double r_ab_y = pos_y[i] - pos_y[j]; - // - // double r_ab_norm = sqrt(r_ab_x * r_ab_x + r_ab_y * r_ab_y); - - // r_ab = pos_i - pos_j ; |r_ab| - __m256d r_ab_x = _mm256_sub_pd(pix, pjx); // # 4 ADDs - __m256d r_ab_y = _mm256_sub_pd(piy, pjy); // # 4 ADDs - __m256d r_ab_n2 = _mm256_fmadd_pd(r_ab_x, r_ab_x, _mm256_mul_pd(r_ab_y, r_ab_y)); // # 8 MULTs, 4 ADDs - __m256d r_ab_n = _mm256_sqrt_pd(r_ab_n2); // # 4 SQRTs - - // double r_ab2_x = r_ab_x - sb_e_x[j]; - // double r_ab2_y = r_ab_y - sb_e_y[j]; - // - // double r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - - // r_ab2 = r_ab - sb_e_j ; |r_ab2| - __m256d r_ab2_x = _mm256_sub_pd(r_ab_x, sbe_jx); // # 4 ADDs - __m256d r_ab2_y = _mm256_sub_pd(r_ab_y, sbe_jy); // # 4 ADDs - __m256d r_ab2_n2 = _mm256_fmadd_pd(r_ab2_x, r_ab2_x, _mm256_mul_pd(r_ab2_y, r_ab2_y)); // # 8 MULTs, 4 ADDs - __m256d r_ab2_n = _mm256_sqrt_pd(r_ab2_n2); // # 4 SQRTs - - // double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - // double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - sb_sq[j]; - // double two_b = sqrt(two_b_sq); - - // 2b = sqrt((|r_ab| + |r_ab2|)^2 - sb_sq_j) - __m256d r_sum = _mm256_add_pd(r_ab_n, r_ab2_n); // # 4 ADDs - __m256d two_b_sq = _mm256_fmsub_pd(r_sum, r_sum, sb_jsq); // # 4 MULTs, 4 ADDs - __m256d two_b = _mm256_sqrt_pd(two_b_sq); // # 4 SQRTs - - __m256d e_arg = _mm256_mul_pd(two_b, v_neg_2s_inv); // # 4 MULTs - // we used to call libm exp() 4 times into a scratch array here, now do it in simd - // alignas(32) double tmp_arg[4], tmp_val[4]; - // _mm256_store_pd(tmp_arg, e_arg); - // tmp_val[0] = exp(tmp_arg[0]); - // tmp_val[1] = exp(tmp_arg[1]); - // tmp_val[2] = exp(tmp_arg[2]); - // tmp_val[3] = exp(tmp_arg[3]); - // __m256d e_val = _mm256_load_pd(tmp_val); - __m256d e_val = exp256_pd(e_arg); // # 64 MULTs, 56 ADDs - - // double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - - // smc = (r_sum * NEG_V0_AB_div_TWO_SIGMA * e_val) / (two_b * r_ab_n * r_ab2_n) - // the i==j lane collapses denom to 0, so we swap in 1.0 there to keep smc finite. - // the AND with active zeroes the contribution from that lane anyway. - __m256d numer = _mm256_mul_pd(_mm256_mul_pd(r_sum, v_neg_v0_div_2s), e_val); // # 8 MULTs - __m256d denom = _mm256_mul_pd(_mm256_mul_pd(two_b, r_ab_n), r_ab2_n); // # 8 MULTs - __m256d denom_safe = _mm256_blendv_pd(_mm256_set1_pd(1.0), denom, active); // bitwise, no flop - __m256d smc = _mm256_div_pd(numer, denom_safe); // # 4 DIVs - - // double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - __m256d sm_r_ab = _mm256_mul_pd(smc, r_ab2_n); // # 4 MULTs - __m256d sm_r_ab2 = _mm256_mul_pd(smc, r_ab_n); // # 4 MULTs - - // double f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // double f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - // scalar wants r_ab * sm_r_ab + r_ab2 * sm_r_ab2, the previous fma had the layout wrong - __m256d f_x = _mm256_fmadd_pd(r_ab2_x, sm_r_ab2, _mm256_mul_pd(r_ab_x, sm_r_ab)); // # 8 MULTs, 4 ADDs - __m256d f_y = _mm256_fmadd_pd(r_ab2_y, sm_r_ab2, _mm256_mul_pd(r_ab_y, sm_r_ab)); // # 8 MULTs, 4 ADDs - - // w_val = dt_neg by default, c_neg_times_dt when (a >= 0 && a*a > b_sq) - __m256d a = _mm256_mul_pd(_mm256_fmadd_pd(eix, f_x, _mm256_mul_pd(eiy, f_y)), v_cos_phi_inv); // # 12 MULTs, 4 ADDs - __m256d b_sq = _mm256_fmadd_pd(f_x, f_x, _mm256_mul_pd(f_y, f_y)); // # 8 MULTs, 4 ADDs - __m256d a_sq = _mm256_mul_pd(a, a); // # 4 MULTs - - __m256d c1 = _mm256_cmp_pd(a, v_zero, _CMP_GE_OQ); - __m256d c2 = _mm256_cmp_pd(a_sq, b_sq, _CMP_GT_OQ); - __m256d cond = _mm256_and_pd(c1, c2); - __m256d w_val = _mm256_blendv_pd(v_dt_neg, v_c_neg_dt, cond); - - // zero out inactive lanes (i==j and padding) before accumulating. - // AND with all-zero mask = 0, AND with all-one mask passes through. cheaper than blendv here. - __m256d f_x_safe = _mm256_and_pd(f_x, active); // bitwise, no flop - __m256d f_y_safe = _mm256_and_pd(f_y, active); // bitwise, no flop - acc_x = _mm256_fmadd_pd(f_x_safe, w_val, acc_x); // # 4 MULTs, 4 ADDs - acc_y = _mm256_fmadd_pd(f_y_safe, w_val, acc_y); // # 4 MULTs, 4 ADDs - } - - // horizontal sum acc_x, acc_y into the scalar f_alpha_dt - __m256d hsum_xy = _mm256_hadd_pd(acc_x, acc_y); // [x0+x1, y0+y1, x2+x3, y2+y3] // # 4 ADDs - __m128d lo = _mm256_castpd256_pd128(hsum_xy); - __m128d hi = _mm256_extractf128_pd(hsum_xy, 1); - __m128d sum_xy = _mm_add_pd(lo, hi); // [x_total, y_total] // # 2 ADDs - alignas(16) double sum_out[2]; - _mm_store_pd(sum_out, sum_xy); - f_alpha_dt_x += sum_out[0]; // # 1 ADD - f_alpha_dt_y += sum_out[1]; // # 1 ADD - - for (int k = 0; k < board.n_obstacles; k++) { - - double v1_x = pos_x[i] - board.obstacles[k].start.x; // # 1 ADD - double v1_y = pos_y[i] - board.obstacles[k].start.y; // # 1 ADD - - double projection = (v1_x * board.obstacles[k].obstacle_v.x + v1_y * board.obstacles[k].obstacle_v.y) * board.obstacles[k].length_sq_inv; // # 3 MULTs, 1 ADD - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - double r_aB_x = pos_x[i] - (board.obstacles[k].start.x + board.obstacles[k].obstacle_v.x * projection); // # 1 MULT, 2 ADDs - double r_aB_y = pos_y[i] - (board.obstacles[k].start.y + board.obstacles[k].obstacle_v.y * projection); // # 1 MULT, 2 ADDs - - double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); // # 2 MULTs, 1 ADD, 1 SQRT - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; // # 2 MULTs, 1 EXP, 1 DIV - double f_alpha_B_neg_x = r_aB_x * scalar_mult; // # 1 MULT - double f_alpha_B_neg_y = r_aB_y * scalar_mult; // # 1 MULT - - double w_val; - double a = (e_x[i] * f_alpha_B_neg_x + e_y[i] * f_alpha_B_neg_y) * cos_phi_inv; // # 3 MULTs, 1 ADD - double b_sq = f_alpha_B_neg_x * f_alpha_B_neg_x + f_alpha_B_neg_y * f_alpha_B_neg_y; // # 2 MULTs, 1 ADD - - w_val = dt_neg; - if (a >= 0 && (a * a > b_sq)) { // # 1 MULT for a*a - w_val = c_neg_times_dt; - } - - double f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; // # 1 MULT - double f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; // # 1 MULT - - f_alpha_dt_x += f_alpha_B_dt_x; // # 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; // # 1 ADD - } - - w_x[i] += f_alpha_dt_x; // # 1 ADD - w_y[i] += f_alpha_dt_y; // # 1 ADD - } - - for (int i = 0; i < n_p; i++) { - - double w_norm = sqrt(w_x[i] * w_x[i] + w_y[i] * w_y[i]); // # 2 MULTs, 1 ADD, 1 SQRT - - double correction_factor = min(1.0, v_max[i] / w_norm); // # 1 DIV - - curr_vel_x[i] = w_x[i] * correction_factor; // # 1 MULT - curr_vel_y[i] = w_y[i] * correction_factor; // # 1 MULT - - pos_x[i] = pos_x[i] + curr_vel_x[i] * dt; // # 1 MULT, 1 ADD - pos_y[i] = pos_y[i] + curr_vel_y[i] * dt; // # 1 MULT, 1 ADD - - if (color[i] == 0 && pos_x[i] > (BOARD_LENGTH - 1.5)) { - pos_x[i] = 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - if (color[i] == 1 && pos_x[i] < 1.5) { - pos_x[i] = BOARD_LENGTH - 1.0; - destination_y[i] = pos_y[i]; - w_x[i] = -desired_v0[i]; - w_y[i] = 0.0; - curr_vel_x[i] = w_x[i]; - curr_vel_y[i] = w_y[i]; - } - - pos_y[i] = clamp(pos_y[i], WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - double ex = destination_x[i] - pos_x[i]; // # 1 ADD - double ey = destination_y[i] - pos_y[i]; // # 1 ADD - - double e_norm_inv = 1.0 / sqrt(ex * ex + ey * ey); // # 2 MULTs, 1 ADD, 1 SQRT, 1 DIV - - e_x[i] = ex * e_norm_inv; // # 1 MULT - e_y[i] = ey * e_norm_inv; // # 1 MULT - - double velocity_norm_sq = curr_vel_x[i] * curr_vel_x[i] + curr_vel_y[i] * curr_vel_y[i]; // # 2 MULTs, 1 ADD - - double velocity_norm = sqrt(velocity_norm_sq); // # 1 SQRT - - sb_sq[i] = velocity_norm_sq * delta_t_sq; // # 1 MULT - - sb_e_x[i] = e_x[i] * (velocity_norm * delta_t); // # 2 MULTs (vel_norm*delta_t shared with next line) - sb_e_y[i] = e_y[i] * (velocity_norm * delta_t); // # 1 MULT - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, pos_x[i], pos_y[i], e_x[i], e_y[i], color[i]); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_inlining_7_symmetry_1_flopcount.cpp b/variants/variant_inlining_7_symmetry_1_flopcount.cpp deleted file mode 100644 index b0888a8..0000000 --- a/variants/variant_inlining_7_symmetry_1_flopcount.cpp +++ /dev/null @@ -1,423 +0,0 @@ -// # Use symmetrical properties to reduce flop count -// # Changes: flopcount -// FLOPCOUNT = n_iter * ( 69 * n_p ** 2 - 32 * n_p + 63 * n_obstacles * n_p ) -// n_iter * [ n_p ** 2 * (12.5 ADD + 23 MULT + 1 DIV + 1 EXP + 2.5 SQRT) + n_p * (- 1.5 ADD + 0.5 SQRT - 2 MULT - 1 EXP + 1 DIV) + n_obstacles * n_p * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) ] -// # Predecessor: variant_inlining_7_symmetry_1.cpp - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian_reduced.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - double start_pos_x = x; - double start_pos_y = y; - - double destination_x = BOARD_LENGTH + 30.0; - double destination_y = y; - - double init_e_x = destination_x - start_pos_x; - double init_e_y = destination_y - start_pos_y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(0, 0, 0, start_pos_x, start_pos_y, init_e_unit_x, init_e_unit_y, v_max, desired_v0, destination_x, destination_y); - } - - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - double start_pos_x = x; - double start_pos_y = y; - double destination_x = -30; - double destination_y = y; - - double init_e_x = destination_x - start_pos_x; - double init_e_y = destination_y - start_pos_y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(1, 0, 0, start_pos_x, start_pos_y, init_e_unit_x, init_e_unit_y, v_max, desired_v0, destination_x, destination_y); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos_x, p.curr_pos_y, p.e_x, p.e_y, p.color); - } - } -#endif - - // here we have real (simple) symmetry - std::vector r_ab_x_lut(n_p * n_p, 0.0); - std::vector r_ab_y_lut(n_p * n_p, 0.0); - std::vector r_ab_norm_lut(n_p * n_p, 0.0); - // use NAME[i * n_p + j] - - // FLOPCOUNT = n_iter * ( {0.5 * (n_p * n_p - n_p) * 6 FLOPS} + {n_p * [ (8 FLOPS) + ((n_p - 1) * 66 FLOPS) + (n_obstacles * 63 FLOPS) ]} + {n_p * 29 FLOPS} ) - // FLOPCOUNT = n_iter * ( {3 * n_p ** 2 - 3 * n_p} + {66 * n_p ** 2 - 58 * n_p + 63 * n_obstacles * n_p} + {29 * n_p} ) - // FLOPCOUNT = n_iter * ( 3 * n_p ** 2 - 3 * n_p + 66 * n_p ** 2 - 58 * n_p + 63 * n_obstacles * n_p + 29 * n_p) - // FLOPCOUNT = n_iter * ( [3 * n_p ** 2 + 66 * n_p ** 2] + [- 3 * n_p - 58 * n_p + 29 * n_p] + [63 * n_obstacles * n_p] ) - // FLOPCOUNT = n_iter * ( 69 * n_p ** 2 - 32 * n_p + 63 * n_obstacles * n_p ) - - // n_iter * {0.5 * (n_p * n_p - n_p) * (3 ADD + 1 SQRT + 2 MULT) + n_p * [(4 MULT + 4 ADD) + ((n_p - 1) * (11 ADD + 22 MULT + 1 DIV + 1 EXP + 2 SQRT)) + (n_obstacles * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP))] + n_p * [ 7 ADD + 17 MULT + 3 SQRT + 2 DIV ]} - - - - // 0.5 * (n_p * n_p - n_p) * (3 ADD + 1 SQRT + 2 MULT) - // n_p ** 2 * (1.5 ADD + 0.5 SQRT + 1 MULT) + n_p * (- 1.5 ADD - 0.5 SQRT - 1 MULT) - - - - // n_p * [(4 MULT + 4 ADD) + ((n_p - 1) * (11 ADD + 22 MULT + 1 DIV + 1 EXP + 2 SQRT)) + (n_obstacles * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP))] - // n_p * [n_p * (11 ADD + 22 MULT + 1 DIV + 1 EXP + 2 SQRT) - 1 * (7 ADD + 18 MULT + 1 DIV + 1 EXP + 2 SQRT) + n_obstacles * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP)] - // n_p ** 2 * (11 ADD + 22 MULT + 1 DIV + 1 EXP + 2 SQRT) - n_p * (7 ADD + 18 MULT + 1 DIV + 1 EXP + 2 SQRT) + n_obstacles * n_p * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) - - - - // n_p * (7 ADD + 17 MULT + 3 SQRT + 2 DIV) - - - - // n_p ** 2 * (1.5 ADD + 0.5 SQRT + 1 MULT) + n_p * (- 1.5 ADD - 0.5 SQRT - 1 MULT) + n_p ** 2 * (11 ADD + 22 MULT + 1 DIV + 1 EXP + 2 SQRT) - n_p * (7 ADD + 18 MULT + 1 DIV + 1 EXP + 2 SQRT) + n_obstacles * n_p * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) + n_p * (7 ADD + 17 MULT + 3 SQRT + 2 DIV) - // n_p ** 2 * (12.5 ADD + 23 MULT + 1 DIV + 1 EXP + 2.5 SQRT) + n_p * (- 1.5 ADD + 0.5 SQRT - 2 MULT - 1 EXP + 1 DIV) + n_obstacles * n_p * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) - // n_p ** 2 * (12.5 A + 23 M + 1 D + 1 E + 2.5 S) + n_p * (- 1.5 A + 0.5 S - 2 M - 1 E + 1 D) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E) - - for (int t = 0; t < n_iter; t++) { - - // get all distances first - // total iterations = 0.5 * (n_p * n_p - n_p) - // flops per iter = 6 FLOPS - // FLOPCOUNT = 0.5 * (n_p * n_p - n_p) * 6 FLOPS - // 0.5 * (n_p * n_p - n_p) * (3 ADD + 1 SQRT + 2 MULT) - for (int i = 0; i < n_p; i++) { - for (int j = i + 1; j < n_p; j++) { - // 1 FLOP = 1 ADD - double dx = pedestrians[i].curr_pos_x - pedestrians[j].curr_pos_x; - // 1 FLOP = 1 ADD - double dy = pedestrians[i].curr_pos_y - pedestrians[j].curr_pos_y; - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double norm = sqrt(dx * dx + dy * dy); - r_ab_x_lut[i * n_p + j] = dx; - r_ab_y_lut[i * n_p + j] = dy; - r_ab_norm_lut[i * n_p + j] = norm; - } - } - - // FLOPCOUNT = n_p * [ (8 FLOPS) + ((n_p - 1) * 66 FLOPS) + (n_obstacles * 63 FLOPS) ] - // n_p * [(4 MULT + 4 ADD) + ((n_p - 1) * (11 ADD + 22 MULT + 1 DIV + 1 EXP + 2 SQRT)) + (n_obstacles * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP))] - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // 1 FLOP = 1 MULT - double desired_velocity_x = p.e_x * p.desired_v0; - // 1 FLOP = 1 MULT - double desired_velocity_y = p.e_y * p.desired_v0; - - // 2 FLOPS = 1 ADD + 1 MULT - double f_alpha_dt_x = (desired_velocity_x - p.curr_vel_x) * dt_times_tau_alpha_inv; - // 2 FLOPS = 1 ADD + 1 MULT - double f_alpha_dt_y = (desired_velocity_y - p.curr_vel_y) * dt_times_tau_alpha_inv; - - // force between pedestrians - // FLOPS = ((n_p - 1) * 66 FLOPS) - for (int j = 0; j < n_p; j++) { - if (i == j) - continue; - - // symmetry - //double r_ab_x = p.curr_pos_x - pedestrians[j].curr_pos_x; - //double r_ab_y = p.curr_pos_y - pedestrians[j].curr_pos_y; - //double r_ab_norm = sqrt(r_ab_x * r_ab_x + r_ab_y * r_ab_y); - double r_ab_x, r_ab_y, r_ab_norm; - if (i < j) { - r_ab_x = r_ab_x_lut[i * n_p + j]; - r_ab_y = r_ab_y_lut[i * n_p + j]; - r_ab_norm = r_ab_norm_lut[i * n_p + j]; - } else { - // other direction - r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - r_ab_norm = r_ab_norm_lut[j * n_p + i]; - } - - // ---- - - // 1 FLOP = 1 ADD - double r_ab2_x = r_ab_x - pedestrians[j].s_b_e_x; - // 1 FLOP = 1 ADD - double r_ab2_y = r_ab_y - pedestrians[j].s_b_e_y; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - - // 1 FLOP = 1 ADD - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // 2 FLOPS = 1 MULT + 1 ADD - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - pedestrians[j].s_b_sq; - - // 1 FLOP = 1 SQRT - double two_b = sqrt(two_b_sq); - - // 36 FLOPS = 1 DIV + 5 MULTS + 1 EXP - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - - // 1 FLOP = 1 MULT - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - - // 1 FLOP = 1 MULT - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - double w_val; - // 4 FLOPS = 3 MULT + 1 ADD - double a = (p.e_x * f_alpha_beta_neg_x + p.e_y * f_alpha_beta_neg_y) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - - w_val = dt_neg; - // 1 FLOP = 1 MULT - if (a >= 0 && (a * a > b_sq)) { - w_val = c_neg_times_dt; - } - - // 2 FLOPS = 1 ADD + 1 MULT - f_alpha_dt_x = f_alpha_dt_x + f_alpha_beta_neg_x * w_val; - // 2 FLOPS = 1 ADD + 1 MULT - f_alpha_dt_y = f_alpha_dt_y + f_alpha_beta_neg_y * w_val; - } - - // force between pedestrian and obstacles - // FLOPS = n_obstacles * 63 FLOPS - for (int k = 0; k < board.n_obstacles; k++) { - - // 1 FLOP = 1 ADD - double v1_x = p.curr_pos_x - board.obstacles[k].start.x; - // 1 FLOP = 1 ADD - double v1_y = p.curr_pos_y - board.obstacles[k].start.y; - - // 4 FLOPS = 3 MULT + 1 ADD - double projection = (v1_x * board.obstacles[k].obstacle_v.x + v1_y * board.obstacles[k].obstacle_v.y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - // 3 FLOPS = 1 MULT + 2 ADD - double r_aB_x = p.curr_pos_x - (board.obstacles[k].start.x + board.obstacles[k].obstacle_v.x * projection); - // 3 FLOPS = 1 MULT + 2 ADD - double r_aB_y = p.curr_pos_y - (board.obstacles[k].start.y + board.obstacles[k].obstacle_v.y * projection); - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - // 33 FLOPS = 2 MULT + 1 DIV + 1 EXP - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - // 1 FLOP = 1 MULT - double f_alpha_B_neg_x = r_aB_x * scalar_mult; - // 1 FLOP = 1 MULT - double f_alpha_B_neg_y = r_aB_y * scalar_mult; - - double w_val; - // 4 FLOPS = 3 MULTS + 1 ADD - double a = (p.e_x * f_alpha_B_neg_x + p.e_y * f_alpha_B_neg_y) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq = f_alpha_B_neg_x * f_alpha_B_neg_x + f_alpha_B_neg_y * f_alpha_B_neg_y; - - w_val = dt_neg; - // 1 FLOP = 1 MULT - if (a >= 0 && (a * a > b_sq)) { - w_val = c_neg_times_dt; - } - - // 1 FLOP = 1 MULT - double f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - // 1 FLOP = 1 MULT - double f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // 1 FLOP = 1 ADD - f_alpha_dt_x += f_alpha_B_dt_x; - // 1 FLOP = 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // 1 FLOP = 1 ADD - p.w_x += f_alpha_dt_x; - // 1 FLOP = 1 ADD - p.w_y += f_alpha_dt_y; - } - - // update the positions etc. - // FLOPCOUNT = n_p * 29 FLOPS - // n_p * [ 7 ADD + 17 MULT + 3 SQRT + 2 DIV ] - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double w_norm = sqrt(p.w_x * p.w_x + p.w_y * p.w_y); - - // TODO: check correctness of this - // 1 FLOP = 1 DIV - double correction_factor = min(1.0, p.v_max / w_norm); - - // 1 FLOP = 1 MULT - p.curr_vel_x = p.w_x * correction_factor; - // 1 FLOP = 1 MULT - p.curr_vel_y = p.w_y * correction_factor; - - // 2 FLOPS = 1 ADD + 1 MULT - p.curr_pos_x = p.curr_pos_x + p.curr_vel_x * dt; - // 2 FLOPS = 1 ADD + 1 MULT - p.curr_pos_y = p.curr_pos_y + p.curr_vel_y * dt; - - if (p.color == 0 && p.curr_pos_x > (BOARD_LENGTH - 1.5)) { - p.curr_pos_x = 1.0; - p.destination_y = p.curr_pos_y; - p.w_x = p.desired_v0; - p.w_y = 0.0; - p.curr_vel_x = p.w_x; - p.curr_vel_y = p.w_y; - } - if (p.color == 1 && p.curr_pos_x < 1.5) { - p.curr_pos_x = BOARD_LENGTH - 1.0; - p.destination_y = p.curr_pos_y; - p.w_x = -p.desired_v0; - p.w_y = 0.0; - p.curr_vel_x = p.w_x; - p.curr_vel_y = p.w_y; - } - - p.curr_pos_y = clamp(p.curr_pos_y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // 1 FLOP = 1 ADD - double e_x = p.destination_x - p.curr_pos_x; - // 1 FLOP = 1 ADD - double e_y = p.destination_y - p.curr_pos_y; - - // 5 FLOPS = 1 DIV + 1 SQRT + 2 MULT + 1 ADD - double e_norm_inv = 1.0 / sqrt(e_x * e_x + e_y * e_y); - - // 1 FLOP = 1 MULT - p.e_x = e_x * e_norm_inv; - // 1 FLOP = 1 MULT - p.e_y = e_y * e_norm_inv; - - // 3 FLOPS = 2 MULT + 1 ADD - double velocity_norm_sq = p.curr_vel_x * p.curr_vel_x + p.curr_vel_y * p.curr_vel_y; - - // 1 FLOP = 1 SQRT - double velocity_norm = sqrt(velocity_norm_sq); - - // 1 FLOP = 1 MULT - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - // 2 FLOPS = 2 MULT - p.s_b_e_x = p.e_x * (velocity_norm * delta_t); - // 2 FLOP = 2 MULT - p.s_b_e_y = p.e_y * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos_x, p.curr_pos_y, p.e_x, p.e_y, p.color); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_inlining_7_symmetry_2_flopcount.cpp b/variants/variant_inlining_7_symmetry_2_flopcount.cpp deleted file mode 100644 index a6740c9..0000000 --- a/variants/variant_inlining_7_symmetry_2_flopcount.cpp +++ /dev/null @@ -1,385 +0,0 @@ -// # Use symmetrical properties to reduce flop count -// # Changes: flopcount -// FLOPCOUNT = n_iter * ( 69 * n_p ** 2 - 32 * n_p + 63 * n_obstacles * n_p ) -// n_iter * [ n_p ** 2 * (12.5 ADD + 23 MULT + 1 DIV + 1 EXP + 2.5 SQRT) + n_p * (- 1.5 ADD + 0.5 SQRT - 2 MULT - 1 EXP + 1 DIV) + n_obstacles * n_p * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) ] -// # Predecessor: variant_inlining_7_symmetry_2.cpp - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian_reduced.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - double start_pos_x = x; - double start_pos_y = y; - - double destination_x = BOARD_LENGTH + 30.0; - double destination_y = y; - - double init_e_x = destination_x - start_pos_x; - double init_e_y = destination_y - start_pos_y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(0, 0, 0, start_pos_x, start_pos_y, init_e_unit_x, init_e_unit_y, v_max, desired_v0, destination_x, destination_y); - } - - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - double start_pos_x = x; - double start_pos_y = y; - double destination_x = -30; - double destination_y = y; - - double init_e_x = destination_x - start_pos_x; - double init_e_y = destination_y - start_pos_y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(1, 0, 0, start_pos_x, start_pos_y, init_e_unit_x, init_e_unit_y, v_max, desired_v0, destination_x, destination_y); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos_x, p.curr_pos_y, p.e_x, p.e_y, p.color); - } - } -#endif - - // here we have real (simple) symmetry - std::vector r_ab_x_lut(n_p * n_p, 0.0); - std::vector r_ab_y_lut(n_p * n_p, 0.0); - std::vector r_ab_norm_lut(n_p * n_p, 0.0); - // use NAME[i * n_p + j] - - // FLOPCOUNT = n_iter * ( {0.5 * (n_p * n_p - n_p) * 6 FLOPS} + {n_p * [ (8 FLOPS) + ((n_p - 1) * 66 FLOPS) + (n_obstacles * 63 FLOPS) ]} + {n_p * 29 FLOPS} ) - // FLOPCOUNT = n_iter * ( {3 * n_p ** 2 - 3 * n_p} + {66 * n_p ** 2 - 58 * n_p + 63 * n_obstacles * n_p} + {29 * n_p} ) - // FLOPCOUNT = n_iter * ( 3 * n_p ** 2 - 3 * n_p + 66 * n_p ** 2 - 58 * n_p + 63 * n_obstacles * n_p + 29 * n_p) - // FLOPCOUNT = n_iter * ( [3 * n_p ** 2 + 66 * n_p ** 2] + [- 3 * n_p - 58 * n_p + 29 * n_p] + [63 * n_obstacles * n_p] ) - // FLOPCOUNT = n_iter * ( 69 * n_p ** 2 - 32 * n_p + 63 * n_obstacles * n_p ) - for (int t = 0; t < n_iter; t++) { - - // get all distances first - // FLOPCOUNT = 0.5 * (n_p * n_p - n_p) * 6 FLOPS - for (int i = 0; i < n_p; i++) { - for (int j = i + 1; j < n_p; j++) { - // 1 FLOP = 1 ADD - double dx = pedestrians[i].curr_pos_x - pedestrians[j].curr_pos_x; - // 1 FLOP = 1 ADD - double dy = pedestrians[i].curr_pos_y - pedestrians[j].curr_pos_y; - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double norm = sqrt(dx * dx + dy * dy); - int idx = i * n_p + j; - r_ab_x_lut[idx] = dx; - r_ab_y_lut[idx] = dy; - r_ab_norm_lut[idx] = norm; - } - } - - // FLOPCOUNT = n_p * [ 8 FLOPS + (((n_p - 1) * 66 FLOPS)) + (n_obstacles * 63 FLOPS) ] - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // 1 FLOP = 1 MULT - double desired_velocity_x = p.e_x * p.desired_v0; - // 1 FLOP = 1 MULT - double desired_velocity_y = p.e_y * p.desired_v0; - - // 2 FLOPS = 1 ADD + 1 MULT - double f_alpha_dt_x = (desired_velocity_x - p.curr_vel_x) * dt_times_tau_alpha_inv; - // 2 FLOPS = 1 ADD + 1 MULT - double f_alpha_dt_y = (desired_velocity_y - p.curr_vel_y) * dt_times_tau_alpha_inv; - - // force between pedestrians --> no symmetry because of different accellerations - // FLOPCOUNT = (((n_p - 1) * 66 FLOPS)) - for (int j = 0; j < n_p; j++) { - if (i == j) - continue; - - // symmetry - double r_ab_x, r_ab_y, r_ab_norm; - if (i < j) { - r_ab_x = r_ab_x_lut[i * n_p + j]; - r_ab_y = r_ab_y_lut[i * n_p + j]; - r_ab_norm = r_ab_norm_lut[i * n_p + j]; - } else { - // other direction - r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - r_ab_norm = r_ab_norm_lut[j * n_p + i]; - } - - // ---- - - // 1 FLOP = 1 ADD - double r_ab2_x = r_ab_x - pedestrians[j].s_b_e_x; - // 1 FLOP = 1 ADD - double r_ab2_y = r_ab_y - pedestrians[j].s_b_e_y; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - // 1 FLOP = 1 ADD - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - // 2 FLOPS = 1 MULT + 1 ADD - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - pedestrians[j].s_b_sq; - // 1 FLOP = 1 SQRT - double two_b = sqrt(two_b_sq); - // 36 FLOPS = 1 DIV + 5 MULT + 1 EXP - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // 1 FLOP = 1 MULT - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // 1 FLOP = 1 MULT - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - double w_val; - // 4 FLOPS = 3 MULT + 1 ADD - double a = (p.e_x * f_alpha_beta_neg_x + p.e_y * f_alpha_beta_neg_y) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - - w_val = dt_neg; - // 1 FLOP = 1 MULT - if (a >= 0 && (a * a > b_sq)) { - w_val = c_neg_times_dt; - } - - // 2 FLOPS = 1 ADD + 1 MULT - f_alpha_dt_x = f_alpha_dt_x + f_alpha_beta_neg_x * w_val; - // 2 FLOPS = 1 ADD + 1 MULT - f_alpha_dt_y = f_alpha_dt_y + f_alpha_beta_neg_y * w_val; - } - - // force between pedestrian and obstacles - // FLOPCOUNT = n_obstacles * 63 FLOPS - for (int k = 0; k < board.n_obstacles; k++) { - - // 1 FLOP = 1 ADD - double v1_x = p.curr_pos_x - board.obstacles[k].start.x; - // 1 FLOP = 1 ADD - double v1_y = p.curr_pos_y - board.obstacles[k].start.y; - - // 4 FLOPS = 3 MULT + 1 ADD - double projection = (v1_x * board.obstacles[k].obstacle_v.x + v1_y * board.obstacles[k].obstacle_v.y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - // 3 FLOPS = 2 ADD + 1 MULT - double r_aB_x = p.curr_pos_x - (board.obstacles[k].start.x + board.obstacles[k].obstacle_v.x * projection); - // 3 FLOPS = 2 ADD + 1 MULT - double r_aB_y = p.curr_pos_y - (board.obstacles[k].start.y + board.obstacles[k].obstacle_v.y * projection); - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - // 33 FLOPS = 1 EXP + 2 MULT + 1 DIV - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - // 1 FLOP = 1 MULT - double f_alpha_B_neg_x = r_aB_x * scalar_mult; - // 1 FLOP = 1 MULT - double f_alpha_B_neg_y = r_aB_y * scalar_mult; - - double w_val; - // 4 FLOPS = 3 MULT + 1 ADD - double a = (p.e_x * f_alpha_B_neg_x + p.e_y * f_alpha_B_neg_y) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq = f_alpha_B_neg_x * f_alpha_B_neg_x + f_alpha_B_neg_y * f_alpha_B_neg_y; - - w_val = dt_neg; - // 1 FLOP = 1 MULT - if (a >= 0 && (a * a > b_sq)) { - w_val = c_neg_times_dt; - } - - // 1 FLOP = 1 MULT - double f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - // 1 FLOP = 1 MULT - double f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // 1 FLOP = 1 ADD - f_alpha_dt_x += f_alpha_B_dt_x; - // 1 FLOP = 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // 1 FLOP = 1 ADD - p.w_x += f_alpha_dt_x; - // 1 FLOP = 1 ADD - p.w_y += f_alpha_dt_y; - } - - // update the positions etc. - // FLOPCOUNT = n_p * 29 FLOPS - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double w_norm = sqrt(p.w_x * p.w_x + p.w_y * p.w_y); - - // 1 FLOP = 1 DIV - double correction_factor = min(1.0, p.v_max / w_norm); - - // 1 FLOP = 1 MULT - p.curr_vel_x = p.w_x * correction_factor; - // 1 FLOP = 1 MULT - p.curr_vel_y = p.w_y * correction_factor; - - // 2 FLOPS = 1 MULT + 1 ADD - p.curr_pos_x = p.curr_pos_x + p.curr_vel_x * dt; - // 2 FLOPS = 1 MULT + 1 ADD - p.curr_pos_y = p.curr_pos_y + p.curr_vel_y * dt; - - if (p.color == 0 && p.curr_pos_x > (BOARD_LENGTH - 1.5)) { - p.curr_pos_x = 1.0; - p.destination_y = p.curr_pos_y; - p.w_x = p.desired_v0; - p.w_y = 0.0; - p.curr_vel_x = p.w_x; - p.curr_vel_y = p.w_y; - } - if (p.color == 1 && p.curr_pos_x < 1.5) { - p.curr_pos_x = BOARD_LENGTH - 1.0; - p.destination_y = p.curr_pos_y; - p.w_x = -p.desired_v0; - p.w_y = 0.0; - p.curr_vel_x = p.w_x; - p.curr_vel_y = p.w_y; - } - - p.curr_pos_y = clamp(p.curr_pos_y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // 1 FLOP = 1 ADD - double e_x = p.destination_x - p.curr_pos_x; - // 1 FLOP = 1 ADD - double e_y = p.destination_y - p.curr_pos_y; - - // 5 FLOPS = 1 DIV + 1 SQRT + 2 MULT + 1 ADD - double e_norm_inv = 1.0 / sqrt(e_x * e_x + e_y * e_y); - - // 1 FLOP = 1 MULT - p.e_x = e_x * e_norm_inv; - // 1 FLOP = 1 MULT - p.e_y = e_y * e_norm_inv; - - // 3 FLOPS = 2 MULT + 1 ADD - double velocity_norm_sq = p.curr_vel_x * p.curr_vel_x + p.curr_vel_y * p.curr_vel_y; - - // 1 FLOP = 1 SQRT - double velocity_norm = sqrt(velocity_norm_sq); - - // 1 FLOP = 1 MULT - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - // 2 FLOPS = 2 MULT - p.s_b_e_x = p.e_x * (velocity_norm * delta_t); - // 2 FLOPS = 2 MULT - p.s_b_e_y = p.e_y * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos_x, p.curr_pos_y, p.e_x, p.e_y, p.color); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_inlining_7_symmetry_2_ilp_1_flopcount.cpp b/variants/variant_inlining_7_symmetry_2_ilp_1_flopcount.cpp deleted file mode 100644 index 2e85f45..0000000 --- a/variants/variant_inlining_7_symmetry_2_ilp_1_flopcount.cpp +++ /dev/null @@ -1,633 +0,0 @@ -// # ILP -// # Changes: FLOPCOUNT -// n_iter * [ n_p ** 2 * (12.5 A + 2.5 S + 23 M + 1 D + 1 E) + n_p * (6.5 A + 0.5 S - 2 M + 1 D - 1 E) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E)] -// # Predecessor: variant_inlining_7_symmetry_2_ilp.cpp - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian_reduced.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - double start_pos_x = x; - double start_pos_y = y; - - double destination_x = BOARD_LENGTH + 30.0; - double destination_y = y; - - double init_e_x = destination_x - start_pos_x; - double init_e_y = destination_y - start_pos_y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(0, 0, 0, start_pos_x, start_pos_y, init_e_unit_x, init_e_unit_y, v_max, desired_v0, destination_x, destination_y); - } - - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - double start_pos_x = x; - double start_pos_y = y; - double destination_x = -30; - double destination_y = y; - - double init_e_x = destination_x - start_pos_x; - double init_e_y = destination_y - start_pos_y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(1, 0, 0, start_pos_x, start_pos_y, init_e_unit_x, init_e_unit_y, v_max, desired_v0, destination_x, destination_y); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos_x, p.curr_pos_y, p.e_x, p.e_y, p.color); - } - } -#endif - - // here we have real (simple) symmetry - std::vector r_ab_x_lut(n_p * n_p, 0.0); - std::vector r_ab_y_lut(n_p * n_p, 0.0); - std::vector r_ab_norm_lut(n_p * n_p, 0.0); - // use NAME[i * n_p + j] - - - - // n_iter * [ 0.5 * (n_p * n_p - n_p) * (3 ADD + 1 SQRT + 2 MULT) + n_p * [ (12 ADD + 4 MULT) + ((n_p - 1) * (11 ADD + 22 MULT + 2 SQRT + 1 DIV + 1 EXP)) ] + n_p * (7 ADD + 17 MULT + 3 SQRT + 2 DIV) + n_obstacles * n_p * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP)] - // n_iter * [ 0.5 * n_p ** 2 * (3 A + 1 S + 2 M) - 0.5 * n_p * (3 A + 1 S + 2 M) + n_p ** 2 * (11 A + 22 M + 2 S + 1 D + 1 E) + n_p * (1 A - 18 M - 2 S - 1 D - 1 E) + n_p * (7 A + 17 M + 3 S + 2 D) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E)] - // n_iter * [ n_p ** 2 * (1.5 A + 0.5 S + 1 M + 11 A + 22 M + 2 S + 1 D + 1 E) + n_p * (- 1.5 A - 0.5 S - 1 M + 1 A - 18 M - 2 S - 1 D - 1 E + 7 A + 17 M + 3 S + 2 D) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E)] - // n_iter * [ n_p ** 2 * (12.5 A + 2.5 S + 23 M + 1 D + 1 E) + n_p * (6.5 A + 0.5 S - 2 M + 1 D - 1 E) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E)] - for (int t = 0; t < n_iter; t++) { - - // get all distances first - // No ILP because no accumulators - // FLOPCOUNT = 0.5 * (n_p * n_p - n_p) * 6 FLOPS - // 0.5 * (n_p * n_p - n_p) * (3 ADD + 1 SQRT + 2 MULT) - for (int i = 0; i < n_p; i++) { - // only upper triangle - for (int j = i + 1; j < n_p; j++) { - // 1 FLOP = 1 ADD - double dx = pedestrians[i].curr_pos_x - pedestrians[j].curr_pos_x; - // 1 FLOP = 1 ADD - double dy = pedestrians[i].curr_pos_y - pedestrians[j].curr_pos_y; - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double norm = sqrt(dx * dx + dy * dy); - r_ab_x_lut[i * n_p + j] = dx; - r_ab_y_lut[i * n_p + j] = dy; - r_ab_norm_lut[i * n_p + j] = norm; - } - } - - - // FLOPCOUNT = n_p * [ 16 FLOPS + ((n_p - 1) * 66 FLOPS) + (n_obstacles * 63 FLOPS)] - // n_p * [ (12 ADD + 4 MULT) + ((n_p - 1) * (11 ADD + 22 MULT + 2 SQRT + 1 DIV + 1 EXP)) + n_obstacles * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) ] - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // 1 FLOP = 1 MULT - double desired_velocity_x = p.e_x * p.desired_v0; - // 1 FLOP = 1 MULT - double desired_velocity_y = p.e_y * p.desired_v0; - - // 2 FLOPS = 1 ADD + 1 MULT - double f_alpha_dt_x = (desired_velocity_x - p.curr_vel_x) * dt_times_tau_alpha_inv; - // 2 FLOPS = 1 ADD + 1 MULT - double f_alpha_dt_y = (desired_velocity_y - p.curr_vel_y) * dt_times_tau_alpha_inv; - - // accumulators for ILP - // f_alpha_dt_x = f_alpha_dt_x + f_alpha_beta_neg_x * w_val; - // f_alpha_dt_y = f_alpha_dt_y + f_alpha_beta_neg_y * w_val; - double acc_0_x = 0.0, acc_1_x = 0.0, acc_2_x = 0.0, acc_3_x = 0.0; - double acc_0_y = 0.0, acc_1_y = 0.0, acc_2_y = 0.0, acc_3_y = 0.0; - - const double p_e_x = p.e_x; - const double p_e_y = p.e_y; - - - // force between pedestrians --> no symmetry because of different accelerations - // ILP - int j = 0; - // FLOPCOUNT = ((n_p - 1) * 66 FLOPS) - // ((n_p - 1) * (11 ADD + 22 MULT + 2 SQRT + 1 DIV + 1 EXP)) - for (; j <= n_p - 4; j+=4) { - // distance symmetry - double r_ab_x_0, r_ab_y_0, r_ab_norm_0; - double r_ab_x_1, r_ab_y_1, r_ab_norm_1; - double r_ab_x_2, r_ab_y_2, r_ab_norm_2; - double r_ab_x_3, r_ab_y_3, r_ab_norm_3; - - int j0 = j, j1 = j+1, j2 = j+2, j3 = j+3; - - // skip i == j - // FLOPCOUNT = 66 FLOPS - // 11 ADD + 22 MULT + 2 SQRT + 1 DIV + 1 EXP - if (i != j0) { - if (i < j0) { - r_ab_x_0 = r_ab_x_lut[i * n_p + j0]; - r_ab_y_0 = r_ab_y_lut[i * n_p + j0]; - r_ab_norm_0 = r_ab_norm_lut[i * n_p + j0]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_0 = -1 * r_ab_x_lut[j0 * n_p + i]; - r_ab_y_0 = -1 * r_ab_y_lut[j0 * n_p + i]; - r_ab_norm_0 = r_ab_norm_lut[j0 * n_p + i]; - } - // the math, see paper - // 1 FLOP = 1 ADD - double r_ab2_x_0 = r_ab_x_0 - pedestrians[j0].s_b_e_x; - // 1 FLOP = 1 ADD - double r_ab2_y_0 = r_ab_y_0 - pedestrians[j0].s_b_e_y; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - - // 1 FLOP = 1 ADD - double r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - - // 2 FLOPS = 1 MULT + 1 ADD - double two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - pedestrians[j0].s_b_sq; - - // 1 FLOP = 1 SQRT - double two_b_0 = sqrt(two_b_sq_0); - - // 36 FLOPS = 1 DIV + 5 MULT + 1 EXP - double scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - - // 1 FLOP = 1 MULT - double scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - - // 1 FLOP = 1 MULT - double scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - - double w_val_0; - // 4 FLOPS = 3 MULT + 1 ADD - double a_0 = (p_e_x * f_alpha_beta_neg_x_0 + p_e_y * f_alpha_beta_neg_y_0) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - - w_val_0 = dt_neg; - // 1 FLOP = 1 MULT - if (a_0 >= 0 && (a_0 * a_0 > b_sq_0)) { - w_val_0 = c_neg_times_dt; - } - - // 2 FLOPS = 1 ADD + 1 MULT - acc_0_x += f_alpha_beta_neg_x_0 * w_val_0; - // 2 FLOPS = 1 ADD + 1 MULT - acc_0_y += f_alpha_beta_neg_y_0 * w_val_0; - } - - // skip i == j - // FLOPCOUNT= 66 FLOPS - if (i != j1) { - if (i < j1) { - r_ab_x_1 = r_ab_x_lut[i * n_p + j1]; - r_ab_y_1 = r_ab_y_lut[i * n_p + j1]; - r_ab_norm_1 = r_ab_norm_lut[i * n_p + j1]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_1 = -1 * r_ab_x_lut[j1 * n_p + i]; - r_ab_y_1 = -1 * r_ab_y_lut[j1 * n_p + i]; - r_ab_norm_1 = r_ab_norm_lut[j1 * n_p + i]; - } - // the math, see paper - // 1 FLOP = 1 ADD - double r_ab2_x_1 = r_ab_x_1 - pedestrians[j1].s_b_e_x; - // 1 FLOP = 1 ADD - double r_ab2_y_1 = r_ab_y_1 - pedestrians[j1].s_b_e_y; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - - // 1 FLOP = 1 ADD - double r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - - // 2 FLOPS = 1 MULT + 1 ADD - double two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - pedestrians[j1].s_b_sq; - - // 1 FLOP = 1 SQRT - double two_b_1 = sqrt(two_b_sq_1); - - // 36 FLOPS = 1 DIV + 5 MULT + 1 EXP - double scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - double scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - - double w_val_1; - // 4 FLOPS = 3 MULT + 1 ADD - double a_1 = (p_e_x * f_alpha_beta_neg_x_1 + p_e_y * f_alpha_beta_neg_y_1) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - - w_val_1 = dt_neg; - // 1 FLOP = 1 MULT - if (a_1 >= 0 && (a_1 * a_1 > b_sq_1)) { - w_val_1 = c_neg_times_dt; - } - - // 2 FLOPS = 1 MULT + 1 ADD - acc_1_x += f_alpha_beta_neg_x_1 * w_val_1; - // 2 FLOPS = 1 MULT + 1 ADD - acc_1_y += f_alpha_beta_neg_y_1 * w_val_1; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j2) { - if (i < j2) { - r_ab_x_2 = r_ab_x_lut[i * n_p + j2]; - r_ab_y_2 = r_ab_y_lut[i * n_p + j2]; - r_ab_norm_2 = r_ab_norm_lut[i * n_p + j2]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_2 = -1 * r_ab_x_lut[j2 * n_p + i]; - r_ab_y_2 = -1 * r_ab_y_lut[j2 * n_p + i]; - r_ab_norm_2 = r_ab_norm_lut[j2 * n_p + i]; - } - // the math, see paper - double r_ab2_x_2 = r_ab_x_2 - pedestrians[j2].s_b_e_x; - double r_ab2_y_2 = r_ab_y_2 - pedestrians[j2].s_b_e_y; - - double r_ab2_norm_2 = sqrt(r_ab2_x_2 * r_ab2_x_2 + r_ab2_y_2 * r_ab2_y_2); - double r_ab_norm_sum_2 = r_ab_norm_2 + r_ab2_norm_2; - double two_b_sq_2 = r_ab_norm_sum_2 * r_ab_norm_sum_2 - pedestrians[j2].s_b_sq; - double two_b_2 = sqrt(two_b_sq_2); - double scalar_mult_common_2 = (r_ab_norm_sum_2 / (two_b_2 * r_ab_norm_2 * r_ab2_norm_2)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_2 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_2 = scalar_mult_common_2 * r_ab2_norm_2; - double scalar_mult_r_ab2_2 = scalar_mult_common_2 * r_ab_norm_2; - - double f_alpha_beta_neg_x_2 = r_ab_x_2 * scalar_mult_r_ab_2 + r_ab2_x_2 * scalar_mult_r_ab2_2; - double f_alpha_beta_neg_y_2 = r_ab_y_2 * scalar_mult_r_ab_2 + r_ab2_y_2 * scalar_mult_r_ab2_2; - - double w_val_2; - double a_2 = (p_e_x * f_alpha_beta_neg_x_2 + p_e_y * f_alpha_beta_neg_y_2) * cos_phi_inv; - double b_sq_2 = f_alpha_beta_neg_x_2 * f_alpha_beta_neg_x_2 + f_alpha_beta_neg_y_2 * f_alpha_beta_neg_y_2; - - w_val_2 = dt_neg; - if (a_2 >= 0 && (a_2 * a_2 > b_sq_2)) { - w_val_2 = c_neg_times_dt; - } - - acc_2_x += f_alpha_beta_neg_x_2 * w_val_2; - acc_2_y += f_alpha_beta_neg_y_2 * w_val_2; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j3) { - if (i < j3) { - r_ab_x_3 = r_ab_x_lut[i * n_p + j3]; - r_ab_y_3 = r_ab_y_lut[i * n_p + j3]; - r_ab_norm_3 = r_ab_norm_lut[i * n_p + j3]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_3 = -1 * r_ab_x_lut[j3 * n_p + i]; - r_ab_y_3 = -1 * r_ab_y_lut[j3 * n_p + i]; - r_ab_norm_3 = r_ab_norm_lut[j3 * n_p + i]; - } - // the math, see paper - double r_ab2_x_3 = r_ab_x_3 - pedestrians[j3].s_b_e_x; - double r_ab2_y_3 = r_ab_y_3 - pedestrians[j3].s_b_e_y; - - double r_ab2_norm_3 = sqrt(r_ab2_x_3 * r_ab2_x_3 + r_ab2_y_3 * r_ab2_y_3); - double r_ab_norm_sum_3 = r_ab_norm_3 + r_ab2_norm_3; - double two_b_sq_3 = r_ab_norm_sum_3 * r_ab_norm_sum_3 - pedestrians[j3].s_b_sq; - double two_b_3 = sqrt(two_b_sq_3); - double scalar_mult_common_3 = (r_ab_norm_sum_3 / (two_b_3 * r_ab_norm_3 * r_ab2_norm_3)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_3 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_3 = scalar_mult_common_3 * r_ab2_norm_3; - double scalar_mult_r_ab2_3 = scalar_mult_common_3 * r_ab_norm_3; - - double f_alpha_beta_neg_x_3 = r_ab_x_3 * scalar_mult_r_ab_3 + r_ab2_x_3 * scalar_mult_r_ab2_3; - double f_alpha_beta_neg_y_3 = r_ab_y_3 * scalar_mult_r_ab_3 + r_ab2_y_3 * scalar_mult_r_ab2_3; - - double w_val_3; - double a_3 = (p_e_x * f_alpha_beta_neg_x_3 + p_e_y * f_alpha_beta_neg_y_3) * cos_phi_inv; - double b_sq_3 = f_alpha_beta_neg_x_3 * f_alpha_beta_neg_x_3 + f_alpha_beta_neg_y_3 * f_alpha_beta_neg_y_3; - - w_val_3 = dt_neg; - if (a_3 >= 0 && (a_3 * a_3 > b_sq_3)) { - w_val_3 = c_neg_times_dt; - } - - acc_3_x += f_alpha_beta_neg_x_3 * w_val_3; - acc_3_y += f_alpha_beta_neg_y_3 * w_val_3; - } - } - - // add the result - // 4 FLOPS = 4 ADD - f_alpha_dt_x += acc_0_x + acc_1_x + acc_2_x + acc_3_x; - // 4 FLOPS = 4 ADD - f_alpha_dt_y += acc_0_y + acc_1_y + acc_2_y + acc_3_y; - - // remaining - // 66 FLOPS --> see ILP loop - // 11 ADD + 22 MULT + 2 SQRT + 1 DIV + 1 EXP - for (; j < n_p; j++) { - if (i == j) continue; - - double r_ab_x, r_ab_y, r_ab_norm; - if (i < j) { - r_ab_x = r_ab_x_lut[i * n_p + j]; - r_ab_y = r_ab_y_lut[i * n_p + j]; - r_ab_norm = r_ab_norm_lut[i * n_p + j]; - } else { - r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - r_ab_norm = r_ab_norm_lut[j * n_p + i]; - } - - // 1 FLOP = 1 ADD - double r_ab2_x = r_ab_x - pedestrians[j].s_b_e_x; - // 1 FLOP = 1 ADD - double r_ab2_y = r_ab_y - pedestrians[j].s_b_e_y; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - // 1 FLOP = 1 ADD - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - // 2 FLOPS = 1 MULT + 1 ADD - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - pedestrians[j].s_b_sq; - // 1 FLOP = 1 SQRT - double two_b = sqrt(two_b_sq); - // 36 FLOPS = 1 DIV + 5 MULT + 1 EXP - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // 1 FLOP = 1 MULT - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // 1 FLOP = 1 MULT - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - double w_val; - // 4 FLOPS = 3 MULT + 1 ADD - double a = (p_e_x * f_alpha_beta_neg_x + p_e_y * f_alpha_beta_neg_y) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - - w_val = dt_neg; - // 1 FLOP = 1 MULT - if (a >= 0 && (a * a > b_sq)) { - w_val = c_neg_times_dt; - } - - // 2 FLOPS = 1 MULT + 1 ADD - f_alpha_dt_x = f_alpha_dt_x + f_alpha_beta_neg_x * w_val; - // 2 FLOPS = 1 MULT + 1 ADD - f_alpha_dt_y = f_alpha_dt_y + f_alpha_beta_neg_y * w_val; - } - - - // force between pedestrian and obstacles - // initialized with 0 obstacles, might consider deleting this? - // to sparse to use ILP - // FLOPCOUNT = n_obstacles * 63 FLOPS - // n_obstacles * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) - for (int k = 0; k < board.n_obstacles; k++) { - - // 1 FLOP = 1 ADD - double v1_x = p.curr_pos_x - board.obstacles[k].start.x; - // 1 FLOP = 1 ADD - double v1_y = p.curr_pos_y - board.obstacles[k].start.y; - - // 4 FLOPS = 3 MULT + 1 ADD - double projection = (v1_x * board.obstacles[k].obstacle_v.x + v1_y * board.obstacles[k].obstacle_v.y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - // 3 FLOPS = 1 MULT + 2 ADD - double r_aB_x = p.curr_pos_x - (board.obstacles[k].start.x + board.obstacles[k].obstacle_v.x * projection); - // 3 FLOPS = 1 MULT + 2 ADD - double r_aB_y = p.curr_pos_y - (board.obstacles[k].start.y + board.obstacles[k].obstacle_v.y * projection); - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - // 33 FLOPS = 1 DIV + 2 MULT + 1 EXPT - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - // 1 FLOP = 1 MULT - double f_alpha_B_neg_x = r_aB_x * scalar_mult; - // 1 FLOP = 1 MULT - double f_alpha_B_neg_y = r_aB_y * scalar_mult; - - double w_val; - // 4 FLOPS = 3 MULT + 1 ADD - double a = (p.e_x * f_alpha_B_neg_x + p.e_y * f_alpha_B_neg_y) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq = f_alpha_B_neg_x * f_alpha_B_neg_x + f_alpha_B_neg_y * f_alpha_B_neg_y; - - w_val = dt_neg; - // 1 FLOP = 1 MULT - if (a >= 0 && (a * a > b_sq)) { - w_val = c_neg_times_dt; - } - - // 1 FLOP = 1 MULT - double f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - // 1 FLOP = 1 MULT - double f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - // 1 FLOP = 1 ADD - f_alpha_dt_x += f_alpha_B_dt_x; - // 1 FLOP = 1 ADD - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // 1 FLOP = 1 ADD - p.w_x += f_alpha_dt_x; - // 1 FLOP = 1 ADD - p.w_y += f_alpha_dt_y; - } - - // update the positions etc. - // ILP is not possible I think - // FLOPCOUNT = n_p * 29 FLOPS - // n_p * (7 ADD + 17 MULT + 3 SQRT + 2 DIV) - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double w_norm = sqrt(p.w_x * p.w_x + p.w_y * p.w_y); - - // 1 FLOP = 1 DIV - double correction_factor = min(1.0, p.v_max / w_norm); - - // 1 FLOP = 1 MULT - p.curr_vel_x = p.w_x * correction_factor; - // 1 FLOP = 1 MULT - p.curr_vel_y = p.w_y * correction_factor; - - // maybe move this calculation to a separate loop to enable ILP - // 2 FLOPS = 1 MULT + 1 ADD - p.curr_pos_x = p.curr_pos_x + p.curr_vel_x * dt; - // 2 FLOPS = 1 MULT + 1 ADD - p.curr_pos_y = p.curr_pos_y + p.curr_vel_y * dt; - - if (p.color == 0 && p.curr_pos_x > (BOARD_LENGTH - 1.5)) { - p.curr_pos_x = 1.0; - p.destination_y = p.curr_pos_y; - p.w_x = p.desired_v0; - p.w_y = 0.0; - p.curr_vel_x = p.w_x; - p.curr_vel_y = p.w_y; - } - if (p.color == 1 && p.curr_pos_x < 1.5) { - p.curr_pos_x = BOARD_LENGTH - 1.0; - p.destination_y = p.curr_pos_y; - p.w_x = -p.desired_v0; - p.w_y = 0.0; - p.curr_vel_x = p.w_x; - p.curr_vel_y = p.w_y; - } - - p.curr_pos_y = clamp(p.curr_pos_y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // 1 FLOP = 1 ADD - double e_x = p.destination_x - p.curr_pos_x; - // 1 FLOP = 1 ADD - double e_y = p.destination_y - p.curr_pos_y; - - // 5 FLOPS = 1 DIV + 1 SQRT + 2 MULT + 1 ADD - double e_norm_inv = 1.0 / sqrt(e_x * e_x + e_y * e_y); - - // 1 FLOP = 1 MULT - p.e_x = e_x * e_norm_inv; - // 1 FLOP = 1 MULT - p.e_y = e_y * e_norm_inv; - - // 3 FLOPS = 2 MULT + 1 ADD - double velocity_norm_sq = p.curr_vel_x * p.curr_vel_x + p.curr_vel_y * p.curr_vel_y; - - // 1 FLOP = 1 SQRT - double velocity_norm = sqrt(velocity_norm_sq); - - // 1 FLOP = 1 MULT - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - // 2 FLOPS = 2 MULT - p.s_b_e_x = p.e_x * (velocity_norm * delta_t); - // 2 FLOPS = 2 MULT - p.s_b_e_y = p.e_y * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos_x, p.curr_pos_y, p.e_x, p.e_y, p.color); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_inlining_7_symmetry_2_ilp_2_flopcount.cpp b/variants/variant_inlining_7_symmetry_2_ilp_2_flopcount.cpp deleted file mode 100644 index 372c69c..0000000 --- a/variants/variant_inlining_7_symmetry_2_ilp_2_flopcount.cpp +++ /dev/null @@ -1,753 +0,0 @@ -// # ILP -// # Changes: flopcoun -// FLOPCOUNT = n_iter * [ 69 * n_p ** 2 - 16 * n_p + 63 * n_obstacles * n_p ] -// n_iter * [ n_p ** 2 * (12.5 A + 23 M + 2.5 S + 1 D + 1 E) + n_p * (14.5 A - 2 M + 0.5 S + 1 D - 1 E) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E) ] -// # Predecessor: variant_inlining_7_symmetry_2_ilp_2.cpp - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian_reduced.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; - mt19937 gen(rd()); -#else - mt19937 gen(SEED); -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - double start_pos_x = x; - double start_pos_y = y; - - double destination_x = BOARD_LENGTH + 30.0; - double destination_y = y; - - double init_e_x = destination_x - start_pos_x; - double init_e_y = destination_y - start_pos_y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(0, 0, 0, start_pos_x, start_pos_y, init_e_unit_x, init_e_unit_y, v_max, desired_v0, destination_x, destination_y); - } - - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - double start_pos_x = x; - double start_pos_y = y; - double destination_x = -30; - double destination_y = y; - - double init_e_x = destination_x - start_pos_x; - double init_e_y = destination_y - start_pos_y; - double init_e_norm = sqrt(init_e_x * init_e_x + init_e_y * init_e_y); - double init_e_unit_x = init_e_x / init_e_norm; - double init_e_unit_y = init_e_y / init_e_norm; - - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(1, 0, 0, start_pos_x, start_pos_y, init_e_unit_x, init_e_unit_y, v_max, desired_v0, destination_x, destination_y); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos_x, p.curr_pos_y, p.e_x, p.e_y, p.color); - } - } -#endif - - // here we have real (simple) symmetry - std::vector r_ab_x_lut(n_p * n_p, 0.0); - std::vector r_ab_y_lut(n_p * n_p, 0.0); - std::vector r_ab_norm_lut(n_p * n_p, 0.0); - // use NAME[i * n_p + j] - - // FLOPCOUNT = n_iter * [ {0.5 * (n_p * n_p - n_p) * 6 FLOPS} + {n_p * [(24 FLOPS) + ((n_p - 1) * 66 FLOPS) + (n_obstacles * 63 FLOPS) ]} + {n_p * 29 FLOPS} ] - // FLOPCOUNT = n_iter * [ {3 * (n_p * n_p - n_p)} + {n_p * [(24) + ((n_p - 1) * 66) + (n_obstacles * 63) ]} + {n_p * 29} ] - // FLOPCOUNT = n_iter * [ {3 * n_p ** 2 - 3 * n_p} + {66 * n_p ** 2 - 42 * n_p + 63 * n_obstacles * n_p} + {29 * n_p} ] - // FLOPCOUNT = n_iter * [ {3 * n_p ** 2 + 66 * n_p ** 2} + { - 3 * n_p - 42 * n_p + 29 * n_p} + {63 * n_obstacles * n_p} ] - // FLOPCOUNT = n_iter * [ 69 * n_p ** 2 - 16 * n_p + 63 * n_obstacles * n_p ] - - - // n_iter * [ {0.5 * (n_p * n_p - n_p) * (3 ADD + 2 MULT + 1 SQRT)} + {n_p * [ (20 ADD + 4 MULT) + ((n_p - 1) * (11 ADD + 22 MULT + 2 SQRT + 1 DIV + 1 EXP)) + n_obstacles * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) ]} + {n_p * (7 ADD + 17 MULT + 3 SQRT + 2 DIV)} ] - // n_iter * [ {0.5 * n_p ** 2 * (3 A + 2 M + 1 S) - 0.5 * n_p * (3 A + 2 M + 1 S)} + {n_p * [ (20 A + 4 M) + ((n_p - 1) * (11 A + 22 M + 2 S + 1 D + 1 E)) + n_obstacles * (12 A + 19 M + 1 S + 1 D + 1 E) ]} + {n_p * (7 A + 17 M + 3 S + 2 D)} ] - // n_iter * [ 0.5 * n_p ** 2 * (3 A + 2 M + 1 S) - 0.5 * n_p * (3 A + 2 M + 1 S) + {n_p * [ (20 A + 4 M) + n_p * (11 A + 22 M + 2 S + 1 D + 1 E) - 1 * (11 A + 22 M + 2 S + 1 D + 1 E) + n_obstacles * (12 A + 19 M + 1 S + 1 D + 1 E) ]} + {n_p * (7 A + 17 M + 3 S + 2 D)} ] - // n_iter * [ 0.5 * n_p ** 2 * (3 A + 2 M + 1 S) - 0.5 * n_p * (3 A + 2 M + 1 S) + n_p ** 2 * (11 A + 22 M + 2 S + 1 D + 1 E) + n_p * (9 A - 18 M - 2 S - 1 D - 1 E) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E) + n_p * (7 A + 17 M + 3 S + 2 D) ] - // n_iter * [ n_p ** 2 * (1.5 A + 1 M + 0.5 S + 11 A + 22 M + 2 S + 1 D + 1 E) + n_p * (- 1.5 A - 1 M - 0.5 S + 9 A - 18 M - 2 S - 1 D - 1 E + 7 A + 17 M + 3 S + 2 D) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E) ] - // n_iter * [ n_p ** 2 * (12.5 A + 23 M + 2.5 S + 1 D + 1 E) + n_p * (14.5 A - 2 M + 0.5 S + 1 D - 1 E) + n_obstacles * n_p * (12 A + 19 M + 1 S + 1 D + 1 E) ] - for (int t = 0; t < n_iter; t++) { - - // get all distances first - // No ILP because no accumulators - // FLOPCOUNT = 0.5 * (n_p * n_p - n_p) * 6 FLOPS - // 0.5 * (n_p * n_p - n_p) * (3 ADD + 2 MULT + 1 SQRT) - for (int i = 0; i < n_p; i++) { - // only upper triangle - for (int j = i + 1; j < n_p; j++) { - // 1 FLOP = 1 ADD - double dx = pedestrians[i].curr_pos_x - pedestrians[j].curr_pos_x; - // 1 FLOP = 1 ADD - double dy = pedestrians[i].curr_pos_y - pedestrians[j].curr_pos_y; - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double norm = sqrt(dx * dx + dy * dy); - r_ab_x_lut[i * n_p + j] = dx; - r_ab_y_lut[i * n_p + j] = dy; - r_ab_norm_lut[i * n_p + j] = norm; - } - } - - - // FLOPCOUNT = n_p * [(24 FLOPS) + ((n_p - 1) * 66 FLOPS) + (n_obstacles * 63 FLOPS) ] - // n_p * [ (20 ADD + 4 MULT) + ((n_p - 1) * (11 ADD + 22 MULT + 2 SQRT + 1 DIV + 1 EXP)) + n_obstacles * (12 ADD + 19 MULT + 1 SQRT + 1 DIV + 1 EXP) ] - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // 1 FLOP = 1 MULT - double desired_velocity_x = p.e_x * p.desired_v0; - // 1 FLOP = 1 MULT - double desired_velocity_y = p.e_y * p.desired_v0; - - // 2 FLOPS = 1ADD + 1 MULT - double f_alpha_dt_x = (desired_velocity_x - p.curr_vel_x) * dt_times_tau_alpha_inv; - // 2 FLOPS = 1ADD + 1 MULT - double f_alpha_dt_y = (desired_velocity_y - p.curr_vel_y) * dt_times_tau_alpha_inv; - - // accumulators for ILP - // f_alpha_dt_x = f_alpha_dt_x + f_alpha_beta_neg_x * w_val; - // f_alpha_dt_y = f_alpha_dt_y + f_alpha_beta_neg_y * w_val; - double acc_0_x = 0.0, acc_1_x = 0.0, acc_2_x = 0.0, acc_3_x = 0.0, acc_4_x = 0.0, acc_5_x = 0.0, acc_6_x = 0.0, acc_7_x = 0.0; - double acc_0_y = 0.0, acc_1_y = 0.0, acc_2_y = 0.0, acc_3_y = 0.0, acc_4_y = 0.0, acc_5_y = 0.0, acc_6_y = 0.0, acc_7_y = 0.0; - - const double p_e_x = p.e_x; - const double p_e_y = p.e_y; - - - // force between pedestrians --> no symmetry because of different accelerations - // ILP - int j = 0; - // FLOPCOUNT = (n_p - 1) * 66 FLOPS - for (; j <= n_p - 8; j+=8) { - // distance symmetry - double r_ab_x_0, r_ab_y_0, r_ab_norm_0; - double r_ab_x_1, r_ab_y_1, r_ab_norm_1; - double r_ab_x_2, r_ab_y_2, r_ab_norm_2; - double r_ab_x_3, r_ab_y_3, r_ab_norm_3; - double r_ab_x_4, r_ab_y_4, r_ab_norm_4; - double r_ab_x_5, r_ab_y_5, r_ab_norm_5; - double r_ab_x_6, r_ab_y_6, r_ab_norm_6; - double r_ab_x_7, r_ab_y_7, r_ab_norm_7; - - int j0 = j, j1 = j+1, j2 = j+2, j3 = j+3, j4 = j+4, j5 = j+5, j6 = j+6, j7 = j+7; - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j0) { - if (i < j0) { - r_ab_x_0 = r_ab_x_lut[i * n_p + j0]; - r_ab_y_0 = r_ab_y_lut[i * n_p + j0]; - r_ab_norm_0 = r_ab_norm_lut[i * n_p + j0]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_0 = -1 * r_ab_x_lut[j0 * n_p + i]; - r_ab_y_0 = -1 * r_ab_y_lut[j0 * n_p + i]; - r_ab_norm_0 = r_ab_norm_lut[j0 * n_p + i]; - } - // the math, see paper - // 1 FLOP = 1 ADD - double r_ab2_x_0 = r_ab_x_0 - pedestrians[j0].s_b_e_x; - // 1 FLOP = 1 ADD - double r_ab2_y_0 = r_ab_y_0 - pedestrians[j0].s_b_e_y; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_ab2_norm_0 = sqrt(r_ab2_x_0 * r_ab2_x_0 + r_ab2_y_0 * r_ab2_y_0); - // 1 FLOP = 1 ADD - double r_ab_norm_sum_0 = r_ab_norm_0 + r_ab2_norm_0; - // 2 FLOPS = 1 MULT + 1 ADD - double two_b_sq_0 = r_ab_norm_sum_0 * r_ab_norm_sum_0 - pedestrians[j0].s_b_sq; - // 1 FLOP = 1 SQRT - double two_b_0 = sqrt(two_b_sq_0); - // 36 FLOPS = 1 DIV + 5 MULT + 1 EXP - double scalar_mult_common_0 = (r_ab_norm_sum_0 / (two_b_0 * r_ab_norm_0 * r_ab2_norm_0)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_0 * NEG_TWO_SIGMA_INV)); - // 1 FLOP = 1 MULT - double scalar_mult_r_ab_0 = scalar_mult_common_0 * r_ab2_norm_0; - // 1 FLOP = 1 MULT - double scalar_mult_r_ab2_0 = scalar_mult_common_0 * r_ab_norm_0; - - // 3 FLOPS = 1 ADD + 2 MULT - double f_alpha_beta_neg_x_0 = r_ab_x_0 * scalar_mult_r_ab_0 + r_ab2_x_0 * scalar_mult_r_ab2_0; - // 3 FLOPS = 1 ADD + 2 MULT - double f_alpha_beta_neg_y_0 = r_ab_y_0 * scalar_mult_r_ab_0 + r_ab2_y_0 * scalar_mult_r_ab2_0; - - double w_val_0; - // 4 FLOPS = 1 ADD + 3 MULT - double a_0 = (p_e_x * f_alpha_beta_neg_x_0 + p_e_y * f_alpha_beta_neg_y_0) * cos_phi_inv; - // 3 FLOPS = 1 ADD + 2 MULT - double b_sq_0 = f_alpha_beta_neg_x_0 * f_alpha_beta_neg_x_0 + f_alpha_beta_neg_y_0 * f_alpha_beta_neg_y_0; - - w_val_0 = dt_neg; - // 1 FLOP - if (a_0 >= 0 && (a_0 * a_0 > b_sq_0)) { - w_val_0 = c_neg_times_dt; - } - - // 2 FLOPS = 1 ADD + 1 MULT - acc_0_x += f_alpha_beta_neg_x_0 * w_val_0; - // 2 FLOPS = 1 ADD + 1 MULT - acc_0_y += f_alpha_beta_neg_y_0 * w_val_0; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j1) { - if (i < j1) { - r_ab_x_1 = r_ab_x_lut[i * n_p + j1]; - r_ab_y_1 = r_ab_y_lut[i * n_p + j1]; - r_ab_norm_1 = r_ab_norm_lut[i * n_p + j1]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_1 = -1 * r_ab_x_lut[j1 * n_p + i]; - r_ab_y_1 = -1 * r_ab_y_lut[j1 * n_p + i]; - r_ab_norm_1 = r_ab_norm_lut[j1 * n_p + i]; - } - // the math, see paper - double r_ab2_x_1 = r_ab_x_1 - pedestrians[j1].s_b_e_x; - double r_ab2_y_1 = r_ab_y_1 - pedestrians[j1].s_b_e_y; - - double r_ab2_norm_1 = sqrt(r_ab2_x_1 * r_ab2_x_1 + r_ab2_y_1 * r_ab2_y_1); - double r_ab_norm_sum_1 = r_ab_norm_1 + r_ab2_norm_1; - double two_b_sq_1 = r_ab_norm_sum_1 * r_ab_norm_sum_1 - pedestrians[j1].s_b_sq; - double two_b_1 = sqrt(two_b_sq_1); - double scalar_mult_common_1 = (r_ab_norm_sum_1 / (two_b_1 * r_ab_norm_1 * r_ab2_norm_1)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_1 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_1 = scalar_mult_common_1 * r_ab2_norm_1; - double scalar_mult_r_ab2_1 = scalar_mult_common_1 * r_ab_norm_1; - - double f_alpha_beta_neg_x_1 = r_ab_x_1 * scalar_mult_r_ab_1 + r_ab2_x_1 * scalar_mult_r_ab2_1; - double f_alpha_beta_neg_y_1 = r_ab_y_1 * scalar_mult_r_ab_1 + r_ab2_y_1 * scalar_mult_r_ab2_1; - - double w_val_1; - double a_1 = (p_e_x * f_alpha_beta_neg_x_1 + p_e_y * f_alpha_beta_neg_y_1) * cos_phi_inv; - double b_sq_1 = f_alpha_beta_neg_x_1 * f_alpha_beta_neg_x_1 + f_alpha_beta_neg_y_1 * f_alpha_beta_neg_y_1; - - w_val_1 = dt_neg; - if (a_1 >= 0 && (a_1 * a_1 > b_sq_1)) { - w_val_1 = c_neg_times_dt; - } - - acc_1_x += f_alpha_beta_neg_x_1 * w_val_1; - acc_1_y += f_alpha_beta_neg_y_1 * w_val_1; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j2) { - if (i < j2) { - r_ab_x_2 = r_ab_x_lut[i * n_p + j2]; - r_ab_y_2 = r_ab_y_lut[i * n_p + j2]; - r_ab_norm_2 = r_ab_norm_lut[i * n_p + j2]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_2 = -1 * r_ab_x_lut[j2 * n_p + i]; - r_ab_y_2 = -1 * r_ab_y_lut[j2 * n_p + i]; - r_ab_norm_2 = r_ab_norm_lut[j2 * n_p + i]; - } - // the math, see paper - double r_ab2_x_2 = r_ab_x_2 - pedestrians[j2].s_b_e_x; - double r_ab2_y_2 = r_ab_y_2 - pedestrians[j2].s_b_e_y; - - double r_ab2_norm_2 = sqrt(r_ab2_x_2 * r_ab2_x_2 + r_ab2_y_2 * r_ab2_y_2); - double r_ab_norm_sum_2 = r_ab_norm_2 + r_ab2_norm_2; - double two_b_sq_2 = r_ab_norm_sum_2 * r_ab_norm_sum_2 - pedestrians[j2].s_b_sq; - double two_b_2 = sqrt(two_b_sq_2); - double scalar_mult_common_2 = (r_ab_norm_sum_2 / (two_b_2 * r_ab_norm_2 * r_ab2_norm_2)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_2 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_2 = scalar_mult_common_2 * r_ab2_norm_2; - double scalar_mult_r_ab2_2 = scalar_mult_common_2 * r_ab_norm_2; - - double f_alpha_beta_neg_x_2 = r_ab_x_2 * scalar_mult_r_ab_2 + r_ab2_x_2 * scalar_mult_r_ab2_2; - double f_alpha_beta_neg_y_2 = r_ab_y_2 * scalar_mult_r_ab_2 + r_ab2_y_2 * scalar_mult_r_ab2_2; - - double w_val_2; - double a_2 = (p_e_x * f_alpha_beta_neg_x_2 + p_e_y * f_alpha_beta_neg_y_2) * cos_phi_inv; - double b_sq_2 = f_alpha_beta_neg_x_2 * f_alpha_beta_neg_x_2 + f_alpha_beta_neg_y_2 * f_alpha_beta_neg_y_2; - - w_val_2 = dt_neg; - if (a_2 >= 0 && (a_2 * a_2 > b_sq_2)) { - w_val_2 = c_neg_times_dt; - } - - acc_2_x += f_alpha_beta_neg_x_2 * w_val_2; - acc_2_y += f_alpha_beta_neg_y_2 * w_val_2; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j3) { - if (i < j3) { - r_ab_x_3 = r_ab_x_lut[i * n_p + j3]; - r_ab_y_3 = r_ab_y_lut[i * n_p + j3]; - r_ab_norm_3 = r_ab_norm_lut[i * n_p + j3]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_3 = -1 * r_ab_x_lut[j3 * n_p + i]; - r_ab_y_3 = -1 * r_ab_y_lut[j3 * n_p + i]; - r_ab_norm_3 = r_ab_norm_lut[j3 * n_p + i]; - } - // the math, see paper - double r_ab2_x_3 = r_ab_x_3 - pedestrians[j3].s_b_e_x; - double r_ab2_y_3 = r_ab_y_3 - pedestrians[j3].s_b_e_y; - - double r_ab2_norm_3 = sqrt(r_ab2_x_3 * r_ab2_x_3 + r_ab2_y_3 * r_ab2_y_3); - double r_ab_norm_sum_3 = r_ab_norm_3 + r_ab2_norm_3; - double two_b_sq_3 = r_ab_norm_sum_3 * r_ab_norm_sum_3 - pedestrians[j3].s_b_sq; - double two_b_3 = sqrt(two_b_sq_3); - double scalar_mult_common_3 = (r_ab_norm_sum_3 / (two_b_3 * r_ab_norm_3 * r_ab2_norm_3)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_3 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_3 = scalar_mult_common_3 * r_ab2_norm_3; - double scalar_mult_r_ab2_3 = scalar_mult_common_3 * r_ab_norm_3; - - double f_alpha_beta_neg_x_3 = r_ab_x_3 * scalar_mult_r_ab_3 + r_ab2_x_3 * scalar_mult_r_ab2_3; - double f_alpha_beta_neg_y_3 = r_ab_y_3 * scalar_mult_r_ab_3 + r_ab2_y_3 * scalar_mult_r_ab2_3; - - double w_val_3; - double a_3 = (p_e_x * f_alpha_beta_neg_x_3 + p_e_y * f_alpha_beta_neg_y_3) * cos_phi_inv; - double b_sq_3 = f_alpha_beta_neg_x_3 * f_alpha_beta_neg_x_3 + f_alpha_beta_neg_y_3 * f_alpha_beta_neg_y_3; - - w_val_3 = dt_neg; - if (a_3 >= 0 && (a_3 * a_3 > b_sq_3)) { - w_val_3 = c_neg_times_dt; - } - - acc_3_x += f_alpha_beta_neg_x_3 * w_val_3; - acc_3_y += f_alpha_beta_neg_y_3 * w_val_3; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j4) { - if (i < j4) { - r_ab_x_4 = r_ab_x_lut[i * n_p + j4]; - r_ab_y_4 = r_ab_y_lut[i * n_p + j4]; - r_ab_norm_4 = r_ab_norm_lut[i * n_p + j4]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_4 = -1 * r_ab_x_lut[j4 * n_p + i]; - r_ab_y_4 = -1 * r_ab_y_lut[j4 * n_p + i]; - r_ab_norm_4 = r_ab_norm_lut[j4 * n_p + i]; - } - // the math, see paper - double r_ab2_x_4 = r_ab_x_4 - pedestrians[j4].s_b_e_x; - double r_ab2_y_4 = r_ab_y_4 - pedestrians[j4].s_b_e_y; - - double r_ab2_norm_4 = sqrt(r_ab2_x_4 * r_ab2_x_4 + r_ab2_y_4 * r_ab2_y_4); - double r_ab_norm_sum_4 = r_ab_norm_4 + r_ab2_norm_4; - double two_b_sq_4 = r_ab_norm_sum_4 * r_ab_norm_sum_4 - pedestrians[j4].s_b_sq; - double two_b_4 = sqrt(two_b_sq_4); - double scalar_mult_common_4 = (r_ab_norm_sum_4 / (two_b_4 * r_ab_norm_4 * r_ab2_norm_4)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_4 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_4 = scalar_mult_common_4 * r_ab2_norm_4; - double scalar_mult_r_ab2_4 = scalar_mult_common_4 * r_ab_norm_4; - - double f_alpha_beta_neg_x_4 = r_ab_x_4 * scalar_mult_r_ab_4 + r_ab2_x_4 * scalar_mult_r_ab2_4; - double f_alpha_beta_neg_y_4 = r_ab_y_4 * scalar_mult_r_ab_4 + r_ab2_y_4 * scalar_mult_r_ab2_4; - - double w_val_4; - double a_4 = (p_e_x * f_alpha_beta_neg_x_4 + p_e_y * f_alpha_beta_neg_y_4) * cos_phi_inv; - double b_sq_4 = f_alpha_beta_neg_x_4 * f_alpha_beta_neg_x_4 + f_alpha_beta_neg_y_4 * f_alpha_beta_neg_y_4; - - w_val_4 = dt_neg; - if (a_4 >= 0 && (a_4 * a_4 > b_sq_4)) { - w_val_4 = c_neg_times_dt; - } - - acc_4_x += f_alpha_beta_neg_x_4 * w_val_4; - acc_4_y += f_alpha_beta_neg_y_4 * w_val_4; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j5) { - if (i < j5) { - r_ab_x_5 = r_ab_x_lut[i * n_p + j5]; - r_ab_y_5 = r_ab_y_lut[i * n_p + j5]; - r_ab_norm_5 = r_ab_norm_lut[i * n_p + j5]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_5 = -1 * r_ab_x_lut[j5 * n_p + i]; - r_ab_y_5 = -1 * r_ab_y_lut[j5 * n_p + i]; - r_ab_norm_5 = r_ab_norm_lut[j5 * n_p + i]; - } - // the math, see paper - double r_ab2_x_5 = r_ab_x_5 - pedestrians[j5].s_b_e_x; - double r_ab2_y_5 = r_ab_y_5 - pedestrians[j5].s_b_e_y; - - double r_ab2_norm_5 = sqrt(r_ab2_x_5 * r_ab2_x_5 + r_ab2_y_5 * r_ab2_y_5); - double r_ab_norm_sum_5 = r_ab_norm_5 + r_ab2_norm_5; - double two_b_sq_5 = r_ab_norm_sum_5 * r_ab_norm_sum_5 - pedestrians[j5].s_b_sq; - double two_b_5 = sqrt(two_b_sq_5); - double scalar_mult_common_5 = (r_ab_norm_sum_5 / (two_b_5 * r_ab_norm_5 * r_ab2_norm_5)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_5 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_5 = scalar_mult_common_5 * r_ab2_norm_5; - double scalar_mult_r_ab2_5 = scalar_mult_common_5 * r_ab_norm_5; - - double f_alpha_beta_neg_x_5 = r_ab_x_5 * scalar_mult_r_ab_5 + r_ab2_x_5 * scalar_mult_r_ab2_5; - double f_alpha_beta_neg_y_5 = r_ab_y_5 * scalar_mult_r_ab_5 + r_ab2_y_5 * scalar_mult_r_ab2_5; - - double w_val_5; - double a_5 = (p_e_x * f_alpha_beta_neg_x_5 + p_e_y * f_alpha_beta_neg_y_5) * cos_phi_inv; - double b_sq_5 = f_alpha_beta_neg_x_5 * f_alpha_beta_neg_x_5 + f_alpha_beta_neg_y_5 * f_alpha_beta_neg_y_5; - - w_val_5 = dt_neg; - if (a_5 >= 0 && (a_5 * a_5 > b_sq_5)) { - w_val_5 = c_neg_times_dt; - } - - acc_5_x += f_alpha_beta_neg_x_5 * w_val_5; - acc_5_y += f_alpha_beta_neg_y_5 * w_val_5; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j6) { - if (i < j6) { - r_ab_x_6 = r_ab_x_lut[i * n_p + j6]; - r_ab_y_6 = r_ab_y_lut[i * n_p + j6]; - r_ab_norm_6 = r_ab_norm_lut[i * n_p + j6]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_6 = -1 * r_ab_x_lut[j6 * n_p + i]; - r_ab_y_6 = -1 * r_ab_y_lut[j6 * n_p + i]; - r_ab_norm_6 = r_ab_norm_lut[j6 * n_p + i]; - } - // the math, see paper - double r_ab2_x_6 = r_ab_x_6 - pedestrians[j6].s_b_e_x; - double r_ab2_y_6 = r_ab_y_6 - pedestrians[j6].s_b_e_y; - - double r_ab2_norm_6 = sqrt(r_ab2_x_6 * r_ab2_x_6 + r_ab2_y_6 * r_ab2_y_6); - double r_ab_norm_sum_6 = r_ab_norm_6 + r_ab2_norm_6; - double two_b_sq_6 = r_ab_norm_sum_6 * r_ab_norm_sum_6 - pedestrians[j6].s_b_sq; - double two_b_6 = sqrt(two_b_sq_6); - double scalar_mult_common_6 = (r_ab_norm_sum_6 / (two_b_6 * r_ab_norm_6 * r_ab2_norm_6)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_6 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_6 = scalar_mult_common_6 * r_ab2_norm_6; - double scalar_mult_r_ab2_6 = scalar_mult_common_6 * r_ab_norm_6; - - double f_alpha_beta_neg_x_6 = r_ab_x_6 * scalar_mult_r_ab_6 + r_ab2_x_6 * scalar_mult_r_ab2_6; - double f_alpha_beta_neg_y_6 = r_ab_y_6 * scalar_mult_r_ab_6 + r_ab2_y_6 * scalar_mult_r_ab2_6; - - double w_val_6; - double a_6 = (p_e_x * f_alpha_beta_neg_x_6 + p_e_y * f_alpha_beta_neg_y_6) * cos_phi_inv; - double b_sq_6 = f_alpha_beta_neg_x_6 * f_alpha_beta_neg_x_6 + f_alpha_beta_neg_y_6 * f_alpha_beta_neg_y_6; - - w_val_6 = dt_neg; - if (a_6 >= 0 && (a_6 * a_6 > b_sq_6)) { - w_val_6 = c_neg_times_dt; - } - - acc_6_x += f_alpha_beta_neg_x_6 * w_val_6; - acc_6_y += f_alpha_beta_neg_y_6 * w_val_6; - } - - // skip i == j - // FLOPCOUNT = 66 FLOPS - if (i != j7) { - if (i < j7) { - r_ab_x_7 = r_ab_x_lut[i * n_p + j7]; - r_ab_y_7 = r_ab_y_lut[i * n_p + j7]; - r_ab_norm_7 = r_ab_norm_lut[i * n_p + j7]; - } else { - // other direction - // I think here we can optimize for sure, currently not using LUT good - r_ab_x_7 = -1 * r_ab_x_lut[j7 * n_p + i]; - r_ab_y_7 = -1 * r_ab_y_lut[j7 * n_p + i]; - r_ab_norm_7 = r_ab_norm_lut[j7 * n_p + i]; - } - // the math, see paper - double r_ab2_x_7 = r_ab_x_7 - pedestrians[j7].s_b_e_x; - double r_ab2_y_7 = r_ab_y_7 - pedestrians[j7].s_b_e_y; - - double r_ab2_norm_7 = sqrt(r_ab2_x_7 * r_ab2_x_7 + r_ab2_y_7 * r_ab2_y_7); - double r_ab_norm_sum_7 = r_ab_norm_7 + r_ab2_norm_7; - double two_b_sq_7 = r_ab_norm_sum_7 * r_ab_norm_sum_7 - pedestrians[j7].s_b_sq; - double two_b_7 = sqrt(two_b_sq_7); - double scalar_mult_common_7 = (r_ab_norm_sum_7 / (two_b_7 * r_ab_norm_7 * r_ab2_norm_7)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b_7 * NEG_TWO_SIGMA_INV)); - double scalar_mult_r_ab_7 = scalar_mult_common_7 * r_ab2_norm_7; - double scalar_mult_r_ab2_7 = scalar_mult_common_7 * r_ab_norm_7; - - double f_alpha_beta_neg_x_7 = r_ab_x_7 * scalar_mult_r_ab_7 + r_ab2_x_7 * scalar_mult_r_ab2_7; - double f_alpha_beta_neg_y_7 = r_ab_y_7 * scalar_mult_r_ab_7 + r_ab2_y_7 * scalar_mult_r_ab2_7; - - double w_val_7; - double a_7 = (p_e_x * f_alpha_beta_neg_x_7 + p_e_y * f_alpha_beta_neg_y_7) * cos_phi_inv; - double b_sq_7 = f_alpha_beta_neg_x_7 * f_alpha_beta_neg_x_7 + f_alpha_beta_neg_y_7 * f_alpha_beta_neg_y_7; - - w_val_7 = dt_neg; - if (a_7 >= 0 && (a_7 * a_7 > b_sq_7)) { - w_val_7 = c_neg_times_dt; - } - - acc_7_x += f_alpha_beta_neg_x_7 * w_val_7; - acc_7_y += f_alpha_beta_neg_y_7 * w_val_7; - } - - } - - // add the result - // 8 FLOPS = 8 ADD - f_alpha_dt_x += acc_0_x + acc_1_x + acc_2_x + acc_3_x + acc_4_x + acc_5_x + acc_6_x + acc_7_x; - // 8 FLOPS = 8 ADD - f_alpha_dt_y += acc_0_y + acc_1_y + acc_2_y + acc_3_y + acc_4_y + acc_5_y + acc_6_y + acc_7_y; - - // remaining - // 66 FLOPS --> ignore for flop count already handled correctly with ILP loop - for (; j < n_p; j++) { - if (i == j) continue; - - double r_ab_x, r_ab_y, r_ab_norm; - if (i < j) { - r_ab_x = r_ab_x_lut[i * n_p + j]; - r_ab_y = r_ab_y_lut[i * n_p + j]; - r_ab_norm = r_ab_norm_lut[i * n_p + j]; - } else { - r_ab_x = -1 * r_ab_x_lut[j * n_p + i]; - r_ab_y = -1 * r_ab_y_lut[j * n_p + i]; - r_ab_norm = r_ab_norm_lut[j * n_p + i]; - } - - // 1 FLOP = 1 ADD - double r_ab2_x = r_ab_x - pedestrians[j].s_b_e_x; - // 1 FLOP = 1 ADD - double r_ab2_y = r_ab_y - pedestrians[j].s_b_e_y; - - // 4 FLOPS = 1 SQRT + 2 MULT + 1 ADD - double r_ab2_norm = sqrt(r_ab2_x * r_ab2_x + r_ab2_y * r_ab2_y); - // 1 FLOP = 1 ADD - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - // 2 FLOPS = 1 MULT + 1 ADD - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - pedestrians[j].s_b_sq; - // 1 FLOP = 1 SQRT - double two_b = sqrt(two_b_sq); - // 36 FLOPS = 1 DIV + 5 MULT + 1 EXP - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * exp(two_b * NEG_TWO_SIGMA_INV)); - // 1 FLOP = 1 MULT - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - // 1 FLOP = 1 MULT - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_x = r_ab_x * scalar_mult_r_ab + r_ab2_x * scalar_mult_r_ab2; - // 3 FLOPS = 2 MULT + 1 ADD - double f_alpha_beta_neg_y = r_ab_y * scalar_mult_r_ab + r_ab2_y * scalar_mult_r_ab2; - - double w_val; - // 4 FLOPS = 3 MULT + 1 ADD - double a = (p_e_x * f_alpha_beta_neg_x + p_e_y * f_alpha_beta_neg_y) * cos_phi_inv; - // 3 FLOPS = 2 MULT + 1 ADD - double b_sq = f_alpha_beta_neg_x * f_alpha_beta_neg_x + f_alpha_beta_neg_y * f_alpha_beta_neg_y; - - w_val = dt_neg; - // 1 FLOP = 1 MULT - if (a >= 0 && (a * a > b_sq)) { - w_val = c_neg_times_dt; - } - - // 2 FLOPS = 1 ADD + 1 MULT - f_alpha_dt_x = f_alpha_dt_x + f_alpha_beta_neg_x * w_val; - // 2 FLOPS = 1 ADD + 1 MULT - f_alpha_dt_y = f_alpha_dt_y + f_alpha_beta_neg_y * w_val; - } - - - // force between pedestrian and obstacles - // initialized with 0 obstacles, might consider deleting this? - // to sparse to use ILP - // FLOPS = n_obstacles * 63 FLOPS - for (int k = 0; k < board.n_obstacles; k++) { - - double v1_x = p.curr_pos_x - board.obstacles[k].start.x; - double v1_y = p.curr_pos_y - board.obstacles[k].start.y; - - double projection = (v1_x * board.obstacles[k].obstacle_v.x + v1_y * board.obstacles[k].obstacle_v.y) * board.obstacles[k].length_sq_inv; - - if (projection < 0.0) - projection = 0.0; - else if (projection > 1.0) - projection = 1.0; - - double r_aB_x = p.curr_pos_x - (board.obstacles[k].start.x + board.obstacles[k].obstacle_v.x * projection); - double r_aB_y = p.curr_pos_y - (board.obstacles[k].start.y + board.obstacles[k].obstacle_v.y * projection); - - double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); - double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - double f_alpha_B_neg_x = r_aB_x * scalar_mult; - double f_alpha_B_neg_y = r_aB_y * scalar_mult; - - double w_val; - double a = (p.e_x * f_alpha_B_neg_x + p.e_y * f_alpha_B_neg_y) * cos_phi_inv; - double b_sq = f_alpha_B_neg_x * f_alpha_B_neg_x + f_alpha_B_neg_y * f_alpha_B_neg_y; - - w_val = dt_neg; - if (a >= 0 && (a * a > b_sq)) { - w_val = c_neg_times_dt; - } - - double f_alpha_B_dt_x = f_alpha_B_neg_x * w_val; - double f_alpha_B_dt_y = f_alpha_B_neg_y * w_val; - - f_alpha_dt_x += f_alpha_B_dt_x; - f_alpha_dt_y += f_alpha_B_dt_y; - } - - // 1 FLOP = 1 ADD - p.w_x += f_alpha_dt_x; - // 1 FLOP = 1 ADD - p.w_y += f_alpha_dt_y; - } - - // update the positions etc. - // ILP is not possible I think - // FLOPCOUNT = n_p * 29 FLOPS - // n_p * (7 ADD + 17 MULT + 3 SQRT + 2 DIV) - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - double w_norm = sqrt(p.w_x * p.w_x + p.w_y * p.w_y); - - double correction_factor = min(1.0, p.v_max / w_norm); - - p.curr_vel_x = p.w_x * correction_factor; - p.curr_vel_y = p.w_y * correction_factor; - - // maybe move this calculation to a separate loop to enable ILP - p.curr_pos_x = p.curr_pos_x + p.curr_vel_x * dt; - p.curr_pos_y = p.curr_pos_y + p.curr_vel_y * dt; - - if (p.color == 0 && p.curr_pos_x > (BOARD_LENGTH - 1.5)) { - p.curr_pos_x = 1.0; - p.destination_y = p.curr_pos_y; - p.w_x = p.desired_v0; - p.w_y = 0.0; - p.curr_vel_x = p.w_x; - p.curr_vel_y = p.w_y; - } - if (p.color == 1 && p.curr_pos_x < 1.5) { - p.curr_pos_x = BOARD_LENGTH - 1.0; - p.destination_y = p.curr_pos_y; - p.w_x = -p.desired_v0; - p.w_y = 0.0; - p.curr_vel_x = p.w_x; - p.curr_vel_y = p.w_y; - } - - p.curr_pos_y = clamp(p.curr_pos_y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - double e_x = p.destination_x - p.curr_pos_x; - double e_y = p.destination_y - p.curr_pos_y; - - double e_norm_inv = 1.0 / sqrt(e_x * e_x + e_y * e_y); - - p.e_x = e_x * e_norm_inv; - p.e_y = e_y * e_norm_inv; - - double velocity_norm_sq = p.curr_vel_x * p.curr_vel_x + p.curr_vel_y * p.curr_vel_y; - - double velocity_norm = sqrt(velocity_norm_sq); - - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - p.s_b_e_x = p.e_x * (velocity_norm * delta_t); - p.s_b_e_y = p.e_y * (velocity_norm * delta_t); - -#if !BENCHMARK - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos_x, p.curr_pos_y, p.e_x, p.e_y, p.color); - } -#endif - } - } - -#if !BENCHMARK - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} diff --git a/variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.cpp b/variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.cpp index 2164224..cb3d8e3 100644 --- a/variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.cpp +++ b/variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt.cpp @@ -166,23 +166,42 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 128 MULTs, 112 ADDs static inline __m512d _avx512_exp_pd(__m512d x) { - + const __m512d log2e = _mm512_set1_pd(1.4426950408889634); + + // # 8 MULTs __m512d nf = _mm512_roundscale_pd(_mm512_mul_pd(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512d ln2 = _mm512_set1_pd(0.6931471805599453); + + // # 8 MULTs, 8 ADDs __m512d r = _mm512_fnmadd_pd(nf, ln2, x); - const __m512d c7 = _mm512_set1_pd(1.0 / 5040.0); - const __m512d c6 = _mm512_set1_pd(1.0 / 720.0); - const __m512d c5 = _mm512_set1_pd(1.0 / 120.0); - const __m512d c4 = _mm512_set1_pd(1.0 / 24.0); - const __m512d c3 = _mm512_set1_pd(1.0 / 6.0); - const __m512d c2 = _mm512_set1_pd(0.5); - const __m512d c1 = _mm512_set1_pd(1.0); - const __m512d c0 = _mm512_set1_pd(1.0); + const __m512d c13 = _mm512_set1_pd(c13_scalar); + const __m512d c12 = _mm512_set1_pd(c12_scalar); + const __m512d c11 = _mm512_set1_pd(c11_scalar); + const __m512d c10 = _mm512_set1_pd(c10_scalar); + const __m512d c9 = _mm512_set1_pd(c9_scalar); + const __m512d c8 = _mm512_set1_pd(c8_scalar); + const __m512d c7 = _mm512_set1_pd(c7_scalar); + const __m512d c6 = _mm512_set1_pd(c6_scalar); + const __m512d c5 = _mm512_set1_pd(c5_scalar); + const __m512d c4 = _mm512_set1_pd(c4_scalar); + const __m512d c3 = _mm512_set1_pd(c3_scalar); + const __m512d c2 = _mm512_set1_pd(c2_scalar); + const __m512d c1 = _mm512_set1_pd(c1_scalar); + const __m512d c0 = _mm512_set1_pd(c0_scalar); - __m512d p = c7; + __m512d p = c13; + + // # 104 MULTs, 104 ADDs + p = _mm512_fmadd_pd(p, r, c12); + p = _mm512_fmadd_pd(p, r, c11); + p = _mm512_fmadd_pd(p, r, c10); + p = _mm512_fmadd_pd(p, r, c9); + p = _mm512_fmadd_pd(p, r, c8); + p = _mm512_fmadd_pd(p, r, c7); p = _mm512_fmadd_pd(p, r, c6); p = _mm512_fmadd_pd(p, r, c5); p = _mm512_fmadd_pd(p, r, c4); @@ -208,6 +227,7 @@ static inline __m512d _avx512_exp_pd(__m512d x) { __m512d pow2n = _mm512_castsi512_pd(scaled); + // # 8 MULTs return _mm512_mul_pd(p, pow2n); } @@ -261,6 +281,23 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512d v_np = _mm512_set1_pd(n_p); + // # ADDs: n_iter * n_p * 33 + + // # n_iter * n_p * (n_p_pad / 8) * 232 + + // # n_iter * n_p * board.n_obstacles * 10 + + // # MULTs: n_iter * n_p * 40 + + // # n_iter * n_p * (n_p_pad / 8) * 320 + + // # n_iter * n_p * board.n_obstacles * 13 + + // # DIVs: n_iter * n_p * (n_p_pad / 8) * 8 + + // # n_iter * n_p * board.n_obstacles + + // # SQRTs: n_iter * n_p + + // # n_iter * n_p * (n_p_pad / 8) * 24 + + // # n_iter * n_p * board.n_obstacles + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { diff --git a/variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.cpp b/variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.cpp index 6f5b8df..c621275 100644 --- a/variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.cpp +++ b/variants/variant_inlining_8_SoA_AVX512_polyexp_fastinvsqrt_sr.cpp @@ -14,6 +14,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0 #define BOARD_LENGTH 50.0 +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0 +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5 #define WALL_EPS 1e-3 #define BOARD_WIDTH_MINUS_WALL_EPS 9.999 @@ -163,23 +165,42 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 128 MULTs, 112 ADDs static inline __m512d _avx512_exp_pd(__m512d x) { const __m512d log2e = _mm512_set1_pd(1.4426950408889634); + + // # 8 MULTs __m512d nf = _mm512_roundscale_pd(_mm512_mul_pd(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512d ln2 = _mm512_set1_pd(0.6931471805599453); + + // # 8 MULTs, 8 ADDs __m512d r = _mm512_fnmadd_pd(nf, ln2, x); - const __m512d c7 = _mm512_set1_pd(1.0 / 5040.0); - const __m512d c6 = _mm512_set1_pd(1.0 / 720.0); - const __m512d c5 = _mm512_set1_pd(1.0 / 120.0); - const __m512d c4 = _mm512_set1_pd(1.0 / 24.0); - const __m512d c3 = _mm512_set1_pd(1.0 / 6.0); - const __m512d c2 = _mm512_set1_pd(0.5); - const __m512d c1 = _mm512_set1_pd(1.0); - const __m512d c0 = _mm512_set1_pd(1.0); + const __m512d c13 = _mm512_set1_pd(c13_scalar); + const __m512d c12 = _mm512_set1_pd(c12_scalar); + const __m512d c11 = _mm512_set1_pd(c11_scalar); + const __m512d c10 = _mm512_set1_pd(c10_scalar); + const __m512d c9 = _mm512_set1_pd(c9_scalar); + const __m512d c8 = _mm512_set1_pd(c8_scalar); + const __m512d c7 = _mm512_set1_pd(c7_scalar); + const __m512d c6 = _mm512_set1_pd(c6_scalar); + const __m512d c5 = _mm512_set1_pd(c5_scalar); + const __m512d c4 = _mm512_set1_pd(c4_scalar); + const __m512d c3 = _mm512_set1_pd(c3_scalar); + const __m512d c2 = _mm512_set1_pd(c2_scalar); + const __m512d c1 = _mm512_set1_pd(c1_scalar); + const __m512d c0 = _mm512_set1_pd(c0_scalar); - __m512d p = c7; + __m512d p = c13; + + // # 104 MULTs, 104 ADDs + p = _mm512_fmadd_pd(p, r, c12); + p = _mm512_fmadd_pd(p, r, c11); + p = _mm512_fmadd_pd(p, r, c10); + p = _mm512_fmadd_pd(p, r, c9); + p = _mm512_fmadd_pd(p, r, c8); + p = _mm512_fmadd_pd(p, r, c7); p = _mm512_fmadd_pd(p, r, c6); p = _mm512_fmadd_pd(p, r, c5); p = _mm512_fmadd_pd(p, r, c4); @@ -205,9 +226,11 @@ static inline __m512d _avx512_exp_pd(__m512d x) { __m512d pow2n = _mm512_castsi512_pd(scaled); + // # 8 MULTs return _mm512_mul_pd(p, pow2n); } +// # 10 MULTs, 3 ADDs static inline double fast_inv_sqrt(double number) { double y = number; @@ -258,9 +281,26 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512d v_np = _mm512_set1_pd(n_p); - for (int t = 0; t < n_iter; t++) { + // # ADDs: n_iter * n_p * 33 + + // # n_iter * n_p * (n_p_pad / 8) * 232 + + // # n_iter * n_p * board.n_obstacles * 10 + // # MULTs: n_iter * n_p * 40 + + // # n_iter * n_p * (n_p_pad / 8) * 320 + + // # n_iter * n_p * board.n_obstacles * 13 + + // # DIVs: n_iter * n_p * (n_p_pad / 8) * 8 + + // # n_iter * n_p * board.n_obstacles + + // # SQRTs: n_iter * n_p + + // # n_iter * n_p * (n_p_pad / 8) * 24 + + // # n_iter * n_p * board.n_obstacles + + // # EXPs: n_iter * n_p * board.n_obstacles + + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + double pos_x_i = pos_x[i]; double pos_y_i = pos_y[i]; double e_x_i = e_x[i]; @@ -269,6 +309,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c double curr_vel_x_i = curr_vel_x[i]; double curr_vel_y_i = curr_vel_y[i]; + // # 4 MULTs, 2 ADDs double f_alpha_dt_x = (e_x_i * desired_v0_i - curr_vel_x_i) * dt_times_tau_alpha_inv; double f_alpha_dt_y = (e_y_i * desired_v0_i - curr_vel_y_i) * dt_times_tau_alpha_inv; @@ -282,6 +323,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d acc_y = _mm512_setzero_pd(); for (int j = 0; j < n_p_pad; j += 8) { + + // # 8 ADDs __m512d j_idx = _mm512_add_pd(_mm512_set1_pd((double)j), lane_off); __mmask8 m_neq_i = _mm512_cmp_pd_mask(j_idx, v_i, _CMP_NEQ_OQ); @@ -294,36 +337,65 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d sbe_jy = _mm512_loadu_pd(&sb_e_y[j]); __m512d sb_jsq = _mm512_loadu_pd(&sb_sq[j]); + // # 16 ADDs __m512d r_ab_x = _mm512_sub_pd(pix, pjx); __m512d r_ab_y = _mm512_sub_pd(piy, pjy); + + // # 16 MULTs, 8 ADDs __m512d r_ab_n2 = _mm512_fmadd_pd(r_ab_x, r_ab_x, _mm512_mul_pd(r_ab_y, r_ab_y)); + + // # 8 SQRTs __m512d r_ab_n = _mm512_sqrt_pd(r_ab_n2); + // # 16 ADDs __m512d r_ab2_x = _mm512_sub_pd(r_ab_x, sbe_jx); __m512d r_ab2_y = _mm512_sub_pd(r_ab_y, sbe_jy); + + // # 16 MULTs, 8 ADDs __m512d r_ab2_n2 = _mm512_fmadd_pd(r_ab2_x, r_ab2_x, _mm512_mul_pd(r_ab2_y, r_ab2_y)); + + // # 8 SQRTs __m512d r_ab2_n = _mm512_sqrt_pd(r_ab2_n2); + // # 8 ADDs __m512d r_sum = _mm512_add_pd(r_ab_n, r_ab2_n); + + // # 8 MULTs, 8 ADDs __m512d two_b_sq = _mm512_fmsub_pd(r_sum, r_sum, sb_jsq); + + // # 8 SQRTs __m512d two_b = _mm512_sqrt_pd(two_b_sq); + // # 8 MULTs __m512d e_arg = _mm512_mul_pd(two_b, v_neg_2s_inv); + + // # 128 MULTs, 112 ADDs __m512d e_val = _avx512_exp_pd(e_arg); + // # 32 MULTs __m512d numer = _mm512_mul_pd(_mm512_mul_pd(r_sum, v_neg_v0_div_2s), e_val); __m512d denom = _mm512_mul_pd(_mm512_mul_pd(two_b, r_ab_n), r_ab2_n); + __m512d denom_safe = _mm512_mask_blend_pd(active, _mm512_set1_pd(1.0), denom); + + // # 8 DIVs __m512d smc = _mm512_div_pd(numer, denom_safe); + // # 16 MULTs __m512d sm_r_ab = _mm512_mul_pd(smc, r_ab2_n); __m512d sm_r_ab2 = _mm512_mul_pd(smc, r_ab_n); + // # 32 MULTs, 16 ADDs __m512d f_x = _mm512_fmadd_pd(r_ab2_x, sm_r_ab2, _mm512_mul_pd(r_ab_x, sm_r_ab)); __m512d f_y = _mm512_fmadd_pd(r_ab2_y, sm_r_ab2, _mm512_mul_pd(r_ab_y, sm_r_ab)); + // # 24 MULTs, 8 ADDs __m512d a = _mm512_mul_pd(_mm512_fmadd_pd(eix, f_x, _mm512_mul_pd(eiy, f_y)), v_cos_phi_inv); + + // # 8 MULTs __m512d a_sq = _mm512_mul_pd(a, a); + + // # 16 MULTs, 8 ADDs __m512d b_sq = _mm512_fmadd_pd(f_x, f_x, _mm512_mul_pd(f_y, f_y)); __mmask8 k_ge = _mm512_cmp_pd_mask(a, v_zero, _CMP_GE_OQ); @@ -333,10 +405,13 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d f_x_safe = _mm512_maskz_mov_pd(active, f_x); __m512d f_y_safe = _mm512_maskz_mov_pd(active, f_y); + + // # 16 MULTs, 16 ADDs acc_x = _mm512_fmadd_pd(f_x_safe, w_val, acc_x); acc_y = _mm512_fmadd_pd(f_y_safe, w_val, acc_y); } + // # 16 ADDs f_alpha_dt_x += _mm512_reduce_add_pd(acc_x); f_alpha_dt_y += _mm512_reduce_add_pd(acc_y); @@ -348,9 +423,11 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c double obs_v_y = board.obstacles[k].obstacle_v_y; double obs_length_sq_inv = board.obstacles[k].length_sq_inv; + // # 2 ADDs double v1_x = pos_x_i - obs_start_x; double v1_y = pos_y_i - obs_start_y; + // # 3 MULTs, 1 ADD double projection = (v1_x * obs_v_x + v1_y * obs_v_y) * obs_length_sq_inv; if (projection < 0.0) @@ -358,11 +435,17 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0) projection = 1.0; + // # 2 MULTs, 4 ADDs double r_aB_x = pos_x_i - (obs_start_x + obs_v_x * projection); double r_aB_y = pos_y_i - (obs_start_y + obs_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); + + // # 2 MULTs, 1 EXP, 1 DIV double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; + + // # 2 MULTs double f_alpha_B_neg_x = r_aB_x * scalar_mult; double f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -371,15 +454,18 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTs, 2 ADDs f_alpha_dt_x += f_alpha_B_neg_x * w_val; f_alpha_dt_y += f_alpha_B_neg_y * w_val; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } for (int i = 0; i < n_p; i++) { + double color_i = color[i]; double curr_vel_x_i = curr_vel_x[i]; double curr_vel_y_i = curr_vel_y[i]; @@ -394,17 +480,21 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c double destination_x_i = destination_x[i]; double destination_y_i = destination_y[i]; + // # 12 MULTs, 4 ADDs double w_norm_inv = fast_inv_sqrt(w_x_i * w_x_i + w_y_i * w_y_i); + // # 1 MULT double correction_factor = min(1.0, v_max_i * w_norm_inv); + // # 2 MULTs curr_vel_x_i = w_x_i * correction_factor; curr_vel_y_i = w_y_i * correction_factor; + // # 2 MULTs, 2 ADDs pos_x_i = pos_x_i + curr_vel_x_i * dt; pos_y_i = pos_y_i + curr_vel_y_i * dt; - if (!color_i && pos_x_i > (BOARD_LENGTH - 1.5)) { + if (!color_i && pos_x_i > (BOARD_LENGTH_MINUS_ONE_POINT_FIVE)) { pos_x_i = 1.0; destination_y_i = pos_y_i; w_x_i = desired_v0_i; @@ -413,7 +503,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c curr_vel_y_i = w_y_i; } if (color_i && pos_x_i < 1.5) { - pos_x_i = BOARD_LENGTH - 1.0; + pos_x_i = BOARD_LENGTH_MINUS_ONE; destination_y_i = pos_y_i; w_x_i = -desired_v0_i; w_y_i = 0.0; @@ -423,16 +513,24 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i = clamp(pos_y_i, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); + // # 2 ADDs double ex = destination_x_i - pos_x_i; double ey = destination_y_i - pos_y_i; + // # 12 MULTs, 4 ADDs double e_norm_inv = fast_inv_sqrt(ex * ex + ey * ey); + // # 2 MULTs e_x_i = ex * e_norm_inv; e_y_i = ey * e_norm_inv; + // # 2 MULTs, 1 ADD double velocity_norm_sq = curr_vel_x_i * curr_vel_x_i + curr_vel_y_i * curr_vel_y_i; + + // # 1 MULT double sb_sq_i = velocity_norm_sq * delta_t_sq; + + // # 1 SQRT double velocity_norm_mul_delta_t = sqrt(sb_sq_i); curr_vel_x[i] = curr_vel_x_i; @@ -450,6 +548,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c destination_y[i] = destination_y_i; sb_sq[i] = sb_sq_i; + + // # 2 MULTs sb_e_x[i] = e_x_i * velocity_norm_mul_delta_t; sb_e_y[i] = e_y_i * velocity_norm_mul_delta_t; diff --git a/variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx.cpp b/variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx.cpp index ceab1cb..7b51d9a 100644 --- a/variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx.cpp +++ b/variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0 #define BOARD_LENGTH 50.0 +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0 +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5 #define WALL_EPS 1e-3 #define BOARD_WIDTH_MINUS_WALL_EPS 9.999 @@ -162,23 +164,42 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 128 MULTs, 112 ADDs static inline __m512d _avx512_exp_pd(__m512d x) { const __m512d log2e = _mm512_set1_pd(1.4426950408889634); + + // # 8 MULTs __m512d nf = _mm512_roundscale_pd(_mm512_mul_pd(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512d ln2 = _mm512_set1_pd(0.6931471805599453); + + // # 8 MULTs, 8 ADDs __m512d r = _mm512_fnmadd_pd(nf, ln2, x); - const __m512d c7 = _mm512_set1_pd(1.0 / 5040.0); - const __m512d c6 = _mm512_set1_pd(1.0 / 720.0); - const __m512d c5 = _mm512_set1_pd(1.0 / 120.0); - const __m512d c4 = _mm512_set1_pd(1.0 / 24.0); - const __m512d c3 = _mm512_set1_pd(1.0 / 6.0); - const __m512d c2 = _mm512_set1_pd(0.5); - const __m512d c1 = _mm512_set1_pd(1.0); - const __m512d c0 = _mm512_set1_pd(1.0); + const __m512d c13 = _mm512_set1_pd(c13_scalar); + const __m512d c12 = _mm512_set1_pd(c12_scalar); + const __m512d c11 = _mm512_set1_pd(c11_scalar); + const __m512d c10 = _mm512_set1_pd(c10_scalar); + const __m512d c9 = _mm512_set1_pd(c9_scalar); + const __m512d c8 = _mm512_set1_pd(c8_scalar); + const __m512d c7 = _mm512_set1_pd(c7_scalar); + const __m512d c6 = _mm512_set1_pd(c6_scalar); + const __m512d c5 = _mm512_set1_pd(c5_scalar); + const __m512d c4 = _mm512_set1_pd(c4_scalar); + const __m512d c3 = _mm512_set1_pd(c3_scalar); + const __m512d c2 = _mm512_set1_pd(c2_scalar); + const __m512d c1 = _mm512_set1_pd(c1_scalar); + const __m512d c0 = _mm512_set1_pd(c0_scalar); - __m512d p = c7; + __m512d p = c13; + + // # 104 MULTs, 104 ADDs + p = _mm512_fmadd_pd(p, r, c12); + p = _mm512_fmadd_pd(p, r, c11); + p = _mm512_fmadd_pd(p, r, c10); + p = _mm512_fmadd_pd(p, r, c9); + p = _mm512_fmadd_pd(p, r, c8); + p = _mm512_fmadd_pd(p, r, c7); p = _mm512_fmadd_pd(p, r, c6); p = _mm512_fmadd_pd(p, r, c5); p = _mm512_fmadd_pd(p, r, c4); @@ -204,6 +225,7 @@ static inline __m512d _avx512_exp_pd(__m512d x) { __m512d pow2n = _mm512_castsi512_pd(scaled); + // # 8 MULTs return _mm512_mul_pd(p, pow2n); } @@ -240,6 +262,26 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512d v_np = _mm512_set1_pd(n_p); + // # ADDs: n_iter * n_p * 20 + + // # n_iter * n_p * (n_p_pad / 8) * 232 + + // # n_iter * n_p * board.n_obstacles * 10 + + // # n_iter * (n_p_pad / 8) * 56 + + // # MULTs: n_iter * n_p * 4 + + // # n_iter * n_p * (n_p_pad / 8) * 320 + + // # n_iter * n_p * board.n_obstacles * 13 + + // # n_iter * (n_p_pad / 8) * 120 + + // # DIVs: n_iter * n_p * (n_p_pad / 8) * 8 + + // # n_iter * n_p * board.n_obstacles + + // # n_iter * (n_p_pad / 8) * 16 + + // # SQRTs: n_iter * n_p * (n_p_pad / 8) * 24 + + // # n_iter * n_p * board.n_obstacles + + // # n_iter * (n_p_pad / 8) * 24 + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { @@ -251,6 +293,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c double curr_vel_x_i = curr_vel_x[i]; double curr_vel_y_i = curr_vel_y[i]; + // # 4 MULTs, 2 ADDs double f_alpha_dt_x = (e_x_i * desired_v0_i - curr_vel_x_i) * dt_times_tau_alpha_inv; double f_alpha_dt_y = (e_y_i * desired_v0_i - curr_vel_y_i) * dt_times_tau_alpha_inv; @@ -264,6 +307,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d acc_y = _mm512_setzero_pd(); for (int j = 0; j < n_p_pad; j += 8) { + + // # 8 ADDs __m512d j_idx = _mm512_add_pd(_mm512_set1_pd((double)j), lane_off); __mmask8 m_neq_i = _mm512_cmp_pd_mask(j_idx, v_i, _CMP_NEQ_OQ); @@ -276,35 +321,63 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d sbe_jy = _mm512_loadu_pd(&sb_e_y[j]); __m512d sb_jsq = _mm512_loadu_pd(&sb_sq[j]); + // # 16 ADDs __m512d r_ab_x = _mm512_sub_pd(pix, pjx); __m512d r_ab_y = _mm512_sub_pd(piy, pjy); + + // # 16 MULTs, 8 ADDs __m512d r_ab_n2 = _mm512_fmadd_pd(r_ab_x, r_ab_x, _mm512_mul_pd(r_ab_y, r_ab_y)); + + // # 8 SQRTs __m512d r_ab_n = _mm512_sqrt_pd(r_ab_n2); + // # 16 ADDs __m512d r_ab2_x = _mm512_sub_pd(r_ab_x, sbe_jx); __m512d r_ab2_y = _mm512_sub_pd(r_ab_y, sbe_jy); + + // # 16 MULTs, 8 ADDs __m512d r_ab2_n2 = _mm512_fmadd_pd(r_ab2_x, r_ab2_x, _mm512_mul_pd(r_ab2_y, r_ab2_y)); + + // # 8 SQRTs __m512d r_ab2_n = _mm512_sqrt_pd(r_ab2_n2); + // # 8 ADDs __m512d r_sum = _mm512_add_pd(r_ab_n, r_ab2_n); + + // # 8 MULTs, 8 ADDs __m512d two_b_sq = _mm512_fmsub_pd(r_sum, r_sum, sb_jsq); + + // # 8 SQRTs __m512d two_b = _mm512_sqrt_pd(two_b_sq); + // # 8 MULTs __m512d e_arg = _mm512_mul_pd(two_b, v_neg_2s_inv); + + // # 128 MULTs, 112 ADDs __m512d e_val = _avx512_exp_pd(e_arg); + // # 32 MULTs __m512d numer = _mm512_mul_pd(_mm512_mul_pd(r_sum, v_neg_v0_div_2s), e_val); __m512d denom = _mm512_mul_pd(_mm512_mul_pd(two_b, r_ab_n), r_ab2_n); + + // # 8 DIVs __m512d smc = _mm512_div_pd(numer, denom); + // # 16 MULTs __m512d sm_r_ab = _mm512_mul_pd(smc, r_ab2_n); __m512d sm_r_ab2 = _mm512_mul_pd(smc, r_ab_n); + // # 32 MULTs, 16 ADDs __m512d f_x = _mm512_fmadd_pd(r_ab2_x, sm_r_ab2, _mm512_mul_pd(r_ab_x, sm_r_ab)); __m512d f_y = _mm512_fmadd_pd(r_ab2_y, sm_r_ab2, _mm512_mul_pd(r_ab_y, sm_r_ab)); + // # 24 MULTs, 8 ADDs __m512d a = _mm512_mul_pd(_mm512_fmadd_pd(eix, f_x, _mm512_mul_pd(eiy, f_y)), v_cos_phi_inv); + + // # 8 MULTs __m512d a_sq = _mm512_mul_pd(a, a); + + // # 16 MULTs, 8 ADDs __m512d b_sq = _mm512_fmadd_pd(f_x, f_x, _mm512_mul_pd(f_y, f_y)); __mmask8 k_ge = _mm512_cmp_pd_mask(a, v_zero, _CMP_GE_OQ); @@ -314,18 +387,23 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d f_x_safe = _mm512_maskz_mov_pd(active, f_x); __m512d f_y_safe = _mm512_maskz_mov_pd(active, f_y); + + // # 16 MULTs, 16 ADDs acc_x = _mm512_fmadd_pd(f_x_safe, w_val, acc_x); acc_y = _mm512_fmadd_pd(f_y_safe, w_val, acc_y); } + // # 16 ADDS f_alpha_dt_x += _mm512_reduce_add_pd(acc_x); f_alpha_dt_y += _mm512_reduce_add_pd(acc_y); for (int k = 0; k < board.n_obstacles; k++) { + // # 2 ADDs double v1_x = pos_x_i - board.obstacles[k].start_x; double v1_y = pos_y_i - board.obstacles[k].start_y; + // # 3 MULTs, 1 ADD double projection = (v1_x * board.obstacles[k].obstacle_v_x + v1_y * board.obstacles[k].obstacle_v_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0) @@ -333,11 +411,17 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0) projection = 1.0; + // # 4 ADDs, 2 MULTs double r_aB_x = pos_x_i - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); double r_aB_y = pos_y_i - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); + + // # 2 MULTs, 1 EXP, 1 DIV double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; + + // # 2 MULTs double f_alpha_B_neg_x = r_aB_x * scalar_mult; double f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -346,10 +430,12 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTs, 2 ADDs f_alpha_dt_x += f_alpha_B_neg_x * w_val; f_alpha_dt_y += f_alpha_B_neg_y * w_val; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } @@ -367,19 +453,27 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m256i color_i_vec = _mm256_loadu_si256((__m256i *)&color[i]); + // # 16 MULTs, 8 ADDs __m512d w_norm_sq = _mm512_fmadd_pd(w_x_i_vec, w_x_i_vec, _mm512_mul_pd(w_y_i_vec, w_y_i_vec)); + + // # 8 SQRTs __m512d w_norm = _mm512_sqrt_pd(w_norm_sq); + + // # 8 DIVs __m512d correction_factor = _mm512_min_pd(_mm512_set1_pd(1.0), _mm512_div_pd(v_max_i_vec, w_norm)); + // # 16 MULTs __m512d curr_vel_x_i_vec = _mm512_mul_pd(w_x_i_vec, correction_factor); __m512d curr_vel_y_i_vec = _mm512_mul_pd(w_y_i_vec, correction_factor); __m512d dt_vec = _mm512_set1_pd(dt); + + // # 16 MULTs, 16 ADDs pos_x_i_vec = _mm512_fmadd_pd(curr_vel_x_i_vec, dt_vec, pos_x_i_vec); pos_y_i_vec = _mm512_fmadd_pd(curr_vel_y_i_vec, dt_vec, pos_y_i_vec); __mmask8 cond_1_left = _mm256_cmpeq_epi32_mask(color_i_vec, _mm256_setzero_si256()); - __mmask8 cond_1_right = _mm512_cmp_pd_mask(pos_x_i_vec, _mm512_set1_pd(BOARD_LENGTH - 1.5), _CMP_GT_OQ); + __mmask8 cond_1_right = _mm512_cmp_pd_mask(pos_x_i_vec, _mm512_set1_pd(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); __mmask8 cond_1 = cond_1_left & cond_1_right; pos_x_i_vec = _mm512_mask_blend_pd(cond_1, pos_x_i_vec, _mm512_set1_pd(1.0)); @@ -404,16 +498,30 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i_vec = _mm512_max_pd(pos_y_i_vec, _mm512_set1_pd(WALL_EPS)); pos_y_i_vec = _mm512_min_pd(pos_y_i_vec, _mm512_set1_pd(BOARD_WIDTH_MINUS_WALL_EPS)); + // # 16 ADDs __m512d ex_vec = _mm512_sub_pd(destination_x_i_vec, pos_x_i_vec); __m512d ey_vec = _mm512_sub_pd(destination_y_i_vec, pos_y_i_vec); + + // # 16 MULTs, 8 ADDs __m512d e_norm_sq = _mm512_fmadd_pd(ex_vec, ex_vec, _mm512_mul_pd(ey_vec, ey_vec)); + + // # 8 SQRTs __m512d e_norm = _mm512_sqrt_pd(e_norm_sq); + + // # 8 DIVs __m512d e_norm_inv = _mm512_div_pd(_mm512_set1_pd(1.0), e_norm); + + // # 16 MULT __m512d e_x_i_vec = _mm512_mul_pd(ex_vec, e_norm_inv); __m512d e_y_i_vec = _mm512_mul_pd(ey_vec, e_norm_inv); + // # 16 MULTs, 8 ADDs __m512d vel_norm_sq = _mm512_fmadd_pd(curr_vel_x_i_vec, curr_vel_x_i_vec, _mm512_mul_pd(curr_vel_y_i_vec, curr_vel_y_i_vec)); + + // # 8 MULTs __m512d sb_sq_i_vec = _mm512_mul_pd(vel_norm_sq, _mm512_set1_pd(delta_t_sq)); + + // # 8 SQRTs __m512d vel_norm_dt = _mm512_sqrt_pd(sb_sq_i_vec); _mm512_storeu_pd(&curr_vel_x[i], curr_vel_x_i_vec); @@ -426,6 +534,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c _mm512_storeu_pd(&e_y[i], e_y_i_vec); _mm512_storeu_pd(&destination_y[i], destination_y_i_vec); _mm512_storeu_pd(&sb_sq[i], sb_sq_i_vec); + + // # 16 MULTs _mm512_storeu_pd(&sb_e_x[i], _mm512_mul_pd(e_x_i_vec, vel_norm_dt)); _mm512_storeu_pd(&sb_e_y[i], _mm512_mul_pd(e_y_i_vec, vel_norm_dt)); } diff --git a/variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.cpp b/variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.cpp index b6b8d2d..486512c 100644 --- a/variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.cpp +++ b/variants/variant_inlining_8_SoA_AVX512_polyexp_moreavx_ilp_2.cpp @@ -13,6 +13,8 @@ #define SEED 42 #define BOARD_WIDTH 10.0 #define BOARD_LENGTH 50.0 +#define BOARD_LENGTH_MINUS_ONE BOARD_LENGTH - 1.0 +#define BOARD_LENGTH_MINUS_ONE_POINT_FIVE BOARD_LENGTH - 1.5 #define WALL_EPS 1e-3 #define BOARD_WIDTH_MINUS_WALL_EPS 9.999 @@ -162,23 +164,42 @@ Pedestrian *init_pedestrians(int n_p) { Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } +// # 128 MULTs, 112 ADDs static inline __m512d _avx512_exp_pd(__m512d x) { const __m512d log2e = _mm512_set1_pd(1.4426950408889634); + + // # 8 MULTs __m512d nf = _mm512_roundscale_pd(_mm512_mul_pd(x, log2e), _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); const __m512d ln2 = _mm512_set1_pd(0.6931471805599453); + + // # 8 MULTs, 8 ADDs __m512d r = _mm512_fnmadd_pd(nf, ln2, x); - const __m512d c7 = _mm512_set1_pd(1.0 / 5040.0); - const __m512d c6 = _mm512_set1_pd(1.0 / 720.0); - const __m512d c5 = _mm512_set1_pd(1.0 / 120.0); - const __m512d c4 = _mm512_set1_pd(1.0 / 24.0); - const __m512d c3 = _mm512_set1_pd(1.0 / 6.0); - const __m512d c2 = _mm512_set1_pd(0.5); - const __m512d c1 = _mm512_set1_pd(1.0); - const __m512d c0 = _mm512_set1_pd(1.0); + const __m512d c13 = _mm512_set1_pd(c13_scalar); + const __m512d c12 = _mm512_set1_pd(c12_scalar); + const __m512d c11 = _mm512_set1_pd(c11_scalar); + const __m512d c10 = _mm512_set1_pd(c10_scalar); + const __m512d c9 = _mm512_set1_pd(c9_scalar); + const __m512d c8 = _mm512_set1_pd(c8_scalar); + const __m512d c7 = _mm512_set1_pd(c7_scalar); + const __m512d c6 = _mm512_set1_pd(c6_scalar); + const __m512d c5 = _mm512_set1_pd(c5_scalar); + const __m512d c4 = _mm512_set1_pd(c4_scalar); + const __m512d c3 = _mm512_set1_pd(c3_scalar); + const __m512d c2 = _mm512_set1_pd(c2_scalar); + const __m512d c1 = _mm512_set1_pd(c1_scalar); + const __m512d c0 = _mm512_set1_pd(c0_scalar); - __m512d p = c7; + __m512d p = c13; + + // # 104 MULTs, 104 ADDs + p = _mm512_fmadd_pd(p, r, c12); + p = _mm512_fmadd_pd(p, r, c11); + p = _mm512_fmadd_pd(p, r, c10); + p = _mm512_fmadd_pd(p, r, c9); + p = _mm512_fmadd_pd(p, r, c8); + p = _mm512_fmadd_pd(p, r, c7); p = _mm512_fmadd_pd(p, r, c6); p = _mm512_fmadd_pd(p, r, c5); p = _mm512_fmadd_pd(p, r, c4); @@ -204,6 +225,7 @@ static inline __m512d _avx512_exp_pd(__m512d x) { __m512d pow2n = _mm512_castsi512_pd(scaled); + // # 8 MULTs return _mm512_mul_pd(p, pow2n); } @@ -242,9 +264,30 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c const __m512d v_np = _mm512_set1_pd((float)n_p); const __m512d v_exp_min = _mm512_set1_pd(-87.0f); + // # ADDs: n_iter * n_p * 36 + + // # n_iter * n_p * (n_p_pad / 16) * 464 + + // # n_iter * n_p * board.n_obstacles * 10 + + // # n_iter * (n_p_pad / 8) * 56 + + // # MULTs: n_iter * n_p * 4 + + // # n_iter * n_p * (n_p_pad / 16) * 640 + + // # n_iter * n_p * board.n_obstacles * 13 + + // # n_iter * (n_p_pad / 8) * 120 + + // # DIVs: n_iter * n_p * (n_p_pad / 16) * 16 + + // # n_iter * n_p * board.n_obstacles + + // # n_iter * (n_p_pad / 8) * 16 + + // # SQRTs: n_iter * n_p * (n_p_pad / 16) * 48 + + // # n_iter * n_p * board.n_obstacles + + // # n_iter * (n_p_pad / 8) * 24 + + // # EXPs: n_iter * n_p * board.n_obstacles + for (int t = 0; t < n_iter; t++) { for (int i = 0; i < n_p; i++) { + double pos_x_i = pos_x[i]; double pos_y_i = pos_y[i]; double e_x_i = e_x[i]; @@ -253,6 +296,7 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c double curr_vel_x_i = curr_vel_x[i]; double curr_vel_y_i = curr_vel_y[i]; + // # 4 MULTs, 2 ADDs double f_alpha_dt_x = (e_x_i * desired_v0_i - curr_vel_x_i) * dt_times_tau_alpha_inv; double f_alpha_dt_y = (e_y_i * desired_v0_i - curr_vel_y_i) * dt_times_tau_alpha_inv; @@ -268,6 +312,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d acc_y_1 = _mm512_setzero_pd(); for (int j = 0; j < n_p_pad; j += 16) { + + // # 16 ADDs __m512d j_idx_0 = _mm512_add_pd(_mm512_set1_pd((double)j), lane_off); __m512d j_idx_1 = _mm512_add_pd(_mm512_set1_pd((double)j + 8), lane_off); @@ -295,81 +341,105 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d sb_jsq_0 = _mm512_loadu_pd(&sb_sq[j]); __m512d sb_jsq_1 = _mm512_loadu_pd(&sb_sq[j + 8]); + // # 16 ADDs __m512d r_ab_x_0 = _mm512_sub_pd(pix, pjx_0); __m512d r_ab_x_1 = _mm512_sub_pd(pix, pjx_1); + // # 16 ADDs __m512d r_ab_y_0 = _mm512_sub_pd(piy, pjy_0); __m512d r_ab_y_1 = _mm512_sub_pd(piy, pjy_1); + // # 32 MULTs, 16 ADDs __m512d r_ab_n2_0 = _mm512_fmadd_pd(r_ab_x_0, r_ab_x_0, _mm512_mul_pd(r_ab_y_0, r_ab_y_0)); __m512d r_ab_n2_1 = _mm512_fmadd_pd(r_ab_x_1, r_ab_x_1, _mm512_mul_pd(r_ab_y_1, r_ab_y_1)); + // # 16 SQRTs __m512d r_ab_n_0 = _mm512_sqrt_pd(r_ab_n2_0); __m512d r_ab_n_1 = _mm512_sqrt_pd(r_ab_n2_1); + // # 16 ADDs __m512d r_ab2_x_0 = _mm512_sub_pd(r_ab_x_0, sbe_jx_0); __m512d r_ab2_x_1 = _mm512_sub_pd(r_ab_x_1, sbe_jx_1); + // # 16 ADDs __m512d r_ab2_y_0 = _mm512_sub_pd(r_ab_y_0, sbe_jy_0); __m512d r_ab2_y_1 = _mm512_sub_pd(r_ab_y_1, sbe_jy_1); + // # 32 MULTs, 16 ADDs __m512d r_ab2_n2_0 = _mm512_fmadd_pd(r_ab2_x_0, r_ab2_x_0, _mm512_mul_pd(r_ab2_y_0, r_ab2_y_0)); __m512d r_ab2_n2_1 = _mm512_fmadd_pd(r_ab2_x_1, r_ab2_x_1, _mm512_mul_pd(r_ab2_y_1, r_ab2_y_1)); + // # 16 SQRTs __m512d r_ab2_n_0 = _mm512_sqrt_pd(r_ab2_n2_0); __m512d r_ab2_n_1 = _mm512_sqrt_pd(r_ab2_n2_1); + // # 16 ADDs __m512d r_sum_0 = _mm512_add_pd(r_ab_n_0, r_ab2_n_0); __m512d r_sum_1 = _mm512_add_pd(r_ab_n_1, r_ab2_n_1); + // # 16 MULTs, 16 ADDs __m512d two_b_sq_0 = _mm512_fmsub_pd(r_sum_0, r_sum_0, sb_jsq_0); __m512d two_b_sq_1 = _mm512_fmsub_pd(r_sum_1, r_sum_1, sb_jsq_1); + // # 16 SQRTs __m512d two_b_0 = _mm512_sqrt_pd(two_b_sq_0); __m512d two_b_1 = _mm512_sqrt_pd(two_b_sq_1); + // # 16 MULTs __m512d e_arg_0 = _mm512_mul_pd(two_b_0, v_neg_2s_inv); __m512d e_arg_1 = _mm512_mul_pd(two_b_1, v_neg_2s_inv); e_arg_0 = _mm512_max_pd(e_arg_0, v_exp_min); e_arg_1 = _mm512_max_pd(e_arg_1, v_exp_min); + // # 256 MULTs, 224 ADDs __m512d e_val_0 = _avx512_exp_pd(e_arg_0); __m512d e_val_1 = _avx512_exp_pd(e_arg_1); + // # 32 MULTs __m512d numer_0 = _mm512_mul_pd(_mm512_mul_pd(r_sum_0, v_neg_v0_div_2s), e_val_0); __m512d numer_1 = _mm512_mul_pd(_mm512_mul_pd(r_sum_1, v_neg_v0_div_2s), e_val_1); + // # 32 MULTs __m512d denom_0 = _mm512_mul_pd(_mm512_mul_pd(two_b_0, r_ab_n_0), r_ab2_n_0); __m512d denom_1 = _mm512_mul_pd(_mm512_mul_pd(two_b_1, r_ab_n_1), r_ab2_n_1); __m512d denom_safe_0 = _mm512_mask_blend_pd(active_0, v_one, denom_0); __m512d denom_safe_1 = _mm512_mask_blend_pd(active_1, v_one, denom_1); + // # 16 DIVs __m512d smc_0 = _mm512_div_pd(numer_0, denom_safe_0); __m512d smc_1 = _mm512_div_pd(numer_1, denom_safe_1); + // # 16 MULTs __m512d sm_r_ab_0 = _mm512_mul_pd(smc_0, r_ab2_n_0); __m512d sm_r_ab_1 = _mm512_mul_pd(smc_1, r_ab2_n_1); + // # 16 MULTs __m512d sm_r_ab2_0 = _mm512_mul_pd(smc_0, r_ab_n_0); __m512d sm_r_ab2_1 = _mm512_mul_pd(smc_1, r_ab_n_1); + // # 32 MULTs, 16 ADDs __m512d f_x_0 = _mm512_fmadd_pd(r_ab2_x_0, sm_r_ab2_0, _mm512_mul_pd(r_ab_x_0, sm_r_ab_0)); __m512d f_x_1 = _mm512_fmadd_pd(r_ab2_x_1, sm_r_ab2_1, _mm512_mul_pd(r_ab_x_1, sm_r_ab_1)); + // # 32 MULTs, 16 ADDs __m512d f_y_0 = _mm512_fmadd_pd(r_ab2_y_0, sm_r_ab2_0, _mm512_mul_pd(r_ab_y_0, sm_r_ab_0)); __m512d f_y_1 = _mm512_fmadd_pd(r_ab2_y_1, sm_r_ab2_1, _mm512_mul_pd(r_ab_y_1, sm_r_ab_1)); + // # 32 MULTs, 16 ADDs __m512d f_norm_sq_0 = _mm512_fmadd_pd(f_x_0, f_x_0, _mm512_mul_pd(f_y_0, f_y_0)); __m512d f_norm_sq_1 = _mm512_fmadd_pd(f_x_1, f_x_1, _mm512_mul_pd(f_y_1, f_y_1)); + // # 16 MULTs __m512d rhs_0 = _mm512_mul_pd(f_norm_sq_0, v_cos_phi_sq); __m512d rhs_1 = _mm512_mul_pd(f_norm_sq_1, v_cos_phi_sq); + // # 32 MULTs, 16 ADDs __m512d e_dot_f_0 = _mm512_fmadd_pd(eix, f_x_0, _mm512_mul_pd(eiy, f_y_0)); __m512d e_dot_f_1 = _mm512_fmadd_pd(eix, f_x_1, _mm512_mul_pd(eiy, f_y_1)); + // # 16 MULTs __m512d e_dot_f_sq_0 = _mm512_mul_pd(e_dot_f_0, e_dot_f_0); __m512d e_dot_f_sq_1 = _mm512_mul_pd(e_dot_f_1, e_dot_f_1); @@ -391,21 +461,26 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m512d f_y_safe_0 = _mm512_maskz_mov_pd(active_0, f_y_0); __m512d f_y_safe_1 = _mm512_maskz_mov_pd(active_1, f_y_1); + // # 16 MULTs, 16 ADDs acc_x_0 = _mm512_fmadd_pd(f_x_safe_0, w_val_0, acc_x_0); acc_x_1 = _mm512_fmadd_pd(f_x_safe_1, w_val_1, acc_x_1); + // # 16 MULTs, 16 ADDs acc_y_0 = _mm512_fmadd_pd(f_y_safe_0, w_val_0, acc_y_0); acc_y_1 = _mm512_fmadd_pd(f_y_safe_1, w_val_1, acc_y_1); } + // # 32 ADDs f_alpha_dt_x += _mm512_reduce_add_pd(_mm512_add_pd(acc_x_0, acc_x_1)); f_alpha_dt_y += _mm512_reduce_add_pd(_mm512_add_pd(acc_y_0, acc_y_1)); for (int k = 0; k < board.n_obstacles; k++) { + // # 2 ADDs double v1_x = pos_x_i - board.obstacles[k].start_x; double v1_y = pos_y_i - board.obstacles[k].start_y; + // # 3 MULTs, 1 ADD double projection = (v1_x * board.obstacles[k].obstacle_v_x + v1_y * board.obstacles[k].obstacle_v_y) * board.obstacles[k].length_sq_inv; if (projection < 0.0) @@ -413,11 +488,17 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c else if (projection > 1.0) projection = 1.0; + // # 4 ADDs, 2 MULTs double r_aB_x = pos_x_i - (board.obstacles[k].start_x + board.obstacles[k].obstacle_v_x * projection); double r_aB_y = pos_y_i - (board.obstacles[k].start_y + board.obstacles[k].obstacle_v_y * projection); + // # 1 SQRT, 2 MULTs, 1 ADD double r_aB_norm = sqrt(r_aB_x * r_aB_x + r_aB_y * r_aB_y); + + // # 2 MULTs, 1 EXP, 1 DIV double scalar_mult = (NEG_U0_AB_div_R * exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; + + // # 2 MULTs double f_alpha_B_neg_x = r_aB_x * scalar_mult; double f_alpha_B_neg_y = r_aB_y * scalar_mult; @@ -426,10 +507,12 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c if (e_dot_f < cos_phi) w_val = c_neg_times_dt; + // # 2 MULTs, 2 ADDs f_alpha_dt_x += f_alpha_B_neg_x * w_val; f_alpha_dt_y += f_alpha_B_neg_y * w_val; } + // # 2 ADDs w_x[i] += f_alpha_dt_x; w_y[i] += f_alpha_dt_y; } @@ -447,19 +530,27 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c __m256i color_i_vec = _mm256_loadu_si256((__m256i *)&color[i]); + // # 16 MULTs, 8 ADDs __m512d w_norm_sq = _mm512_fmadd_pd(w_x_i_vec, w_x_i_vec, _mm512_mul_pd(w_y_i_vec, w_y_i_vec)); + + // # 8 SQRTs __m512d w_norm = _mm512_sqrt_pd(w_norm_sq); + + // # 8 DIVs __m512d correction_factor = _mm512_min_pd(_mm512_set1_pd(1.0), _mm512_div_pd(v_max_i_vec, w_norm)); + // # 16 MULTs __m512d curr_vel_x_i_vec = _mm512_mul_pd(w_x_i_vec, correction_factor); __m512d curr_vel_y_i_vec = _mm512_mul_pd(w_y_i_vec, correction_factor); __m512d dt_vec = _mm512_set1_pd(dt); + + // # 16 MULTs, 16 ADDs pos_x_i_vec = _mm512_fmadd_pd(curr_vel_x_i_vec, dt_vec, pos_x_i_vec); pos_y_i_vec = _mm512_fmadd_pd(curr_vel_y_i_vec, dt_vec, pos_y_i_vec); __mmask8 cond_1_left = _mm256_cmpeq_epi32_mask(color_i_vec, _mm256_setzero_si256()); - __mmask8 cond_1_right = _mm512_cmp_pd_mask(pos_x_i_vec, _mm512_set1_pd(BOARD_LENGTH - 1.5), _CMP_GT_OQ); + __mmask8 cond_1_right = _mm512_cmp_pd_mask(pos_x_i_vec, _mm512_set1_pd(BOARD_LENGTH_MINUS_ONE_POINT_FIVE), _CMP_GT_OQ); __mmask8 cond_1 = cond_1_left & cond_1_right; pos_x_i_vec = _mm512_mask_blend_pd(cond_1, pos_x_i_vec, _mm512_set1_pd(1.0)); @@ -484,16 +575,30 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c pos_y_i_vec = _mm512_max_pd(pos_y_i_vec, _mm512_set1_pd(WALL_EPS)); pos_y_i_vec = _mm512_min_pd(pos_y_i_vec, _mm512_set1_pd(BOARD_WIDTH_MINUS_WALL_EPS)); + // # 16 ADDs __m512d ex_vec = _mm512_sub_pd(destination_x_i_vec, pos_x_i_vec); __m512d ey_vec = _mm512_sub_pd(destination_y_i_vec, pos_y_i_vec); + + // # 16 MULTs, 8 ADDs __m512d e_norm_sq = _mm512_fmadd_pd(ex_vec, ex_vec, _mm512_mul_pd(ey_vec, ey_vec)); + + // # 8 SQRTs __m512d e_norm = _mm512_sqrt_pd(e_norm_sq); + + // # 8 DIVs __m512d e_norm_inv = _mm512_div_pd(_mm512_set1_pd(1.0), e_norm); + + // # 16 MULT __m512d e_x_i_vec = _mm512_mul_pd(ex_vec, e_norm_inv); __m512d e_y_i_vec = _mm512_mul_pd(ey_vec, e_norm_inv); + // # 16 MULTs, 8 ADDs __m512d vel_norm_sq = _mm512_fmadd_pd(curr_vel_x_i_vec, curr_vel_x_i_vec, _mm512_mul_pd(curr_vel_y_i_vec, curr_vel_y_i_vec)); + + // # 8 MULTs __m512d sb_sq_i_vec = _mm512_mul_pd(vel_norm_sq, _mm512_set1_pd(delta_t_sq)); + + // # 8 SQRTs __m512d vel_norm_dt = _mm512_sqrt_pd(sb_sq_i_vec); _mm512_storeu_pd(&curr_vel_x[i], curr_vel_x_i_vec); @@ -506,6 +611,8 @@ int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, c _mm512_storeu_pd(&e_y[i], e_y_i_vec); _mm512_storeu_pd(&destination_y[i], destination_y_i_vec); _mm512_storeu_pd(&sb_sq[i], sb_sq_i_vec); + + // # 16 MULTs _mm512_storeu_pd(&sb_e_x[i], _mm512_mul_pd(e_x_i_vec, vel_norm_dt)); _mm512_storeu_pd(&sb_e_y[i], _mm512_mul_pd(e_y_i_vec, vel_norm_dt)); } diff --git a/variants/variant_lut_exp_flopcount.cpp b/variants/variant_lut_exp_flopcount.cpp deleted file mode 100644 index 86036ef..0000000 --- a/variants/variant_lut_exp_flopcount.cpp +++ /dev/null @@ -1,492 +0,0 @@ -// This file is copied from the variant_baseline and then modified -// The goal is that we use a look up table to precompute expensive calculations -// especially to precompute the exp calls -// we call exp(two_b * NEG_TWO_SIGMA_INV) -// which is always negativ -// if a value is smaller then -40 its so small that we can ignore it -// for the other values we will create a lookup table - -#include "../headers/board.hpp" -#include "../headers/constants.hpp" -#include "../headers/pedestrian.hpp" -#include "../headers/vector2d.hpp" - -#include -#include -#include -#include -#include // keep — forces code uses unqualified sqrt/exp -#include -#include -#include - -#define SEED 42 -#define BOARD_WIDTH 10.0 -#define BOARD_LENGTH 50.0 -#define WALL_EPS 1e-3 -#define BOARD_WIDTH_MINUS_WALL_EPS 9.999 - -#ifndef RANDOM_SEED -#define RANDOM_SEED 0 -#endif - -#ifndef BENCHMARK -#define BENCHMARK 0 -#endif - -using namespace std; - -// exp lookup table -constexpr int EXP_LUT_SIZE = 69000; -constexpr double EXP_LUT_MIN = -40.0; -constexpr double EXP_LUT_MAX = 0.0; -constexpr double EXP_LUT_INV_STEP = (EXP_LUT_SIZE - 1) / (EXP_LUT_MAX - EXP_LUT_MIN); - -static double exp_lut[EXP_LUT_SIZE]; - -struct ExpLutInit { - ExpLutInit() { - const double step = (EXP_LUT_MAX - EXP_LUT_MIN) / (EXP_LUT_SIZE - 1); - for (int i = 0; i < EXP_LUT_SIZE; i++) { - exp_lut[i] = std::exp(EXP_LUT_MIN + i * step); - } - } -}; -static ExpLutInit exp_lut_init_helper; -// i used the following things to get a better understanding of how i can solve that problem -// Tang, P. T. P. (1989). "Table-driven implementation of the exponential function in IEEE floating-point arithmetic." ACM TOMS 15(2), 144-157. -// https://dl.acm.org/doi/10.1145/63522.214389 -// Cubic Hermite spline. Wikipedia. -// https://en.wikipedia.org/wiki/Cubic_Hermite_spline -inline double fast_exp(double x) { - if (x <= EXP_LUT_MIN) - return 0.0; - if (x >= EXP_LUT_MAX) - return 1.0; - - double pos = (x - EXP_LUT_MIN) * EXP_LUT_INV_STEP; - int idx = static_cast(pos); - if (idx >= EXP_LUT_SIZE - 1) - return 1.0; - - double t = pos - idx; - double y0 = exp_lut[idx]; - double y1 = exp_lut[idx + 1]; - - // For exp(x), derivative equals function value. - // In normalized parameter space (t in [0,1] across width h), - // dy/dt = h * y. No centered differences needed - we know the exact slope. - constexpr double h_step = (EXP_LUT_MAX - EXP_LUT_MIN) / (EXP_LUT_SIZE - 1); - - double t2 = t * t; - double t3 = t2 * t; - - double h00 = 2.0 * t3 - 3.0 * t2 + 1.0; - double h10 = t3 - 2.0 * t2 + t; - double h01 = -2.0 * t3 + 3.0 * t2; - double h11 = t3 - t2; - - return y0 * (h00 + h_step * h10) + y1 * (h01 + h_step * h11); -} -// forces.cpp - -// Flop count: 2 ADDs, 5 MULTs, 1 SQRT -double w(Vector2d e, Vector2d f) { - if (e.dot(f) >= f.norm() * cos_phi) - return dt_neg; - else - return c_neg_times_dt; -} - -// Flop count: 2 ADDs, 4 MULTs -Vector2d acceleration_term_dt(const Pedestrian &alpha) { - - // # 2 MULTs - Vector2d desired_velocity = alpha.e * alpha.desired_v0; - - Vector2d actual_velocity = alpha.curr_vel; - - // # 2 ADDs, 2 MULTs - Vector2d f_alpha_0_dt = (desired_velocity - actual_velocity) * dt_times_tau_alpha_inv; - - return f_alpha_0_dt; -} - -// Flop count: 12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP -Vector2d pedestrian_repulsion_dt(const Pedestrian &alpha, const Pedestrian &beta) { - - // # 2 ADDs - // $\vec{r}_{\alpha\beta}$ - Vector2d r_ab = alpha.curr_pos - beta.curr_pos; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert$ - double r_ab_norm = r_ab.norm(); - r_ab_norm = r_ab_norm > EPS ? r_ab_norm : EPS; // Avoid division by zero - - // $v_\beta \Delta t \vec{e}_\beta$ - Vector2d s_b_e = beta.s_b_e; - - // # 2 ADDs - // $\vec{r}_{\alpha\beta} - \vec{v}_{\beta}\varDelta_{t}\vec{e}_{\beta}$ - Vector2d r_ab2 = r_ab - s_b_e; - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab2_norm = r_ab2.norm(); - r_ab2_norm = r_ab2_norm > EPS ? r_ab2_norm : EPS; // Avoid division by zero - - // # 1 ADD - // $\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert$ - double r_ab_norm_sum = r_ab_norm + r_ab2_norm; - - // # 1 ADD, 1 MULTs, 1 SQRT - // - // $2b = \sqrt{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)^2 - (v_\beta \Delta t)^2}$ - // - double two_b_sq = r_ab_norm_sum * r_ab_norm_sum - beta.s_b_sq; - two_b_sq = two_b_sq > EPS_SQ ? two_b_sq : EPS_SQ; // Avoid sqrt of negative and avoid division by zero - double two_b = sqrt(two_b_sq); - - // # 5 MULTs, 1 DIV, 1 EXP - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_common = (r_ab_norm_sum / (two_b * r_ab_norm * r_ab2_norm)) * (NEG_V0_AB_div_TWO_SIGMA * fast_exp(two_b * NEG_TWO_SIGMA_INV)); - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}$ - // - double scalar_mult_r_ab = scalar_mult_common * r_ab2_norm; - - // # 1 MULT - // - // $\frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}$ - // - double scalar_mult_r_ab2 = scalar_mult_common * r_ab_norm; - - // # 2 ADDs, 4 MULTs - // - // $\vec{-f}_{\alpha \beta}(\vec{r}_{\alpha \beta}) = \frac{-V^0_{\alpha \beta}}{2\sigma}e^{-b / \sigma} 2\grad_{\vec{r}_{\alpha \beta}}b $ - // - // - // $= \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{-V^0_{\alpha\beta}}{2\sigma}\,e^{-b/\sigma}\,\frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - - // v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - // - // $2\grad_{\vec{r}_{\alpha \beta}}b = \frac{(\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert)(\frac{\vec{r}_{\alpha \beta}}{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert} + \frac{\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta}{\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert})}{2b}$ - // - // - // $= \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert}\,\vec{r}_{\alpha \beta} + \frac{\Arrowvert \vec{r}_{\alpha \beta} \Arrowvert + \Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}{2b\,\Arrowvert \vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta \Arrowvert}\,(\vec{r}_{\alpha \beta} - v_\beta \Delta t \vec{e}_\beta)$ - // - // - Vector2d f_alpha_beta_neg = r_ab * scalar_mult_r_ab + r_ab2 * scalar_mult_r_ab2; - - // # 2 ADDs, 7 MULTs, 1 SQRT - // - // $\vec{F}_{\alpha\beta}(\vec{e}_{\alpha}, \vec{r}_{\alpha \beta}).dt = w(\vec{e}_{\alpha}, -\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta})).\vec{f}_{\alpha\beta}(\vec{r}_{\alpha \beta}).dt$ - // - Vector2d f_alpha_beta_dt = f_alpha_beta_neg * w(alpha.e, f_alpha_beta_neg); - - return f_alpha_beta_dt; -} - -// Flop count: 10 ADDs, 18 MULTs, 1 DIVs, 2 SQRTs, 1 EXP -Vector2d obstacle_repulsion_dt(const Pedestrian &alpha, const Obstacle &B) { - - // # 7 ADDs, 5 MULTs - // $\vec{r}_{\alpha B}$ - Vector2d r_aB = alpha.curr_pos - B.find_nearest_obstacle_point(alpha.curr_pos); - - // # 1 ADD, 2 MULTs, 1 SQRT - // $\Arrowvert \vec{r}_{\alpha B} \Arrowvert$ - double r_aB_norm = r_aB.norm(); - r_aB_norm = r_aB_norm > EPS ? r_aB_norm : EPS; // Avoid division by zero - - // # 2 MULTs, 1 DIV, 1 EXP - // - // $\frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R}$ - // - double scalar_mult = (NEG_U0_AB_div_R * fast_exp(r_aB_norm * NEG_R_INV)) / r_aB_norm; - - // # 2 MULTs - // - // $-\vec{F}_{\alpha B}(\vec{r}_{\alpha B}) = \frac{-U^0_{\alpha B}}{R} e^{- \Arrowvert \vec{r}_{\alpha B} \Arrowvert / R} \frac{\vec{r}_{\alpha B}}{\Arrowvert \vec{r}_{\alpha B} \Arrowvert}$ - // - Vector2d f_alpha_B_neg = r_aB * scalar_mult; - - // # 2 ADDs, 7 MULTs, 1 SQRT - // - // $\vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha B}).dt = w(\vec{e}_{\alpha}, -\vec{F}_{\alpha B}(\vec{r}_{\alpha B})).\vec{F}_{\alpha B}(\vec{r}_{\alpha B}).dt$ - // - Vector2d f_alpha_B_dt = f_alpha_B_neg * w(alpha.e, f_alpha_B_neg); - - return f_alpha_B_dt; -} - -// social_force_baseline.cpp - -Pedestrian *init_pedestrians(int n_p) { -#if RANDOM_SEED - random_device rd; // Platform-provided random seed source. - mt19937 gen(rd()); // Non-reproducible initialization. -#else - mt19937 gen(SEED); // Reproducible initialization. -#endif - - normal_distribution velocity_gen(v0_mean, v0_std_dev); - uniform_real_distribution y_gen(0.3, BOARD_WIDTH - 0.3); - uniform_real_distribution x_gen_left(2.0, BOARD_LENGTH / 2.0); - uniform_real_distribution x_gen_right(BOARD_LENGTH / 2.0, BOARD_LENGTH - 2.0); - - Pedestrian *P = new Pedestrian[n_p]; - - int n_p_half = n_p / 2; - - // Group of pedestrians walking from left to right. - for (int i = 0; i < n_p_half; i++) { - double x = x_gen_left(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(BOARD_LENGTH + 30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(WHITE, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - // Group of pedestrians walking from right to left. - for (int i = n_p_half; i < n_p; i++) { - double x = x_gen_right(gen); - double y = y_gen(gen); - - Vector2d start_pos = Vector2d(x, y); - Vector2d destination = Vector2d(-30.0, y); - - Vector2d init_e = destination - start_pos; - double init_e_norm = init_e.norm(); - Vector2d init_e_unit = init_e / init_e_norm; - - // Reject near-zero desired speeds to avoid pedestrians that barely move, - // and reject negative speeds because they would reverse the intended - // walking direction. - double desired_v0; - do { - desired_v0 = velocity_gen(gen); - } while (desired_v0 <= 0.1); - - double v_max = 1.3 * desired_v0; - - P[i] = Pedestrian(BLACK, Vector2d(), start_pos, init_e_unit, v_max, desired_v0, destination); - } - - return P; -} - -Board init_board_without_obstacles() { return Board(BOARD_LENGTH, BOARD_WIDTH, 0, nullptr); } - -int run_simulation(Pedestrian *pedestrians, Board &board, int n_p, int n_iter, const char *dump_path) { - -#if BENCHMARK - // Avoid compiler unused parameter warning - (void)dump_path; -#endif - -#if !BENCHMARK - const int dump_interval = 1; - - // Each sample stores: time step, pedestrian id, x, y, e.x, e.y, color. - vector> samples; - - if (dump_path) { - samples.reserve(static_cast(n_iter / dump_interval + 1) * n_p); - - // Store the initial state before the first simulation step. - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - samples.emplace_back(0, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } - } -#endif - - // FLOPCOUNT (everything inside the t-loop). EXP = one fast_exp() call (LUT + cubic Hermite), - // NOT a real libm exp. AoS baseline, no distance precompute, r_ab recomputed inside the j-loop. - // section costs: overhead 4 ADD 4 MULT (accel 2 ADD 4 MULT + p.w += 2 ADD) | - // ped-ped j 14 ADD 23 MULT 1 DIV 4 SQRT 1 EXP (12/23/1/4/1 + f_alpha += 2 ADD) | - // obstacle 12 ADD 18 MULT 1 DIV 2 SQRT 1 EXP (10/18/1/2/1 + f_alpha += 2 ADD) | - // update 7 ADD 16 MULT 2 DIV 3 SQRT - - // # ADDs: n_iter * ( - // # n_p * 4 + - // # n_p * (n_p - 1) * 14 + - // # n_p * board.n_obstacles * 12 + - // # n_p * 7 - // # ) - - // # MULTs: n_iter * ( - // # n_p * 4 + - // # n_p * (n_p - 1) * 23 + - // # n_p * board.n_obstacles * 18 + - // # n_p * 16 - // # ) - - // # DIVs: n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles + - // # n_p * 2 - // # ) - - // # SQRTs: n_iter * ( - // # n_p * (n_p - 1) * 4 + - // # n_p * board.n_obstacles * 2 + - // # n_p * 3 - // # ) - - // # EXPs (fast_exp, LUT): n_iter * ( - // # n_p * (n_p - 1) + - // # n_p * board.n_obstacles - // # ) - - for (int t = 0; t < n_iter; t++) { - // Pass 1: Compute all forces from the current state. - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 2 ADDs, 4 MULTs - // - // $\vec{F}_\alpha(\vec{v}_\alpha, v^0_\alpha \vec{e}_\alpha) = \frac{1}{\tau_\alpha}(v^0_\alpha \vec{e}_\alpha - \vec{v}_\alpha)$ - // - Vector2d f_alpha_dt = acceleration_term_dt(p); - - // # (N-1) * (12 ADDs, 23 MULTs, 1 DIV, 4 SQRTs, 1 EXP) - // + (N-1) * 2 ADDs - // - // $\sum_{\beta} \vec{F}_{\alpha\beta}(\vec{e}_{\alpha},\vec{r}_{\alpha} - \vec{r}_{\beta})$ - // - for (int j = 0; j < n_p; j++) { - if (i == j) - continue; - - f_alpha_dt += pedestrian_repulsion_dt(p, pedestrians[j]); - } - - // # board.n_obstacles * (10 ADDs, 18 MULTs, 1 DIV, 2 SQRTs, 1 EXP) - // + board.n_obstacles * 2 ADDs - // - // $\sum_{B} \vec{F}_{\alpha B}(\vec{e}_{\alpha}, \vec{r}_{\alpha} - \vec{r}_{B})$ - // - for (int k = 0; k < board.n_obstacles; k++) { - f_alpha_dt += obstacle_repulsion_dt(p, board.obstacles[k]); - } - - // # 2 ADDs - // - // - // $\frac{d\vec{w}_\alpha}{dt} = \vec{F}_{\alpha}(t)$ - // - p.w += f_alpha_dt; - } - - // Pass 2: Update pedestrian states after all forces have been computed. - // # (7 ADDs, 16 MULTs, 2 DIVs, 3 SQRTs) * n_p - for (int i = 0; i < n_p; i++) { - Pedestrian &p = pedestrians[i]; - - // # 1 ADD, 2 MULTs, 1 SQRT - double w_norm = p.w.norm(); - - double w_norm2 = w_norm > EPS ? w_norm : EPS; // Avoid division by zero. - - // # 1 DIV - double correction_factor = std::min(1.0, p.v_max / w_norm2); - - // # 2 MULTs - p.curr_vel = p.w * correction_factor; - - // # 2 ADDs, 2 MULTs - // - // $\vec{r}_i^{t+1} = \vec{r}_i^t + \vec{v}_i^t dt$ - // - p.curr_pos = p.curr_pos + p.curr_vel * dt; - - // When a pedestrian reaches the end of the corridor, teleport them back - // to the opposite entrance while keeping the same y-coordinate. This - // maintains a constant pedestrian flow and makes lane formation easier to - // observe. - if (p.color == WHITE && p.curr_pos.x > (BOARD_LENGTH - 1.5)) { - p.curr_pos.x = 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(p.desired_v0, 0.0); - p.curr_vel = p.w; - } - if (p.color == BLACK && p.curr_pos.x < 1.5) { - p.curr_pos.x = BOARD_LENGTH - 1.0; - p.destination.y = p.curr_pos.y; - p.w = Vector2d(-p.desired_v0, 0.0); - p.curr_vel = p.w; - } - - // Keep pedestrians inside the board boundaries. - p.curr_pos.y = clamp(p.curr_pos.y, WALL_EPS, BOARD_WIDTH_MINUS_WALL_EPS); - - // # 2 ADDs - Vector2d e = p.destination - p.curr_pos; - - // # 1 ADD, 2 MULTs, 1 DIV, 1 SQRT - double e_norm = e.norm(); - double e_norm2 = e_norm > EPS ? e_norm : EPS; // Avoid division by zero. - double e_norm_inv = 1.0 / e_norm2; - - // # 2 MULTs - p.e = e * e_norm_inv; - - // # 1 ADD, 2 MULTs - double velocity_norm_sq = p.curr_vel.dot(p.curr_vel); - - // # 1 SQRT - double velocity_norm = std::sqrt(velocity_norm_sq); - - // # 1 MULT - p.s_b_sq = velocity_norm_sq * delta_t_sq; - - // # 3 MULTs - p.s_b_e = p.e * (velocity_norm * delta_t); - -#if !BENCHMARK - // Store the current pedestrian state for visualization. - if (dump_path && (t % dump_interval == 0)) { - samples.emplace_back(t + 1, i, p.curr_pos.x, p.curr_pos.y, p.e.x, p.e.y, p.color); - } -#endif - } - } - -#if !BENCHMARK - // Export recorded simulation states to CSV. - if (dump_path) { - ofstream out(dump_path); - out << "time,id,x,y,p.e.x,p.e.y,color\n"; - - for (auto &[t, id, x, y, e_x, e_y, c] : samples) { - out << t << ',' << id << ',' << x << ',' << y << ',' << e_x << ',' << e_y << ',' << c << '\n'; - } - } -#endif - - return 0; -} \ No newline at end of file