Coverage Report

Created: 2026-10-28 17:42

/home/runner/work/charm/charm/src/ck-ldb/TreeLB.C
Line
Count
Source (jump to first uncovered line)
1
// Author: Juan Galvez <jjgalvez@illinois.edu>
2
3
#include "TreeBuilder.h"  // TODO this can be deleted if we change it so that LBManager instantiates the builders
4
#include "TreeLB.h"
5
#include "TreeStrategyFactory.h"
6
#include "spanningTree.h"
7
#include <fstream>  // TODO delete if json file is read from LBManager
8
#include <sstream>
9
#include "json.hpp"
10
11
extern int quietModeRequested;
12
13
static void lbinit()
14
256
{
15
256
  const auto& names = TreeStrategy::LBNames;
16
256
  std::ostringstream o;
17
256
  for (const auto& name : names)
18
1.79k
  {
19
1.79k
    o << "\n\t" << name;
20
1.79k
  }
21
256
  LBRegisterBalancer<TreeLB>(
22
256
      "TreeLB", "Pluggable hierarchical LB with available strategies:" + o.str());
23
256
}
24
25
void TreeLB::Migrated(int waitBarrier)
26
49.9k
{
27
49.9k
  objMovedIn(waitBarrier);
28
49.9k
}
29
30
void TreeLB::loadConfigFile(const CkLBOptions& opts)
31
353
{
32
353
  config.clear();
33
353
  std::ifstream ifs(_lb_args.treeLBFile(), std::ifstream::in);
34
353
  if (opts.hasLegacyName())
35
354
  {
36
    // support legacy mode, e.g. map "+GreedyLB" to PE_Root tree using Greedy
37
    // use 2-level tree
38
354
    config["tree"] = "PE_Root";
39
354
    config["root"]["strategies"] = {opts.getLegacyName()};
40
354
    if (CkMyPe() == 0 && !quietModeRequested)
41
108
      CkPrintf("[%d] TreeLB in LEGACY MODE support\n", CkMyPe());
42
354
  }
43
18.4E
  else if (ifs.good())
44
0
  {
45
0
    config = json::parse(ifs, nullptr, false);
46
0
    if (config.is_discarded())
47
0
    {
48
0
      CkPrintf(
49
0
          "Error reading TreeLB configuration file: %s.\n"
50
0
          "Ensure your configuration file is valid JSON.\n",
51
0
          _lb_args.treeLBFile());
52
0
      CkExit(1);
53
0
    }
54
0
  }
55
18.4E
  else
56
18.4E
  {
57
18.4E
    if (CkMyPe() == 0 && !quietModeRequested)
58
0
      CkPrintf(
59
0
          "[%d] No TreeLB configuration file found. Choosing a default configuration.\n",
60
0
          CkMyPe());
61
    // try to pick reasonable defaults
62
    // the problem with using a 2 or 3 level tree in large jobs is that the root's
63
    // strategy could take a long time to run, and also many strategies (but not all)
64
    // could move objects across the whole machine (which means potentially slow
65
    // migrations and could also hurt communication performance depending on the
66
    // strategy).
67
18.4E
    if (CmiNumPhysicalNodes() >= 128)
68
0
    {
69
      // use 4-level tree
70
0
      config["tree"] = "PE_Process_ProcessGroup_Root";
71
0
      config["root"]["pe"] = 0;
72
0
      config["processgroup"]["num_groups"] = CmiNumPhysicalNodes() / 32;
73
0
      config["root"]["step_freq"] = 10;
74
0
      config["processgroup"]["step_freq"] = 5;
75
0
      config["process"]["strategies"] = {"GreedyRefine"};
76
0
      config["processgroup"]["strategies"] = {"GreedyRefine"};
77
0
      config["processgroup"]["GreedyRefine"]["tolerance"] = 1.03;
78
0
    }
79
18.4E
    else if (CmiNumNodes() > 1 && CmiNodeSize(0) > 1)
80
0
    {
81
      // use 3-level tree
82
0
      config["tree"] = "PE_Process_Root";
83
0
      config["root"]["pe"] = 0;
84
0
      config["root"]["step_freq"] = 3;
85
0
      config["root"]["strategies"] = {"GreedyRefine"};
86
0
      config["process"]["strategies"] = {"GreedyRefine"};
87
0
    }
88
18.4E
    else
89
18.4E
    {
90
      // use 2-level tree
91
18.4E
      config["tree"] = "PE_Root";
92
18.4E
      config["root"]["pe"] = 0;
93
18.4E
      config["root"]["strategies"] = {"GreedyRefine"};
94
18.4E
    }
95
18.4E
  }
96
353
  ifs.close();
97
353
}
98
99
void TreeLB::init(const CkLBOptions& opts)
100
351
{
101
351
#if CMK_LBDB_ON
102
103
351
  lbname = "TreeLB";
104
105
351
  if (_lb_args.syncResume()) barrier_after_lb = true;
106
107
  // create and turn on by default
108
351
  startLbFnHdl = lbmgr->AddStartLBFn(this, &TreeLB::StartLB);
109
110
351
  configure(config);
111
112
  // TODO this functionality needs to move to LBManager
113
351
  if (_lb_args.statsOn())
114
339
    lbmgr->CollectStatsOn();  // collect load and (optionally) comm stats
115
116
351
  if (opts.getSeqNo() > 0)
117
0
  {
118
0
    turnOff();
119
0
  }
120
121
351
#endif
122
351
}
123
124
TreeLB::~TreeLB()
125
0
{
126
0
#if CMK_LBDB_ON
127
0
  lbmgr = CProxy_LBManager(_lbmgr).ckLocalBranch();
128
0
  if (lbmgr)
129
0
  {
130
0
    lbmgr->RemoveStartLBFn(startLbFnHdl);
131
0
  }
132
133
0
  for (auto l : logic) delete l;
134
0
  for (auto l : comm_logic) delete l;
135
136
0
#endif
137
0
}
138
139
void TreeLB::configure(LBTreeBuilder& builder, json& config)
140
356
{
141
356
#if CMK_LBDB_ON
142
143
356
  if (numLevels > 0 && CkMyPe() == 0 && !quietModeRequested)
144
0
  {
145
0
    CkPrintf("[%d] Reconfiguring TreeLB\n", CkMyPe());
146
0
  }
147
148
356
  mcast_bfactor = builder.getProperty("mcast_bfactor", mcast_bfactor, config);
149
356
  step_report_lb_times =
150
356
      builder.getProperty("report_lb_times_at_step", step_report_lb_times, config);
151
152
356
  if (numLevels > 0)
153
0
  {
154
0
    for (auto l : logic) delete l;
155
0
    for (auto l : comm_logic) delete l;
156
0
  }
157
356
  numLevels = builder.build(logic, comm_parent, comm_children, comm_logic, config);
158
356
  CkAssert(numLevels > 0 && logic.size() == numLevels &&
159
356
           comm_parent.size() == numLevels && comm_children.size() == numLevels &&
160
356
           comm_logic.size() == numLevels);
161
162
356
  expected_incoming.resize(numLevels);
163
356
  expected_outgoing.resize(numLevels);
164
356
  load_sent.resize(numLevels);
165
356
  load_received.resize(numLevels);
166
  // notify_after_transfer.resize(numLevels);
167
356
  awaitingLB.resize(numLevels);
168
169
  // reset all values since this may be a re-configuration
170
356
  std::fill(expected_incoming.begin(), expected_incoming.end(), 0);
171
356
  std::fill(expected_outgoing.begin(), expected_outgoing.end(), 0);
172
356
  std::fill(load_sent.begin(), load_sent.end(), 0);
173
356
  std::fill(load_received.begin(), load_received.end(), 0);
174
  // std::fill(notify_after_transfer.begin(), notify_after_transfer.end(), -1);
175
356
  std::fill(awaitingLB.begin(), awaitingLB.end(), false);
176
177
356
#endif
178
356
}
179
180
void TreeLB::configure(json& config)
181
356
{
182
356
#if CMK_LBDB_ON
183
356
  const std::string& tree_type = config["tree"];
184
356
  if (tree_type == "PE_Root")
185
353
  {
186
    // builder = new PE_Root_Tree();
187
353
    auto tree = PE_Root_Tree(config);
188
353
    configure(tree, config);
189
353
  }
190
3
  else if (tree_type == "PE_Process_Root")
191
0
  {
192
0
    auto tree = PE_Node_Root_Tree(config);
193
0
    configure(tree, config);
194
0
  }
195
3
  else if (tree_type == "PE_Process_ProcessGroup_Root")
196
0
  {
197
0
    auto tree = PE_Node_NodeSet_Root_Tree(config);
198
0
    configure(tree, config);
199
0
  }
200
3
  else
201
3
  {
202
3
    CkAbort("TreeLB: configured tree not recognized\n");
203
3
  }
204
356
#endif
205
356
}
206
207
void TreeLB::pup(PUP::er& p)
208
20
{
209
20
  std::string configString;
210
20
  if (p.isPacking())
211
8
  {
212
8
    configString = config.dump();
213
8
  }
214
20
  p | configString;
215
20
  if (p.isUnpacking())
216
11
  {
217
11
    config = json::parse(configString);
218
11
    init(CkLBOptions(seqno));
219
11
  }
220
20
}
221
222
void TreeLB::InvokeLB()
223
3.71k
{
224
3.71k
#if CMK_LBDB_ON
225
  // NOTE: I'm assuming new LBManager will know when (and when not to) call AtSync
226
3.71k
  if (barrier_before_lb)
227
0
  {
228
0
    contribute(CkCallback(CkReductionTarget(TreeLB, ProcessAtSync), thisProxy));
229
0
  }
230
3.71k
  else
231
3.71k
  {
232
3.71k
    thisProxy[CkMyPe()].ProcessAtSync();
233
3.71k
  }
234
3.71k
#endif
235
3.71k
}
236
237
void TreeLB::ProcessAtSync()
238
3.70k
{
239
3.70k
#if CMK_LBDB_ON
240
3.70k
  startTime = CkWallTimer();
241
3.70k
  if (CkMyPe() == 0 && _lb_args.debug() > 0)
242
152
  {
243
152
    CkPrintf("--------- Started LB step %d ---------\n", lbmgr->step());
244
152
  }
245
  // CmiAssert(CmiNodeAlive(CkMyPe()));   // TODO move this logic to LBManager
246
3.70k
  int level = 0;  // load balancing starts at the lowest level
247
3.70k
  CkAssert(numLevels > 0 && !awaitingLB[level]);
248
3.70k
  TreeLBMessage* stats = logic[level]->getStats();
249
3.70k
  stats->level = level;
250
3.70k
  awaitingLB[level] = true;
251
3.70k
  sendStatsUp((CkMessage*)stats);
252
3.70k
#endif
253
3.70k
}
254
255
// send stats up using the comm-tree for this level
256
void TreeLB::sendStatsUp(CkMessage* msg)
257
6.31k
{
258
6.31k
  TreeLBMessage* stats = (TreeLBMessage*)msg;
259
6.31k
  int level = stats->level;
260
6.31k
  int comm_parent_pe = comm_parent[level];
261
  // fprintf(stderr, "[%d] TreeLB::sendStatsUp - received msg level=%d comm_parent=%d\n",
262
  // CkMyPe(), level, comm_parent_pe);
263
6.31k
  if (comm_parent_pe == -1)
264
3.36k
  {
265
    // I'm the root of this comm-tree (current destination of the stats)
266
3.36k
    receiveStats(stats, level);
267
3.36k
  }
268
2.94k
  else if (comm_children[level].size() == 0)
269
2.22k
  {
270
    // don't have children so don't have to aggregate any msgs
271
2.22k
    thisProxy[comm_parent_pe].sendStatsUp((CkMessage*)stats);
272
2.22k
  }
273
719
  else
274
719
  {
275
719
    LevelLogic* logic = comm_logic[level];
276
719
    logic->depositStats(stats);
277
719
    if (logic->numStatsReceived() == comm_children[level].size() + 1)
278
356
    {
279
356
      TreeLBMessage* newMsg = logic->mergeStats();
280
356
      newMsg->level = level;
281
356
      thisProxy[comm_parent_pe].sendStatsUp((CkMessage*)newMsg);
282
356
    }
283
719
  }
284
6.31k
}
285
286
void TreeLB::receiveStats(TreeLBMessage* stats, int level)
287
3.36k
{
288
3.36k
  level += 1;
289
3.36k
  awaitingLB[level] = true;
290
3.36k
  LevelLogic* l = logic[level];
291
3.36k
  l->depositStats(stats);
292
3.36k
  size_t expected_msgs = comm_children[level - 1].size();
293
3.36k
  if (logic[level - 1] != nullptr) expected_msgs += 1;  // expect msg from myself too
294
3.36k
  if (l->numStatsReceived() == expected_msgs)
295
1.10k
  {
296
1.10k
    CkAssert(load_sent[level] == 0 && load_received[level] == 0);
297
1.10k
    if (level == numLevels - 1 || l->cutoff())
298
1.10k
    {
299
      // cutoff can be adjusted dynamically, to prevent lb between upper-level domains.
300
      // can be used, for example, to only do within-node lb on some steps
301
1.10k
      loadBalanceSubtree(level);
302
1.10k
    }
303
0
    else
304
0
    {
305
0
      TreeLBMessage* newMsg = l->mergeStats();
306
0
      newMsg->level = level;
307
0
      sendStatsUp((CkMessage*)newMsg);
308
0
    }
309
1.10k
  }
310
3.36k
}
311
312
void TreeLB::loadBalanceSubtree(int level)
313
5.61k
{
314
5.61k
  if (!awaitingLB[level]) return;
315
4.81k
  awaitingLB[level] = false;
316
4.81k
  if (level == 0) return lb_done();
317
318
  // CkPrintf("[%d] TreeLB::loadBalanceSubtree - level=%d\n", CkMyPe(), level);
319
320
  /// CkMessage *inter_subtree_migrations = nullptr;
321
1.10k
  IDM idm;
322
1.10k
  TreeLBMessage* decision = logic[level]->loadBalance(idm);
323
1.10k
  if (idm.size() > 0)
324
0
  {
325
    // this can happen when final destinations of chares has been decided,
326
    // and chares from a subtree need to migrate to a PE in a different subtree
327
0
    std::vector<int> idm_dests(1 + idm.size());
328
0
    int index = 0;
329
0
    idm_dests[index++] = CkMyPe();
330
0
    for (auto& move : idm)
331
0
    {
332
0
      CkAssert(move.second.size() > 0);
333
0
      idm_dests[index++] = move.first;
334
0
    }
335
0
    ST_RecursivePartition<std::vector<int>::iterator> tb(false, false);
336
0
    int num_subtrees =
337
0
        tb.buildSpanningTree(idm_dests.begin(), idm_dests.end(), mcast_bfactor);
338
0
    for (int i = 0; i < num_subtrees; i++)
339
0
      thisProxy[*tb.begin(i)].multicastIDM(idm, tb.subtreeSize(i), &(*tb.begin(i)));
340
0
  }
341
342
  // send decision to next level
343
1.10k
  decision->level = level - 1;
344
1.10k
  sendDecisionDown((CkMessage*)decision);
345
1.10k
}
346
347
void TreeLB::multicastIDM(const IDM& mig_order, int num_pes, int* _pes)
348
0
{
349
#if DEBUG__TREE_LB_L3
350
  fprintf(stderr, "[%d] Received IDM\n", CkMyPe());
351
#endif
352
0
  ST_RecursivePartition<int*> tb(false, false);
353
0
  if (num_pes > 1)
354
0
  {
355
0
    int num_subtrees = tb.buildSpanningTree(_pes, _pes + num_pes, mcast_bfactor);
356
0
    for (int i = 0; i < num_subtrees; i++)
357
0
      thisProxy[*tb.begin(i)].multicastIDM(mig_order, tb.subtreeSize(i), tb.begin(i));
358
0
  }
359
0
  migrateObjects(mig_order);
360
0
}
361
362
void TreeLB::sendDecisionDown(CkMessage* msg)
363
3.70k
{
364
3.70k
  TreeLBMessage* decision = (TreeLBMessage*)msg;
365
3.70k
  const int level = decision->level;
366
3.70k
  std::vector<int>& children = comm_children[level];
367
3.70k
  if (children.empty())
368
2.24k
  {
369
2.24k
    receiveDecision(decision, level);
370
2.24k
  }
371
1.45k
  else
372
1.45k
  {
373
    // comm logic is free to split (scatter) the message, or send same msg to every child,
374
    // etc.
375
1.45k
    CkAssert(comm_logic[level] != nullptr);
376
1.45k
    std::vector<TreeLBMessage*> decisions =
377
1.45k
        comm_logic[level]->splitDecision(decision, children);
378
1.45k
    CkAssert(decisions.size() == children.size() + 1);
379
1.45k
    if (logic[level] != nullptr)
380
1.45k
    {
381
1.45k
      receiveDecision(decisions[0], level);
382
1.45k
    }
383
1
    else
384
1
    {
385
1
      delete decisions[0];
386
1
    }
387
4.07k
    for (int i = 0; i < children.size(); i++)
388
2.61k
    {
389
      // Necessary because in some cases every message in decisions is actually
390
      // the same message that we are reusing, so mark as unused
391
2.61k
      _SET_USED(UsrToEnv(decisions[i + 1]), 0);
392
2.61k
      thisProxy[children[i]].sendDecisionDown((CkMessage*)decisions[i + 1]);
393
2.61k
    }
394
1.45k
  }
395
3.70k
}
396
397
void TreeLB::receiveDecision(TreeLBMessage* decision, int level)
398
3.70k
{
399
  // fprintf(stderr, "[%d] TreeLB::receiveDecision, level=%d\n", CkMyPe(), level);
400
401
  // incoming and outgoing are integers. logic objects determine and interpret these
402
  // values
403
3.70k
  int& incoming = expected_incoming[level];
404
3.70k
  int& outgoing = expected_outgoing[level];
405
3.70k
  logic[level]->processDecision(decision, incoming, outgoing);
406
  // fprintf(stderr, "[%d] level=%d incoming=%d outgoing=%d\n", CkMyPe(), level, incoming,
407
  // outgoing);
408
3.70k
  if (incoming == 0 && outgoing == 0)
409
797
  {
410
    // no exchange with other subtrees, can do lb for my subtree now
411
797
    loadBalanceSubtree(level);
412
797
  }
413
2.90k
  else
414
2.90k
  {
415
    // awaiting load from other subtrees
416
2.90k
    if (outgoing > 0 && level > 0)
417
0
    {
418
      // need to pass info on actual chares to other subtree(s) (they will need it sooner
419
      // or later since some of my chares will be moving there, but I'm not migrating
420
      // chares themselves yet, since their final destination is not yet known) at this
421
      // level I might not know about individual chares. if that's the case, I have to
422
      // delegate to lower levels to pass this info to the other subtree
423
0
      transferLoadToken(decision, level);
424
0
    }
425
426
    // if outgoing > 0 and we are in last level (0), concrete objects are moved to
427
    // concrete PEs. for chares whose destination is known, the logic object would have
428
    // made call to migrate them inside processDecision (above) NOTE: I might be moving
429
    // some objects to other subtrees, might need to wait to receive messages from those
430
    // subtrees telling me to which PEs to migrate them
431
2.90k
  }
432
  // fprintf(stderr, "[%d] TreeLB::receiveDecision, deleting decision msg\n", CkMyPe());
433
3.70k
  delete (CkMessage*)decision;  // FIXME this won't call the subclass' delete method
434
  // a check here is needed because PE might have been sending chares/tokens or
435
  // receiving them even before receiving the decision msg (with the final tally)
436
  // from the parent. So, exchange might have already completed
437
3.70k
  checkLoadExchanged(level);
438
3.70k
}
439
440
// order transfer of load token to dest
441
void TreeLB::transferLoadToken(TreeLBMessage* transferMsg, int level)
442
0
{
443
  // TODO this is a simplified implementation where the first level where tokens are
444
  // needed needs to be able to generate the tokens (so, token requests cannot be
445
  // propagated down the tree). There is a more advanced implementation in charm4py
446
0
  if (logic[level]->makesTokens())
447
0
  {
448
    // one token set goes to one destination
449
0
    std::vector<TreeLBMessage*> token_sets;
450
0
    std::vector<int> destinations;
451
0
    load_sent[level] += logic[level]->getTokenSets(transferMsg, token_sets, destinations);
452
0
    for (size_t i = 0; i < token_sets.size(); i++)
453
0
    {
454
0
      token_sets[i]->level = level;
455
0
      thisProxy[destinations[i]].recvLoadTokens((CkMessage*)token_sets[i]);
456
0
    }
457
0
  }
458
0
  else
459
0
  {
460
    // implemented in python. see code
461
0
    CkAbort("TreeLB::transferLoadToken - NOT IMPLEMENTED\n");
462
0
  }
463
0
}
464
465
void TreeLB::recvLoadTokens(CkMessage* tokens)
466
0
{
467
0
  TreeLBMessage* token_set = (TreeLBMessage*)tokens;
468
0
  int level = token_set->level;
469
#if DEBUG__TREE_LB_L3
470
  fprintf(stderr, "[%d] Received load token, level=%d\n", CkMyPe(), level);
471
#endif
472
0
  int load = logic[level]->tokensReceived(token_set);
473
0
  load_received[level] += load;
474
0
  checkLoadExchanged(level);
475
0
}
476
477
void TreeLB::objMovedIn(bool waitBarrier)
478
49.9k
{
479
49.9k
  if (!waitBarrier) CkAbort("TreeLB future migrates not supported\n");
480
481
  // fprintf(stderr, "[%d] TreeLB::objMovedIn\n", CkMyPe());
482
483
49.9k
  int level = 0;
484
49.9k
  CkAssert(numLevels > 0 && awaitingLB[level]);
485
49.9k
  load_received[level] += 1;
486
49.9k
  checkLoadExchanged(level);
487
49.9k
}
488
489
void TreeLB::migrateObjects(const IDM& mig_order)
490
0
{
491
0
  int level = 0;
492
0
  int sent = logic[level]->migrateObjects(mig_order.at(CkMyPe()));
493
0
  load_sent[level] += sent;
494
#if DEBUG__TREE_LB_L2
495
  fprintf(stderr, "[%d] Received IDM order, sent=%d\n", CkMyPe(), sent);
496
#endif
497
0
  checkLoadExchanged(level);
498
0
}
499
500
void TreeLB::lb_done()
501
3.70k
{
502
  // fprintf(stderr, "[%d] lb_done step %d lb_time=%f\n", CkMyPe(), lbmgr->step(),
503
  // CkWallTimer() - startTime);
504
505
  // TODO LBManager should do all of this, including global syncResume ******
506
  // Currently, TreeLB does syncResume by setting barrier_after_lb=true
507
508
  // clear load stats
509
3.70k
  lbmgr->ClearLoads();
510
511
3.70k
  if (CkMyPe() == 0 && _lb_args.debug() > 0)
512
152
  {
513
152
    CkPrintf("--------- Finished LB step %d ---------\n", lbmgr->step());
514
152
  }
515
516
  // Advance to next load balancer
517
3.70k
  if (!(_lb_args.metaLbOn() && _lb_args.metaLbModelDir() != nullptr))
518
3.69k
    lbmgr->nextLoadbalancer(seqno);
519
520
  // Increment to next step
521
3.70k
  lbmgr->incStep();
522
523
3.70k
  LBManager::Object()
524
3.70k
      ->MigrationDone();  // call registered callbacks. not sure what this is for
525
526
  // again, TreeLB shouldn't be doing these things. it should just notify LBManager
527
  // that it's done. LBManager should take care of the rest.
528
  // not sure why this has to be called as entry method. but if not, it seems like
529
  // the last object to migrate in is not resumed
530
3.70k
  if (barrier_after_lb)
531
0
  {
532
0
    contribute(CkCallback(CkReductionTarget(TreeLB, resumeClients), thisProxy));
533
0
  }
534
3.70k
  else
535
3.70k
  {
536
3.70k
    thisProxy[CkMyPe()].resumeClients();
537
3.70k
  }
538
3.70k
}
539
540
void TreeLB::resumeClients()
541
3.68k
{
542
3.68k
  double lb_time = CkWallTimer() - startTime;
543
3.68k
  if (CkMyPe() == 0 && _lb_args.debug() > 0)
544
152
    CkPrintf("[%d] lb time = %f\n", CkMyPe(), lb_time);
545
3.68k
  int step = lbmgr->step() - 1;
546
  // fprintf(stderr, "[%d] step %d lb_time=%f\n", CkMyPe(), step, lb_time);
547
3.68k
  if (step_report_lb_times >= step)
548
0
  {
549
0
    lb_times.push_back(lb_time);
550
0
    if (step == step_report_lb_times)
551
0
    {
552
0
      contribute(lb_times, CkReduction::sum_double,
553
0
                 CkCallback(CkReductionTarget(TreeLB, reportLbTime), thisProxy[0]));
554
0
      lb_times.clear();
555
0
    }
556
0
  }
557
3.68k
  lbmgr->ResumeClients();
558
3.68k
}
559
560
void TreeLB::reportLbTime(double* times, int n)
561
0
{
562
0
  fprintf(stderr, "lb times: ");
563
0
  for (int i = 0; i < n; i++)
564
0
  {
565
0
    double avg_time = times[i] / CkNumPes();
566
0
    fprintf(stderr, "%f ", avg_time);
567
0
  }
568
0
  fprintf(stderr, "\n");
569
0
}
570
571
#include "TreeLB.def.h"