chombo-discharge
Loading...
Searching...
No Matches
CD_LoadBalancingImplem.H
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021-2026 SINTEF Energy Research
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
13#ifndef CD_LOADBALANCEIMPLEM_H
14#define CD_LOADBALANCEIMPLEM_H
15
16// Std includes
17#include <algorithm>
18#include <random>
19#include <chrono>
20#include <limits>
21#include <array>
22#include <tuple>
23
24// Chombo includes
25#include <LoadBalance.H>
26#include <BoxLayout.H>
27#include <ParmParse.H>
28#include <CH_Timer.H>
29
30// Our includes
31#include <CD_LoadBalancing.H>
32#include <CD_NamespaceHeader.H>
33
34template <class T>
35void
36LoadBalancing::makeBalance(Vector<int>& a_ranks, const Vector<T>& a_loads, const Vector<Box>& a_boxes)
37{
38 CH_TIME("LoadBalancing::makeBalance");
39
40 // LoadBalance(a_ranks, a_loads, a_boxes);
41 Loads rankLoads;
42 LoadBalancing::makeBalance<T>(a_ranks, rankLoads, a_loads, a_boxes);
43}
44
45template <class T>
46void
47LoadBalancing::makeBalance(Vector<int>& a_ranks,
48 Loads& a_rankLoads,
49 const Vector<T>& a_boxLoads,
50 const Vector<Box>& a_boxes)
51{
52 CH_TIME("LoadBalancing::makeBalance");
53
54 // Convert everything to floating points
55 Vector<Real> boxLoads;
56 for (int i = 0; i < a_boxLoads.size(); i++) {
57 boxLoads.push_back(1.0 * a_boxLoads[i]);
58 }
59
60 // Minimum number of grid subsets is the number of boxes, and the maximum number of grid subsets
61 // is the number of ranks.
62 const int numBoxes = static_cast<int>(a_boxes.size());
63 const int numRanks = static_cast<int>(numProc());
64 const int numSubsets = std::min(numBoxes, numRanks);
65
66 a_ranks.resize(numBoxes);
67
68 if (numSubsets > 0) {
69
70 // Figure out the total and target load (load per subset) on this level.
71 Real totalLoad = 0.0;
72 for (int ibox = 0; ibox < numBoxes; ibox++) {
73 totalLoad += boxLoads[ibox];
74 }
75
76 Real staticTargetLoad = totalLoad / numSubsets;
77
78 // Build the grid subsets. When we do this we iterate through the boxes and try to ensure that we partition
79 // the subsets such that the subsetLoad is as close to the dynamic targetLoad as possible.
80 //
81 // The pair contains the starting index for the subset and the computational load for the subset.
82 using Span = std::pair<int, int>;
83 using Subset = std::pair<Span, Real>;
84
85 std::vector<Subset> subsets(numSubsets);
86
87 int firstSubsetBox = 0;
88
89 Real remainingLoad = totalLoad;
90
91 for (int curSubset = 0; curSubset < numSubsets; curSubset++) {
92
93 // The firstSubsetBox is the index for the first box in this subset (always assigned).
94 Real subsetLoad = boxLoads[firstSubsetBox];
95
96 int lastSubsetBox = firstSubsetBox;
97
98 const int subsetsLeft = numSubsets - (curSubset + 1);
99 const int boxesLeft = numBoxes - (firstSubsetBox + 1);
100
101 if (boxesLeft > subsetsLeft) {
102 for (int ibox = firstSubsetBox + 1; ibox < numBoxes; ibox++) {
103
104 // Hook for catching case when we add too many boxes to this subset. Each remaining subset must have at least
105 // one box.
106 if (numBoxes - lastSubsetBox - 1 <= subsetsLeft) {
107 break;
108 }
109
110 // Check if we should add this box - we do this by making sure that the dynamically moving target load stays
111 // as close to the static load as possible.
112 //
113 // In the below, '1' is the load without ibox, and '2' is the load with ibox
114 const Real load1 = subsetLoad;
115 const Real load2 = subsetLoad + boxLoads[ibox];
116
117 // Check if we should add this box.
118 bool addBoxToSubset = false;
119
120 if (boxLoads[ibox] <= std::numeric_limits<Real>::epsilon()) {
121 addBoxToSubset = true;
122 }
123 else if (load1 > staticTargetLoad) {
124 addBoxToSubset = false;
125 }
126 else if (load2 <= staticTargetLoad) {
127 addBoxToSubset = true;
128 }
129 else if (load1 <= staticTargetLoad && load2 > staticTargetLoad) {
130 // Compute the new average load if we add or don't add this box to the current subset. Accept the answer
131 // that leads to a smallest deviation from the static target load.
132 const Real loadErrWithoutBox = std::abs(load1 - staticTargetLoad);
133 const Real loadErrWithBox = std::abs(load2 - staticTargetLoad);
134
135 if (loadErrWithBox <= loadErrWithoutBox) {
136 addBoxToSubset = true;
137 }
138 }
139
140 // Add box or break out of box iteration.
141 if (addBoxToSubset) {
142 subsetLoad = subsetLoad + boxLoads[ibox];
143 lastSubsetBox = ibox;
144
145 continue;
146 }
147 else {
148 lastSubsetBox = ibox - 1;
149
150 break;
151 }
152 }
153 }
154
155 // Create the subset
156 subsets[curSubset] = std::make_pair(std::make_pair(firstSubsetBox, lastSubsetBox), subsetLoad);
157
158 // Update the remaining load.
159 remainingLoad = remainingLoad - subsetLoad;
160 staticTargetLoad = remainingLoad / subsetsLeft;
161
162 // Update start box for next iteration.
163 firstSubsetBox = lastSubsetBox + 1;
164 }
165
166 // Sort the subsets from largest to smallest computational load.
167 std::sort(subsets.begin(), subsets.end(), [](const Subset& A, const Subset& B) -> bool {
168 return A.second > B.second;
169 });
170
171 // Get the accumulated loads per rank, sorted by lowest-to-highest load.
172 const std::vector<std::pair<int, Real>> sortedRankLoads = a_rankLoads.getSortedLoads();
173
174 // Assign the most expensive grid subset to the rank with the lowest accumulated load.
175 for (int i = 0; i < subsets.size(); i++) {
176 const int startIndex = subsets[i].first.first;
177 const int endIndex = subsets[i].first.second;
178 const Real subsetLoad = subsets[i].second;
179 const int rank = sortedRankLoads[i].first;
180
181 // Assign to rank
182 for (int ibox = startIndex; ibox <= endIndex; ibox++) {
183 a_ranks[ibox] = rank;
184 }
185
186 // Update load on this rank.
187 a_rankLoads.incrementLoad(rank, subsetLoad);
188 }
189 }
190 else {
191 a_ranks.resize(0);
192 }
193}
194
195template <class T>
196std::vector<std::pair<Box, T>>
197LoadBalancing::packPairs(const Vector<Box>& a_boxes, const Vector<T>& a_loads)
198{
199 CH_TIME("LoadBalancing::packPairs");
200
201 std::vector<std::pair<Box, T>> vec;
202 for (int i = 0; i < a_boxes.size(); i++) {
203 vec.emplace_back(a_boxes[i], a_loads[i]);
204 }
205
206 return vec;
207}
208
209template <class T>
210void
211LoadBalancing::unpackPairs(Vector<Box>& a_boxes, Vector<T>& a_loads, const std::vector<std::pair<Box, T>>& a_pairs)
212{
213 CH_TIME("LoadBalancing::unpackPairs");
214
215 // Reconstruct boxes and loads
216 a_boxes.resize(0);
217 a_loads.resize(0);
218
219 for (const auto& v : a_pairs) {
220 a_boxes.push_back(v.first);
221 a_loads.push_back(v.second);
222 }
223}
224
225template <typename T>
226void
227LoadBalancing::sort(Vector<Vector<Box>>& a_boxes, Vector<Vector<T>>& a_loads, const BoxSorting a_which)
228{
229 CH_TIME("LoadBalancing::sort");
230
231 for (int lvl = 0; lvl < a_boxes.size(); lvl++) {
232 LoadBalancing::sort(a_boxes[lvl], a_loads[lvl], a_which);
233 }
234}
235
236template <typename T>
237void
238LoadBalancing::sort(Vector<Box>& a_boxes, Vector<T>& a_loads, const BoxSorting a_which)
239{
240 CH_TIME("LoadBalancing::sort");
241
242 switch (a_which) {
243 case BoxSorting::None: {
244 break;
245 }
246 case BoxSorting::Std: {
247 LoadBalancing::standardSort(a_boxes, a_loads);
248
249 break;
250 }
251 case BoxSorting::Shuffle: {
252 LoadBalancing::shuffleSort(a_boxes, a_loads);
253
254 break;
255 }
256 case BoxSorting::Morton: {
257 LoadBalancing::sortSFC<T, SpaceDim>(a_boxes, a_loads, LoadBalancing::mortonIndex<SpaceDim>);
258
259 break;
260 }
261 case BoxSorting::Hilbert: {
262 LoadBalancing::sortSFC<T, SpaceDim>(a_boxes, a_loads, LoadBalancing::hilbertIndex<SpaceDim>);
263
264 break;
265 }
266 default: {
267 MayDay::Abort("LoadBalancing::sort_boxes - unknown algorithm requested");
268
269 break;
270 }
271 }
272}
273
274template <class T>
275void
276LoadBalancing::standardSort(Vector<Box>& a_boxes, Vector<T>& a_loads)
277{
278 CH_TIME("LoadBalancing::standardSort");
279
280 std::vector<std::pair<Box, T>> vec = packPairs(a_boxes, a_loads);
281
282 // Call std::sort, using box1 < box2 lambda as sorting criterion.
283 std::sort(std::begin(vec), std::end(vec), [](const std::pair<Box, T>& v1, const std::pair<Box, T>& v2) {
284 return v1.first < v2.first;
285 });
286
287 unpackPairs(a_boxes, a_loads, vec);
288}
289
290template <class T>
291void
292LoadBalancing::shuffleSort(Vector<Box>& a_boxes, Vector<T>& a_loads)
293{
294 CH_TIME("LoadBalancing::shuffleSort");
295
296 auto vec = packPairs(a_boxes, a_loads);
297
298 // Set up RNG
299 auto seed = static_cast<int>(std::chrono::system_clock::now().time_since_epoch().count());
300#ifdef CH_MPI // Broadcast
301 MPI_Bcast(&seed, 1, MPI_INT, 0, Chombo_MPI::comm);
302#endif
303
304 // Shuffle vector
305 std::default_random_engine e(seed);
306 std::shuffle(vec.begin(), vec.end(), e);
307
308 // Split boxes and loads
309 unpackPairs(a_boxes, a_loads, vec);
310}
311
312template <class T, int DIM>
313void
314LoadBalancing::sortSFC(Vector<Box>& a_boxes,
315 Vector<T>& a_loads,
316 const std::function<uint64_t(const std::array<uint32_t, DIM>)>& a_sfcEncoder) noexcept
317
318{
319 CH_TIME("LoadBalancing::sortSFC");
320
321 using Bundle = std::tuple<Box, T, uint64_t>;
322
323 const int n = static_cast<int>(a_boxes.size());
324 if (n == 0 || n != a_loads.size()) {
325 return;
326 }
327
328 // Pack data into a vector of tuples. Tuple contains (box, load, key).
329 std::vector<Bundle> buf;
330 for (std::size_t i = 0; i < n; ++i) {
331 buf.emplace_back(std::move(a_boxes[i]), std::move(a_loads[i]), uint64_t{0});
332 }
333
334 // Compute Hilbert keys
335 for (std::size_t i = 0; i < n; ++i) {
336 const auto& b = std::get<0>(buf[i]);
337 const auto iv = b.smallEnd();
338
339 std::array<uint32_t, SpaceDim> c;
340
341 for (int d = 0; d < SpaceDim; ++d) {
342 c[d] = static_cast<uint32_t>(iv[d]);
343 }
344
345 std::get<2>(buf[i]) = a_sfcEncoder(c);
346 }
347
348 // 3) Sort by hilbert key.
349 std::sort(buf.begin(), buf.end(), [](const Bundle& a, const Bundle& b) noexcept {
350 return std::get<2>(a) < std::get<2>(b);
351 });
352
353 // Unpack into original containers.
354 for (std::size_t i = 0; i < n; ++i) {
355 a_boxes[i] = std::move(std::get<0>(buf[i]));
356 a_loads[i] = std::move(std::get<1>(buf[i]));
357 }
358}
359
360template <int DIM>
361uint64_t
362LoadBalancing::mortonIndex(const std::array<uint32_t, DIM>& a_coords) noexcept
363{
364 uint64_t code = 0;
365
366 // If this fails then we can't compute a Morton code using a 64-bit integer. We must either scale back the boxes
367 // by their blocking factors, or switch to 128/256 bit integers.
368 const int maxDim = 1 << 21;
369
370 for (int dir = 0; dir < SpaceDim; dir++) {
371 if (a_coords[dir] > maxDim) {
372 MayDay::Abort("LoadBalancing::mortonIndex - logic bust");
373 }
374 }
375
376 for (int bit = 20; bit >= 0; --bit) {
377 for (int dir = CH_SPACEDIM - 1; dir >= 0; --dir) {
378 const uint64_t b = (static_cast<uint64_t>(static_cast<uint32_t>(a_coords[dir])) >> bit) & 1ULL;
379
380 code = (code << 1) | b;
381 }
382 }
383
384 return code;
385}
386
387template <int DIM>
388uint64_t
389LoadBalancing::hilbertIndex(const std::array<uint32_t, DIM>& a_coords)
390{
391 CH_TIME("LoadBalancing::hilbertIndex");
392
393 constexpr int nbits = 21;
394
395 uint32_t x[DIM];
396
397 for (int i = 0; i < DIM; ++i) {
398 x[i] = a_coords[i];
399 }
400
401 const uint32_t M = 1U << (nbits - 1);
402
403 for (uint32_t Q = M; Q > 1; Q >>= 1) {
404 const uint32_t P = Q - 1;
405
406 for (int i = 0; i < DIM; ++i) {
407 if (x[i] & Q) {
408 x[0] ^= P;
409 }
410 else {
411 const uint32_t t = (x[0] ^ x[i]) & P;
412
413 x[0] ^= t;
414 x[i] ^= t;
415 }
416 }
417 }
418
419 for (int i = 1; i < DIM; ++i) {
420 x[i] ^= x[i - 1];
421 }
422
423 uint32_t t = 0;
424 for (uint32_t Q = M; Q > 1; Q >>= 1) {
425 if (x[DIM - 1] & Q) {
426 t ^= (Q - 1);
427 }
428 }
429
430 for (int i = 0; i < DIM; ++i) {
431 x[i] ^= t;
432 }
433
434 uint64_t idx = 0;
435
436 for (int b = nbits - 1; b >= 0; --b) {
437 for (int i = 0; i < DIM; ++i) {
438 idx = (idx << 1) | ((x[i] >> b) & 1U);
439 }
440 }
441
442 return idx;
443}
444
445#include <CD_NamespaceFooter.H>
446
447#endif
BoxSorting
Enum for sorting boxes.
Definition CD_BoxSorting.H:22
Declaration of a static class for various load balancing operations.
static uint64_t mortonIndex(const std::array< uint32_t, DIM > &a_coords) noexcept
Compute a Morton (Z-order) code from integer coordinates, using 21 bits per direction.
Definition CD_LoadBalancingImplem.H:362
static void makeBalance(Vector< int > &a_ranks, const Vector< T > &a_loads, const Vector< Box > &a_boxes)
Load balancing, assigning ranks to boxes.
Definition CD_LoadBalancingImplem.H:36
static uint64_t hilbertIndex(const std::array< uint32_t, DIM > &a_coords)
Compute a Hilbert-curve index from integer coordinates, using 21 bits per direction.
Definition CD_LoadBalancingImplem.H:389
static void standardSort(Vector< Box > &a_boxes, Vector< T > &a_loads)
Standard box sorting, calls C++ std::sort.
Definition CD_LoadBalancingImplem.H:276
static void sort(Vector< Vector< Box > > &a_boxes, Vector< Vector< T > > &a_loads, const BoxSorting a_whichSorting)
Sorts boxes and loads over a hierarchy according to some sorting criterion.
Definition CD_LoadBalancingImplem.H:227
static void unpackPairs(Vector< Box > &a_boxes, Vector< T > &a_loads, const std::vector< std::pair< Box, T > > &a_pairs)
Splits vector pair into separate boxes and loads.
Definition CD_LoadBalancingImplem.H:211
static void sortSFC(Vector< Box > &a_boxes, Vector< T > &a_loads, const std::function< uint64_t(const std::array< uint32_t, DIM >)> &a_sfcEncoder) noexcept
Generic SFC sorting function.
Definition CD_LoadBalancingImplem.H:314
static std::vector< std::pair< Box, T > > packPairs(const Vector< Box > &a_boxes, const Vector< T > &a_loads)
Utility function which packs boxes and loads into a vector of pairs.
Definition CD_LoadBalancingImplem.H:197
static void shuffleSort(Vector< Box > &a_boxes, Vector< T > &a_loads)
Randomly shuffles boxes and loads.
Definition CD_LoadBalancingImplem.H:292
Class for holding computational loads.
Definition CD_Loads.H:31
virtual void incrementLoad(const int a_rank, const Real a_increment) noexcept
Increment load on rank.
Definition CD_Loads.cpp:169
virtual std::vector< std::pair< int, Real > > getSortedLoads() const noexcept
Get sorted loads.
Definition CD_Loads.cpp:184