AutoPas  3.0.0
Loading...
Searching...
No Matches
SortingThresholdBenchmark.h
Go to the documentation of this file.
1
7#pragma once
8
9#include <array>
10#include <cmath>
11#include <memory>
12#include <random>
13
17#include "autopas/utils/Timer.h"
20
21namespace autopas {
22
39 public:
47 SortingThresholdBenchmark(size_t aosSortingThresholdDefault, size_t soaSortingThresholdDefault)
48 : _soaThresholds(std::make_shared<const SortingThresholdInfoSingle>(soaSortingThresholdDefault)),
49 _aosThresholds(std::make_shared<const SortingThresholdInfoSingle>(aosSortingThresholdDefault)) {}
50
56 [[nodiscard]] std::shared_ptr<const SortingThresholdInfoInterface> getSoAThreshold() const { return _soaThresholds; }
57
63 [[nodiscard]] std::shared_ptr<const SortingThresholdInfoInterface> getAoSThreshold() const { return _aosThresholds; }
64
69 [[nodiscard]] bool hasRunSoA() const { return _hasRunSoA; }
70
75 [[nodiscard]] bool hasRunAoS() const { return _hasRunAoS; }
76
88 template <class Functor_T, class Particle_T>
89 void runSoABenchmark(Functor_T &functor, const Particle_T &defaultParticle) {
91 for (const auto &n3 : Newton3Option::getAllOptions()) {
92 for (const auto &cellDirection : SortingDirectionOption::getAllOptions()) {
93 thresholds.setThresholdByOption(
94 n3, cellDirection, /*bidirectional=*/true,
95 runSearch<Functor_T, Particle_T, true, true>(functor, defaultParticle, cellDirection, n3));
96 if (n3 == Newton3Option::disabled) {
97 thresholds.setThresholdByOption(
98 n3, cellDirection, /*bidirectional=*/false,
99 runSearch<Functor_T, Particle_T, true, false>(functor, defaultParticle, cellDirection, n3));
100 }
101 }
102 }
103 _soaThresholds = std::make_shared<const SortingThresholdInfo2B>(thresholds);
104 _hasRunSoA = true;
105 }
106
117 template <class Functor_T, class Particle_T>
118 void runAoSBenchmark(Functor_T &functor, const Particle_T &defaultParticle) {
120 for (const auto &n3 : Newton3Option::getAllOptions()) {
121 for (const auto &cellDirection : SortingDirectionOption::getAllOptions()) {
122 thresholds.setThresholdByOption(
123 n3, cellDirection, /*bidirectional=*/true,
124 runSearch<Functor_T, Particle_T, false, true>(functor, defaultParticle, cellDirection, n3));
125 if (n3 == Newton3Option::disabled) {
126 thresholds.setThresholdByOption(
127 n3, cellDirection, /*bidirectional=*/false,
128 runSearch<Functor_T, Particle_T, false, false>(functor, defaultParticle, cellDirection, n3));
129 }
130 }
131 }
132 _aosThresholds = std::make_shared<const SortingThresholdInfo2B>(thresholds);
133 _hasRunAoS = true;
134 }
135
136 private:
140 bool _hasRunSoA{false};
141
145 bool _hasRunAoS{false};
146
153 std::shared_ptr<const SortingThresholdInfoInterface> _soaThresholds;
154
161 std::shared_ptr<const SortingThresholdInfoInterface> _aosThresholds;
162
166 const size_t _iterations = 25;
167
171 const size_t _repetitions = 100;
177 const size_t _maxSoAParticles = 250;
178
187 const size_t _maxAoSParticles = 50;
188
194 const double _sortedWinMarginFraction = 0.05;
195
201 const double _requiredSortedWinRatio = 0.7;
202
206 const double _scatterFactor = 0.2;
207
229 template <class Functor_T, class Particle_T, bool useSoA, bool bidirectional>
230 size_t executeRun(Functor_T &functor, const Particle_T &defaultParticle, SortingDirectionOption cellDirection,
231 size_t numParticles, Newton3Option newton3) {
232 using BenchCell = FullParticleCell<Particle_T>;
233 using BenchCF = internal::CellFunctor<BenchCell, Functor_T, bidirectional>;
234
235 const double cutoff = functor.getCutoff();
236 const double invSqrt3 = 1. / sqrt(3.);
237 const double invSqrt2 = 1. / sqrt(2.);
238
239 auto numParticlesEqDistr = static_cast<size_t>(std::ceil(numParticles * (1. - _scatterFactor)));
240 size_t numParticlesScatter = numParticles - numParticlesEqDistr + numParticlesEqDistr % 2;
241 size_t numParticlesPerCell = numParticlesEqDistr / 2;
242
243 // Prepare for random scattering.
244 std::random_device rd;
245 std::mt19937 gen(rd());
246 std::uniform_int_distribution<size_t> distrib(0, numParticlesScatter);
247
248 // Setup Cell Layout
249 BenchCell cell1, cell2;
250
251 std::array cell1Low = {0., 0., 0.};
252 std::array cell1High = {cutoff, cutoff, cutoff};
253
254 std::array cell2Low = {0., 0., 0.};
255 std::array cell2High = {cutoff, cutoff, cutoff};
256
257 std::array sortingDirection = {0., 0., 0.};
258
259 switch (cellDirection) {
260 case SortingDirectionOption::corner:
261 cell2Low = {cutoff, cutoff, cutoff};
262 cell2High = {2. * cutoff, 2. * cutoff, 2. * cutoff};
263 sortingDirection = {invSqrt3, invSqrt3, invSqrt3};
264 break;
265 case SortingDirectionOption::edge:
266 cell2Low = {cutoff, cutoff, 0.};
267 cell2High = {2. * cutoff, 2. * cutoff, cutoff};
268 sortingDirection = {invSqrt2, invSqrt2, 0.};
269 break;
270 case SortingDirectionOption::face:
271 cell2Low = {cutoff, 0., 0.};
272 cell2High = {2 * cutoff, cutoff, cutoff};
273 sortingDirection = {1, 0., 0.};
274 break;
275 default:
276 utils::ExceptionHandler::exception("Cell Direction {} is not a valid/supported Direction!", cellDirection);
277 }
278
279 utils::Timer sortedTimer, unsortedTimer;
280 BenchCF cellFunctor{functor, cutoff, useSoA ? DataLayoutOption::soa : DataLayoutOption::aos,
281 newton3 == Newton3Option::enabled};
282 size_t sortedWins = 0;
283 // Set to 0 so whether sorting happens is controlled entirely through the sorting direction.
284 const SortingThresholdInfoSingle zeroThreshold(0);
285 cellFunctor.setSoASortingThresholds(zeroThreshold);
286 cellFunctor.setAoSSortingThresholds(zeroThreshold);
287
288 // For SoA Forces won't accumulate, as SoAExtractor is never called, for AoS they will as AoS modifies the values
289 // directly.
290 auto measureUnsorted = [&]() {
291 unsortedTimer.start();
292 for (size_t j = 0; j < _iterations; j++) {
293 if constexpr (useSoA) {
294 functor.SoALoader(cell1, cell1._particleSoABuffer, 0, false);
295 functor.SoALoader(cell2, cell2._particleSoABuffer, 0, false);
296 }
297 // A sorting direction of (0, 0, 0) disables sorting.
298 cellFunctor.processCellPair(cell1, cell2, {0., 0., 0.});
299 }
300 return unsortedTimer.stop();
301 };
302 auto measureSorted = [&]() {
303 sortedTimer.start();
304 for (size_t j = 0; j < _iterations; j++) {
305 if constexpr (useSoA) {
306 functor.SoALoader(cell1, cell1._particleSoABuffer, 0, false);
307 functor.SoALoader(cell2, cell2._particleSoABuffer, 0, false);
308 }
309 cellFunctor.processCellPair(cell1, cell2, sortingDirection);
310 }
311 return sortedTimer.stop();
312 };
313
314 for (size_t i = 0; i < _repetitions; i++) {
315 cell1.clear();
316 cell2.clear();
317
318 // Generate the number of scattered particles for cell1 and cell2
319 size_t toAddCell1 = distrib(gen);
320 // Vary the seed per repetition per cell so each repetition samples a fresh particle
321 // layout instead of repeatedly timing the exact same configuration.
322 generators::UniformGenerator::fillWithParticles(cell1, defaultParticle, cell1Low, cell1High,
323 numParticlesPerCell + toAddCell1,
324 static_cast<unsigned int>(2 * i));
325 generators::UniformGenerator::fillWithParticles(cell2, defaultParticle, cell2Low, cell2High,
326 numParticlesPerCell + numParticlesScatter - toAddCell1,
327 static_cast<unsigned int>(2 * i + 1));
328
329 long unsortedDelta = 0;
330 long sortedDelta = 0;
331 // Alternate which path is measured first. Whichever path runs second inherits warm caches and a settled
332 // branch predictor from the first, which would otherwise make it look systematically faster than it is.
333 if (i % 2 == 0) {
334 unsortedDelta = measureUnsorted();
335 sortedDelta = measureSorted();
336 } else {
337 sortedDelta = measureSorted();
338 unsortedDelta = measureUnsorted();
339 }
340
341 AutoPasLog(TRACE, "SortingThresholdBenchmark rep {}/{} cell direction={} n={}: unsorted={}ns sorted={}ns", i + 1,
342 _repetitions, cellDirection, numParticles, unsortedDelta, sortedDelta);
343
344 // A repetition only counts as a "sorted win" if it clears the margin: see _sortedWinMarginFraction.
345 if (static_cast<double>(sortedDelta) < static_cast<double>(unsortedDelta) * (1. - _sortedWinMarginFraction)) {
346 ++sortedWins;
347 }
348 }
349
350 const long meanSorted = sortedTimer.getTotalTime() / static_cast<long>(_repetitions);
351 const long meanUnsorted = unsortedTimer.getTotalTime() / static_cast<long>(_repetitions);
352 AutoPasLog(TRACE,
353 "SortingThresholdBenchmark cell direction={} n={}: mean unsorted={}ns mean sorted={}ns sortedWins={}/{}",
354 cellDirection, numParticles, meanUnsorted, meanSorted, sortedWins, _repetitions);
355 return sortedWins;
356 }
357
373 template <class Functor_T, class Particle_T, bool useSoA, bool bidirectional>
374 size_t runSearch(Functor_T &functor, const Particle_T &defaultParticle, SortingDirectionOption cellDirection,
375 Newton3Option newton3) {
376 size_t lowCount = 0;
377 const size_t upperBound = useSoA ? _maxSoAParticles : _maxAoSParticles;
378 size_t highCount = upperBound;
379
380 while (lowCount < highCount) {
381 size_t mid = lowCount + (highCount - lowCount) / 2;
382
383 const auto outcome = executeRun<Functor_T, Particle_T, useSoA, bidirectional>(functor, defaultParticle,
384 cellDirection, mid, newton3);
385 const double winRatio = static_cast<double>(outcome) / static_cast<double>(_repetitions);
386
387 // Conservative decision rule: only accept "sorted wins" once a clear majority of repetitions
388 // agree by a clear margin (see _sortedWinMarginFraction and _requiredSortedWinRatio).
389 if (winRatio >= _requiredSortedWinRatio) {
390 highCount = mid;
391 AutoPasLog(TRACE, "SortingThresholdBenchmark search {} cell direction={} n={}: sorted won {}/{} reps → high={}",
392 newton3, cellDirection, mid, outcome, _repetitions, highCount);
393 } else {
394 lowCount = mid + 1;
395 AutoPasLog(TRACE,
396 "SortingThresholdBenchmark search {} cell direction={} n={}: sorted won only {}/{} reps → low={}",
397 newton3, cellDirection, mid, outcome, _repetitions, lowCount);
398 }
399 }
400 AutoPasLog(DEBUG, "SortingThresholdBenchmark {} cell direction={} threshold={}", newton3, cellDirection, lowCount);
401 // If the threshold does not converge we disable the threshold by setting it to infinity.
402 if (lowCount == upperBound) {
403 return std::numeric_limits<unsigned long>::max();
404 }
405 return lowCount;
406 }
407};
408
409} // namespace autopas
constexpr std::enable_if_t< std::is_floating_point_v< T >, T > sqrt(T x, T epsilon)
Calculates the square root of floating point values x based on Newton-Raphson methon.
Definition: ConstexprMath.h:22
#define AutoPasLog(lvl, fmt,...)
Macro for logging providing common meta information without filename.
Definition: Logger.h:24
Determines the per-Newton3-state, per-direction-type particle-count threshold at which using a sorted...
Definition: SortingThresholdBenchmark.h:38
void runAoSBenchmark(Functor_T &functor, const Particle_T &defaultParticle)
Runs the micro-benchmark for the AoS sorting threshold, sweeping both Newton3 (and bidirectional for ...
Definition: SortingThresholdBenchmark.h:118
std::shared_ptr< const SortingThresholdInfoInterface > getAoSThreshold() const
Return all per-Newton3-state, per-direction-type AoS pair-sorting thresholds if hasRunAoS() is true,...
Definition: SortingThresholdBenchmark.h:63
bool hasRunAoS() const
Returns whether runAoSBenchmark() has already been called.
Definition: SortingThresholdBenchmark.h:75
std::shared_ptr< const SortingThresholdInfoInterface > getSoAThreshold() const
Return all per-Newton3-state, per-direction-type SoA sorting thresholds if hasRunSoA() is true,...
Definition: SortingThresholdBenchmark.h:56
SortingThresholdBenchmark(size_t aosSortingThresholdDefault, size_t soaSortingThresholdDefault)
Constructs a benchmark whose thresholds default to the given uniform values until/unless a benchmark ...
Definition: SortingThresholdBenchmark.h:47
void runSoABenchmark(Functor_T &functor, const Particle_T &defaultParticle)
Runs the micro-benchmark for the SoA sorting threshold, sweeping both Newton3 (and bidirectional for ...
Definition: SortingThresholdBenchmark.h:89
bool hasRunSoA() const
Returns whether runSoABenchmark() has already been called.
Definition: SortingThresholdBenchmark.h:69
static void exception(const Exception e)
Handle an exception derived by std::exception.
Definition: ExceptionHandler.h:64
void fillWithParticles(Container &container, const Particle &defaultParticle, const std::array< double, 3 > &boxMin, const std::array< double, 3 > &boxMax, unsigned long numParticles=100ul, unsigned int seed=42)
Fills any container (also AutoPas object) with randomly uniformly distributed particles.
Definition: UniformGenerator.h:88
This is the main namespace of AutoPas.
Definition: AutoPasDecl.h:34
Per-Newton3-state, per-SortingDirectionOption pair-sorting thresholds for a 2-body CellFunctor.
Definition: SortingThresholdInfo2B.h:21
void setThresholdByOption(Newton3Option n3, SortingDirectionOption cellDirection, bool bidirectional, size_t value)
Setter to set struct values by configuration, represented by options.
Definition: SortingThresholdInfo2B.h:151
Single threshold value struct.
Definition: SortingThresholdInfoSingle.h:18