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