612d202846edf976526a5231bf9810d04b0b2967
[charm.git] / src / ck-ldb / GridCommLB.C
1 /**************************************************************************
2 ** Greg Koenig (koenig@uiuc.edu)
3 ** November 4, 2004
4 **
5 ** This is GridCommLB.C
6 **
7 ** GridCommLB is a load balancer for the Charm++ load balancing framework.
8 ** It is designed to work in a Grid computing environment consisting of
9 ** two or more clusters separated by wide-area communication links.
10 ** Communication between objects within a cluster is assumed to be light
11 ** weight (measured in microseconds) while communication between objects
12 ** on different clusters is assumed to be heavy weight (measured in
13 ** milliseconds).
14 **
15 ** The load balancer examines all communications in a computation and
16 ** attempts to spread the objects that communicate to objects on remote
17 ** clusters evenly across the PEs in the local cluster.  No objects are
18 ** ever migrated across cluster boundaries, they are simply distributed
19 ** as evenly as possible across the PEs in the cluster in which they were
20 ** originally placed.  The idea is that by spreading objects that
21 ** communicate over the wide-area evenly, a relatively small number of
22 ** WAN objects will be mixed with a relatively large number of LAN
23 ** objects, allowing the message-driven characteristics of Charm++ the
24 ** greatest possibility of overlapping the high-cost WAN communication
25 ** with locally-driven work.
26 **
27 ** The load balancer secondarily balances on scaled processor load
28 ** (i.e., a processor that is 2x the speed of another processor in
29 ** the local cluster will get 2x the work) as well as the number of
30 ** LAN objects.
31 **
32 ** This load balancer can severely disrupt the object-to-PE mapping by
33 ** causing large numbers of objects to migrate with each load balancing
34 ** invocation.  This may be undesirable in some cases.  (For example, if
35 ** the vmi-linux "eager protocol" is used, eager channels may be pinned
36 ** between two PEs, and migrating objects that communicate heavily with
37 ** each other onto other PEs could actually slow the computationif they
38 ** no longer communicate with each other over an eager channel.)
39 */
40
41 #include "GridCommLB.decl.h"
42
43 #include "GridCommLB.h"
44 #include "manager.h"
45
46 CreateLBFunc_Def (GridCommLB, "Grid communication load balancer (evenly distribute objects across each cluster)");
47
48
49
50 /**************************************************************************
51 **
52 */
53 GridCommLB::GridCommLB (const CkLBOptions &opt) : CentralLB (opt)
54 {
55   char *value;
56
57
58   lbname = (char *) "GridCommLB";
59
60   if (CkMyPe() == 0) {
61     CkPrintf ("[%d] GridCommLB created.\n", CkMyPe());
62   }
63
64   if (value = getenv ("CK_LDB_GRIDCOMMLB_MODE")) {
65     CK_LDB_GridCommLB_Mode = atoi (value);
66   } else {
67     CK_LDB_GridCommLB_Mode = CK_LDB_GRIDCOMMLB_MODE;
68   }
69
70   if (value = getenv ("CK_LDB_GRIDCOMMLB_BACKGROUND_LOAD")) {
71     CK_LDB_GridCommLB_Background_Load = atoi (value);
72   } else {
73     CK_LDB_GridCommLB_Background_Load = CK_LDB_GRIDCOMMLB_BACKGROUND_LOAD;
74   }
75
76   if (value = getenv ("CK_LDB_GRIDCOMMLB_LOAD_TOLERANCE")) {
77     CK_LDB_GridCommLB_Load_Tolerance = atof (value);
78   } else {
79     CK_LDB_GridCommLB_Load_Tolerance = CK_LDB_GRIDCOMMLB_LOAD_TOLERANCE;
80   }
81
82   manager_init ();
83 }
84
85
86
87 /**************************************************************************
88 **
89 */
90 GridCommLB::GridCommLB (CkMigrateMessage *msg) : CentralLB (msg)
91 {
92   char *value;
93
94
95   lbname = (char *) "GridCommLB";
96
97   if (value = getenv ("CK_LDB_GRIDCOMMLB_MODE")) {
98     CK_LDB_GridCommLB_Mode = atoi (value);
99   } else {
100     CK_LDB_GridCommLB_Mode = CK_LDB_GRIDCOMMLB_MODE;
101   }
102
103   if (value = getenv ("CK_LDB_GRIDCOMMLB_BACKGROUND_LOAD")) {
104     CK_LDB_GridCommLB_Background_Load = atoi (value);
105   } else {
106     CK_LDB_GridCommLB_Background_Load = CK_LDB_GRIDCOMMLB_BACKGROUND_LOAD;
107   }
108
109   if (value = getenv ("CK_LDB_GRIDCOMMLB_LOAD_TOLERANCE")) {
110     CK_LDB_GridCommLB_Load_Tolerance = atof (value);
111   } else {
112     CK_LDB_GridCommLB_Load_Tolerance = CK_LDB_GRIDCOMMLB_LOAD_TOLERANCE;
113   }
114
115   manager_init ();
116 }
117
118
119
120 /**************************************************************************
121 ** The Charm++ load balancing framework invokes this method to determine
122 ** whether load balancing can be performed at a specified time.
123 */
124 CmiBool GridCommLB::QueryBalanceNow (int step)
125 {
126   if (_lb_args.debug() > 2) {
127     CkPrintf ("[%d] GridCommLB is balancing on step %d.\n", CkMyPe(), step);
128   }
129
130   return (CmiTrue);
131 }
132
133
134
135 /**************************************************************************
136 ** The vmi-linux machine layer incorporates the idea that PEs are located
137 ** within identifiable clusters.  This information can be supplied by the
138 ** user or can be probed automatically by the machine layer.  The exposed
139 ** API call CmiGetCluster() returns the integer cluster number for a
140 ** specified PE or -1 if the information is unknown.
141 **
142 ** For machine layers other than vmi-linux, simply return the constant 0.
143 ** GridCommLB will assume a single-cluster computation and will balance
144 ** on the scaled processor load and number of LAN messages.
145 */
146 int GridCommLB::Get_Cluster (int pe)
147 {
148 #if CONVERSE_VERSION_VMI
149   return (CmiGetCluster (pe));
150 #else
151   return (0);
152 #endif
153 }
154
155
156
157 /**************************************************************************
158 ** Instantiate and initialize the PE_Data[] data structure.
159 **
160 ** While doing this...
161 **    - ensure that there is at least one available PE
162 **    - ensure that all PEs are mapped to a cluster
163 **    - determine the maximum cluster number (gives the number of clusters)
164 **    - determine the minimum speed PE (used to compute relative PE speeds)
165 */
166 void GridCommLB::Initialize_PE_Data (CentralLB::LDStats *stats)
167 {
168   int min_speed;
169   int i;
170
171
172   PE_Data = new PE_Data_T[Num_PEs];
173
174   min_speed = MAXINT;
175   for (i = 0; i < Num_PEs; i++) {
176     (&PE_Data[i])->available      = stats->procs[i].available;
177     (&PE_Data[i])->cluster        = Get_Cluster (i);
178     (&PE_Data[i])->num_objs       = 0;
179     (&PE_Data[i])->num_lan_objs   = 0;
180     (&PE_Data[i])->num_lan_msgs   = 0;
181     (&PE_Data[i])->num_wan_objs   = 0;
182     (&PE_Data[i])->num_wan_msgs   = 0;
183     (&PE_Data[i])->relative_speed = 0.0;
184     (&PE_Data[i])->scaled_load    = 0.0;
185
186     if (stats->procs[i].pe_speed < min_speed) {
187       min_speed = stats->procs[i].pe_speed;
188     }
189   }
190
191   // Compute the relative PE speeds.
192   // Also add background CPU time to each PE's scaled load.
193   for (i = 0; i < Num_PEs; i++) {
194     (&PE_Data[i])->relative_speed = (double) (stats->procs[i].pe_speed / min_speed);
195     if (CK_LDB_GridCommLB_Background_Load) {
196       (&PE_Data[i])->scaled_load += stats->procs[i].bg_cputime;
197     }
198   }
199 }
200
201
202
203 /**************************************************************************
204 **
205 */
206 int GridCommLB::Available_PE_Count ()
207 {
208   int available_pe_count;
209   int i;
210
211
212   available_pe_count = 0;
213   for (i = 0; i < Num_PEs; i++) {
214     if ((&PE_Data[i])->available) {
215       available_pe_count += 1;
216     }
217   }
218   return (available_pe_count);
219 }
220
221
222
223 /**************************************************************************
224 **
225 */
226 int GridCommLB::Compute_Number_Of_Clusters ()
227 {
228   int max_cluster;
229   int i;
230
231
232   max_cluster = 0;
233   for (i = 0; i < Num_PEs; i++) {
234     if ((&PE_Data[i])->cluster < 0) {
235       return (-1);
236     }
237
238     if ((&PE_Data[i])->cluster > max_cluster) {
239       max_cluster = (&PE_Data[i])->cluster;
240     }
241   }
242   return (max_cluster + 1);
243 }
244
245
246
247 /**************************************************************************
248 **
249 */
250 void GridCommLB::Initialize_Object_Data (CentralLB::LDStats *stats)
251 {
252   int i;
253
254
255   Object_Data = new Object_Data_T[Num_Objects];
256
257   for (i = 0; i < Num_Objects; i++) {
258     (&Object_Data[i])->migratable   = (&stats->objData[i])->migratable;
259     (&Object_Data[i])->cluster      = Get_Cluster (stats->from_proc[i]);
260     (&Object_Data[i])->from_pe      = stats->from_proc[i];
261     (&Object_Data[i])->to_pe        = -1;
262     (&Object_Data[i])->num_lan_msgs = 0;
263     (&Object_Data[i])->num_wan_msgs = 0;
264     (&Object_Data[i])->load         = (&stats->objData[i])->wallTime;
265   }
266 }
267
268
269
270 /**************************************************************************
271 **
272 */
273 void GridCommLB::Examine_InterObject_Messages (CentralLB::LDStats *stats)
274 {
275   int i;
276   int j;
277   LDCommData *com_data;
278   int send_object;
279   int send_pe;
280   int send_cluster;
281   int recv_object;
282   int recv_pe;
283   int recv_cluster;
284   LDObjKey *recv_objects;
285   int num_objects;
286
287
288   for (i = 0; i < stats->n_comm; i++) {
289     com_data = &(stats->commData[i]);
290     if ((!com_data->from_proc()) && (com_data->recv_type() == LD_OBJ_MSG)) {
291       send_object = stats->getHash (com_data->sender);
292       recv_object = stats->getHash (com_data->receiver.get_destObj());
293
294       if ((send_object < 0) || (send_object > Num_Objects) || (recv_object < 0) || (recv_object > Num_Objects)) {
295         continue;
296       }
297
298       send_pe = (&Object_Data[send_object])->from_pe;
299       recv_pe = (&Object_Data[recv_object])->from_pe;
300
301       send_cluster = Get_Cluster (send_pe);
302       recv_cluster = Get_Cluster (recv_pe);
303
304       if (send_cluster == recv_cluster) {
305         (&Object_Data[send_object])->num_lan_msgs += com_data->messages;
306       } else {
307         (&Object_Data[send_object])->num_wan_msgs += com_data->messages;
308       }
309     } else if (com_data->receiver.get_type() == LD_OBJLIST_MSG) {
310       send_object = stats->getHash (com_data->sender);
311
312       if ((send_object < 0) || (send_object > Num_Objects)) {
313         continue;
314       }
315
316       send_pe = (&Object_Data[send_object])->from_pe;
317       send_cluster = Get_Cluster (send_pe);
318
319       recv_objects = com_data->receiver.get_destObjs (num_objects);   // (num_objects is passed by reference)
320
321       for (j = 0; j < num_objects; j++) {
322         recv_object = stats->getHash (recv_objects[j]);
323
324         if ((recv_object < 0) || (recv_object > Num_Objects)) {
325           continue;
326         }
327
328         recv_pe = (&Object_Data[recv_object])->from_pe;
329         recv_cluster = Get_Cluster (recv_pe);
330
331         if (send_cluster == recv_cluster) {
332           (&Object_Data[send_object])->num_lan_msgs += com_data->messages;
333         } else {
334           (&Object_Data[send_object])->num_wan_msgs += com_data->messages;
335         }
336       }
337     }
338   }
339 }
340
341
342
343 /**************************************************************************
344 **
345 */
346 void GridCommLB::Map_NonMigratable_Objects_To_PEs ()
347 {
348   int i;
349
350
351   for (i = 0; i < Num_Objects; i++) {
352     if (!((&Object_Data[i])->migratable)) {
353       if (_lb_args.debug() > 1) {
354         CkPrintf ("[%d] GridCommLB identifies object %d as non-migratable.\n", CkMyPe(), i);
355       }
356
357       Assign_Object_To_PE (i, (&Object_Data[i])->from_pe);
358     }
359   }
360 }
361
362
363
364 /**************************************************************************
365 **
366 */
367 void GridCommLB::Map_Migratable_Objects_To_PEs (int cluster)
368 {
369   int target_object;
370   int target_pe;
371
372
373   while (1) {
374     target_object = Find_Maximum_Object (cluster);
375     target_pe = Find_Minimum_PE (cluster);
376
377     if ((target_object == -1) || (target_pe == -1)) {
378       break;
379     }
380
381     Assign_Object_To_PE (target_object, target_pe);
382   }
383 }
384
385
386
387 /**************************************************************************
388 ** This method locates the maximum WAN object in terms of number of
389 ** messages that traverse a wide-area connection.  The search is
390 ** constrained to objects within the specified cluster that have not yet
391 ** been mapped (balanced) to a PE.
392 **
393 ** The method returns -1 if no matching object is found.
394 */
395 int GridCommLB::Find_Maximum_Object (int cluster)
396 {
397   int max_index;
398   int max_load_index;
399   double max_load;
400   int max_wan_msgs_index;
401   int max_wan_msgs;
402   double load_tolerance;
403   int i;
404
405
406   max_index = -1;
407
408   max_load_index = -1;
409   max_load = -1.0;
410
411   max_wan_msgs_index = -1;
412   max_wan_msgs = -1;
413
414   for (i = 0; i < Num_Objects; i++) {
415     if (((&Object_Data[i])->cluster == cluster) && ((&Object_Data[i])->to_pe == -1)) {
416       if ((&Object_Data[i])->load > max_load) {
417         max_load_index = i;
418         max_load = (&Object_Data[i])->load;
419       }
420       if ((&Object_Data[i])->num_wan_msgs > max_wan_msgs) {
421         max_wan_msgs_index = i;
422         max_wan_msgs = (&Object_Data[i])->num_wan_msgs;
423       }
424     }
425   }
426
427   if (max_load_index < 0) {
428     return (max_load_index);
429   }
430
431   if ((&Object_Data[max_load_index])->num_wan_msgs >= (&Object_Data[max_wan_msgs_index])->num_wan_msgs) {
432     return (max_load_index);
433   }
434
435   load_tolerance = (&Object_Data[max_load_index])->load * CK_LDB_GridCommLB_Load_Tolerance;
436
437   max_index = max_load_index;
438
439   for (i = 0; i < Num_Objects; i++) {
440     if (((&Object_Data[i])->cluster == cluster) && ((&Object_Data[i])->to_pe == -1)) {
441       if (i != max_load_index) {
442         if (fabs ((&Object_Data[max_load_index])->load - (&Object_Data[i])->load) <= load_tolerance) {
443           if ((&Object_Data[i])->num_wan_msgs > (&Object_Data[max_index])->num_wan_msgs) {
444             max_index = i;
445           }
446         }
447       }
448     }
449   }
450
451   return (max_index);
452
453 /*
454   int i;
455   int max_index;
456   int max_wan_msgs;
457
458
459   max_index = -1;
460   max_wan_msgs = -1;
461
462   for (i = 0; i < Num_Objects; i++) {
463     if ((&Object_Data[i])->cluster == cluster) {
464       if ((&Object_Data[i])->to_pe == -1) {
465         if ((&Object_Data[i])->num_wan_msgs > max_wan_msgs) {
466           max_index = i;
467           max_wan_msgs = (&Object_Data[i])->num_wan_msgs;
468         }
469       }
470     }
471   }
472
473   return (max_index);
474 */
475 }
476
477
478
479 /**************************************************************************
480 ** This method locates the minimum WAN PE in terms of number of objects
481 ** that communicate with objects across a wide-area connection.  The search
482 ** is constrained to PEs within the specified cluster.
483 **
484 ** In the event of a "tie" (i.e., the number of WAN objects on a candidate
485 ** PE is equal to the minimum number of WAN objects discovered so far) the
486 ** tie is broken by considering the scaled CPU loads on the PEs.  The PE
487 ** with the smaller scaled load is the better candidate.  In the event of
488 ** a secondary tie, the secondary tie is broken by considering the number
489 ** of LAN objects on the two PEs.
490 **
491 ** The method returns -1 if no matching PE is found.
492 */
493 int GridCommLB::Find_Minimum_PE (int cluster)
494 {
495   if (CK_LDB_GridCommLB_Mode == 0) {
496     int min_index;
497     int min_load_index;
498     double min_scaled_load;
499     int min_wan_msgs_index;
500     int min_wan_msgs;
501     double load_tolerance;
502     int i;
503
504
505     min_index = -1;
506
507     min_load_index = -1;
508     min_scaled_load = MAXDOUBLE;
509
510     min_wan_msgs_index = -1;
511     min_wan_msgs = MAXINT;
512
513     for (i = 0; i < Num_PEs; i++) {
514       if (((&PE_Data[i])->available) && ((&PE_Data[i])->cluster == cluster)) {
515         if ((&PE_Data[i])->scaled_load < min_scaled_load) {
516           min_load_index = i;
517           min_scaled_load = (&PE_Data[i])->scaled_load;
518         }
519         if ((&PE_Data[i])->num_wan_msgs < min_wan_msgs) {
520           min_wan_msgs_index = i;
521           min_wan_msgs = (&PE_Data[i])->num_wan_msgs;
522         }
523       }
524     }
525
526     // If no PE at all was found, return a -1.
527     if (min_load_index < 0) {
528       return (min_load_index);
529     }
530
531     // If the number of WAN messages on the lightest loaded PE happens to match the minimum number
532     // of WAN messages overall, we win because this target PE is overall the minimum PE in terms
533     // of both load *and* WAN messages.
534     if ((&PE_Data[min_load_index])->num_wan_msgs <= (&PE_Data[min_wan_msgs_index])->num_wan_msgs) {
535       return (min_load_index);
536     }
537
538     // Otherwise, we now search for PEs that have loads +/- our tolerance.  If any PE has a load
539     // within our tolerance, check its number of WAN messages.  The one of these that has the
540     // fewest WAN messages is probably the best candidate for placing the next object onto.
541
542     load_tolerance = (&PE_Data[min_load_index])->scaled_load * CK_LDB_GridCommLB_Load_Tolerance;
543
544     min_index = min_load_index;
545
546     for (i = 0; i < Num_PEs; i++) {
547       if (((&PE_Data[i])->available) && ((&PE_Data[i])->cluster == cluster)) {
548         if (i != min_load_index) {
549           if (fabs ((&PE_Data[i])->scaled_load - (&PE_Data[min_load_index])->scaled_load) <= load_tolerance) {
550             if ((&PE_Data[i])->num_wan_msgs < (&PE_Data[min_index])->num_wan_msgs) {
551               min_index = i;
552             }
553           }
554         }
555       }
556     }
557
558     return (min_index);
559   } else if (CK_LDB_GridCommLB_Mode == 1) {
560     int min_index;
561     int min_objs;
562     int i;
563
564
565     min_index = -1;
566     min_objs = MAXINT;
567
568     for (i = 0; i < Num_PEs; i++) {
569       if (((&PE_Data[i])->available) && ((&PE_Data[i])->cluster == cluster)) {
570         if ((&PE_Data[i])->num_objs < min_objs) {
571           min_index = i;
572           min_objs = (&PE_Data[i])->num_objs;
573         } else if (((&PE_Data[i])->num_objs == min_objs) &&
574                    ((&PE_Data[i])->num_wan_msgs < (&PE_Data[min_index])->num_wan_msgs)) {
575           min_index = i;
576           min_objs = (&PE_Data[i])->num_objs;
577         } else if (((&PE_Data[i])->num_objs == min_objs) &&
578                    ((&PE_Data[i])->num_wan_msgs == (&PE_Data[min_index])->num_wan_msgs) &&
579                    ((&PE_Data[i])->scaled_load < (&PE_Data[min_index])->scaled_load)) {
580           min_index = i;
581           min_objs = (&PE_Data[i])->num_objs;
582         }
583       }
584     }
585
586     return (min_index);
587   } else {
588     if (_lb_args.debug() > 0) {
589       CkPrintf ("[%d] GridCommLB was told to use bad mode (%d).\n", CkMyPe(), CK_LDB_GridCommLB_Mode);
590     }
591     return (-1);
592   }
593 }
594
595
596
597 /**************************************************************************
598 ** This method assigns target_object to target_pe.  The data structure
599 ** entry for target_pe is updated appropriately with measurements from
600 ** target_object.  This updated information is considered when placing
601 ** successive objects onto PEs.
602 */
603 void GridCommLB::Assign_Object_To_PE (int target_object, int target_pe)
604 {
605   (&Object_Data[target_object])->to_pe = target_pe;
606
607   (&PE_Data[target_pe])->num_objs += 1;
608
609   if ((&Object_Data[target_object])->num_lan_msgs > 0) {
610     (&PE_Data[target_pe])->num_lan_objs += 1;
611     (&PE_Data[target_pe])->num_lan_msgs += (&Object_Data[target_object])->num_lan_msgs;
612   }
613
614   if ((&Object_Data[target_object])->num_wan_msgs > 0) {
615     (&PE_Data[target_pe])->num_wan_objs += 1;
616     (&PE_Data[target_pe])->num_wan_msgs += (&Object_Data[target_object])->num_wan_msgs;
617   }
618
619   (&PE_Data[target_pe])->scaled_load += (&Object_Data[target_object])->load / (&PE_Data[target_pe])->relative_speed;
620 }
621
622
623
624 /**************************************************************************
625 ** The Charm++ load balancing framework invokes this method to cause the
626 ** load balancer to migrate objects to "better" PEs.
627 */
628 void GridCommLB::work (CentralLB::LDStats *stats, int count)
629 {
630   int i;
631
632
633   if (_lb_args.debug() > 0) {
634     CkPrintf ("[%d] GridCommLB is working (mode=%d, background load=%d, load tolerance=%f).\n", CkMyPe(), CK_LDB_GridCommLB_Mode, CK_LDB_GridCommLB_Background_Load, CK_LDB_GridCommLB_Load_Tolerance);
635   }
636
637   // Since this load balancer looks at communications data, it must initialize the CommHash.
638   stats->makeCommHash ();
639
640   // Initialize object variables for the number of PEs and number of objects.
641   Num_PEs = count;
642   Num_Objects = stats->n_objs;
643
644   if (_lb_args.debug() > 0) {
645     CkPrintf ("[%d] GridCommLB is examining %d PEs and %d objects.\n", CkMyPe(), Num_PEs, Num_Objects);
646   }
647
648   // Initialize the PE_Data[] data structure.
649   Initialize_PE_Data (stats);
650
651   // If at least one available PE does not exist, return from load balancing.
652   if (Available_PE_Count() < 1) {
653     if (_lb_args.debug() > 0) {
654       CkPrintf ("[%d] GridCommLB finds no available PEs -- no balancing done.\n", CkMyPe());
655     }
656
657     delete [] PE_Data;
658
659     return;
660   }
661
662   // Determine the number of clusters.
663   // If any PE is not mapped to a cluster, return from load balancing.
664   Num_Clusters = Compute_Number_Of_Clusters ();
665   if (Num_Clusters < 1) {
666     if (_lb_args.debug() > 0) {
667       CkPrintf ("[%d] GridCommLB finds incomplete PE cluster map -- no balancing done.\n", CkMyPe());
668     }
669
670     delete [] PE_Data;
671
672     return;
673   }
674
675   if (_lb_args.debug() > 0) {
676     CkPrintf ("[%d] GridCommLB finds %d clusters.\n", CkMyPe(), Num_Clusters);
677   }
678
679   // Initialize the Object_Data[] data structure.
680   Initialize_Object_Data (stats);
681
682   // Examine all object-to-object messages for intra-cluster and inter-cluster communications.
683   Examine_InterObject_Messages (stats);
684
685   // Map non-migratable objects to PEs.
686   Map_NonMigratable_Objects_To_PEs ();
687
688   // Map migratable objects to PEs in each cluster.
689   for (i = 0; i < Num_Clusters; i++) {
690     Map_Migratable_Objects_To_PEs (i);
691   }
692
693   // Make the assignment of objects to PEs in the load balancer framework.
694   for (i = 0; i < Num_Objects; i++) {
695     stats->to_proc[i] = (&Object_Data[i])->to_pe;
696
697     if (_lb_args.debug() > 2) {
698       CkPrintf ("[%d] GridCommLB migrates object %d from PE %d to PE %d.\n", CkMyPe(), i, stats->from_proc[i], stats->to_proc[i]);
699     } else if (_lb_args.debug() > 1) {
700       if (stats->to_proc[i] != stats->from_proc[i]) {
701         CkPrintf ("[%d] GridCommLB migrates object %d from PE %d to PE %d.\n", CkMyPe(), i, stats->from_proc[i], stats->to_proc[i]);
702       }
703     }
704   }
705
706   // Free memory.
707   delete [] Object_Data;
708   delete [] PE_Data;
709 }
710
711 #include "GridCommLB.def.h"