Zoltan2
Loading...
Searching...
No Matches
Zoltan2_Directory_Impl.hpp
Go to the documentation of this file.
1// @HEADER
2// *****************************************************************************
3// Zoltan2: A package of combinatorial algorithms for scientific computing
4//
5// Copyright 2012 NTESS and the Zoltan2 contributors.
6// SPDX-License-Identifier: BSD-3-Clause
7// *****************************************************************************
8// @HEADER
9
10#ifndef ZOLTAN2_DIRECTORY_IMPL_H_
11#define ZOLTAN2_DIRECTORY_IMPL_H_
12
13#include "Zoltan2_Directory.hpp"
15
16namespace Zoltan2 {
17
18// These macros were rolled over from the original zoltan code. I've preserved
19// them for now until we have further discussion on how we want debug levels
20// and logging to work in the new code. This should just be debug related and
21// not impact the behavior of the code.
22#define ZOLTAN2_PRINT_INFO(proc,yo,str) \
23 printf("ZOLTAN2 (Processor %d) %s: %s\n", (proc), (yo), \
24 ((str) != NULL ? (char *)(str) : " "));
25
26#define ZOLTAN2_TRACE(proc,where,yo,str) \
27 printf("ZOLTAN (Processor %d) %s %s %s\n", (proc), (where), (yo), \
28 ((str) != NULL ? (char *)(str) : " "));
29
30#define ZOLTAN2_TRACE_IN(proc,yo,str) \
31 ZOLTAN2_TRACE((proc),"Entering",(yo),(str));
32
33#define ZOLTAN2_TRACE_OUT(proc,yo,str) \
34 ZOLTAN2_TRACE((proc),"Exiting",(yo),(str));
35
36// Tags for MPI communications. These need unique values. Arbitrary
37// These were rolled over from the original zoltan code and still used.
38#define ZOLTAN2_DD_FIND_MSG_TAG 29137 /* needs 3 consecutive values */
39#define ZOLTAN2_DD_UPDATE_MSG_TAG 29140 /* needs 2 consecutive values */
40#define ZOLTAN2_DD_REMOVE_MSG_TAG 29142 /* needs 2 consecutive values */
41#define ZOLTAN2_DD_RESIZE_MSG_TAG 29150 /* */
42
43template <typename gid_t,typename lid_t,typename user_t>
45{
46 const char * yo = "Zoltan2_Directory::allocate";
47
48 if (debug_level > 4) {
49 ZOLTAN2_TRACE_IN (comm->getRank(), yo, NULL);
50 }
51
52 /* insure all processors are using the same GID, LID, USER lengths */
53 size_t array[3], max_array[3], min_array[3];
54 array[0] = sizeof(lid_t);
55 array[1] = sizeof(gid_t);
56 array[2] = sizeof(user_t);
57 Teuchos::reduceAll<int,size_t>(
58 *comm, Teuchos::REDUCE_MAX, 3, array, max_array);
59 Teuchos::reduceAll<int,size_t>(
60 *comm, Teuchos::REDUCE_MIN, 3, array, min_array);
61 if (max_array[0] != min_array[0] || max_array[1] != min_array[1]
62 || max_array[2] != min_array[2]) {
63 throw std::invalid_argument(
64 "Zoltan2_Directory() LID, GID, USER data lengths differ globally");
65 }
66
67 // get the base size of the user data component
68 // for simple mode, this is going to just be the sizeof(user_t)
69 // for vector mode, this is sizeof(size_t) for the std::vector length
70 // the actual data will be variable
71 size_t user_base_size = is_Zoltan2_Directory_Vector() ? sizeof(size_t) :
72 size_of_value_type();
73
74 // set up the base size to include the gid and the lid (if used) + user size
75 size_t size = sizeof(gid_t) + (use_lid?sizeof(lid_t):0) + user_base_size;
76
77 // now calculate the full update msg size
78 update_msg_size = size + sizeof(Zoltan2_DD_Update_Msg<gid_t,lid_t>);
79
80 // for remove message we just need the gid, not any other info
81 size = sizeof(gid_t);
82 remove_msg_size = size + sizeof(Zoltan2_DD_Remove_Msg<gid_t,lid_t>);
83
84 // Current form of find_local is passed so gid_t is the input and
85 // lid_t is the output so this guarantees that ptr is sufficient to
86 // cover both (uses the max). This handling is consistent with the original
87 // zoltan code but I initially found it very confusing.
88 // I suggest searching for all of the places where max_id_size is used and
89 // that represents the relevant code. This may be the most optimal way of
90 // doing this. I did not get to assessing it in detail.
91 //
92 // NOTE: To really test this thoroughly we would want to play around with
93 // the values GID_SET_LENGTH and LID_SET_LENGTH in the directoryTest_Impl.hpp
94 // setup. I made sure this works for gid larger than lid and the opposite.
95 // Probably should extend the unit tests to cover all these cases at once as
96 // originally I had some bugs where this would work fine except when lid
97 // size was greater than gid size. Now this all should be ok.
98 max_id_size = std::max(sizeof(gid_t),sizeof(lid_t));
99
100 size = max_id_size + user_base_size;
101 find_msg_size = size + sizeof(Zoltan2_DD_Find_Msg<gid_t,lid_t>);
102
103 // TODO: Alignment is currently off for all of the modes for performance
104 // comparisons. I was not sure yet the implications of this and how it would
105 // best integrate if we have variable length user data
106/*
107 update_msg_size = align_size_t(update_msg_size);
108 remove_msg_size = align_size_t(remove_msg_size);
109 find_msg_size = align_size_t(find_msg_size);
110*/
111
112 if (debug_level > 4) {
113 ZOLTAN2_TRACE_OUT (comm->getRank(), yo, NULL);
114 }
115}
116
117// Copy Block
118template <typename gid_t,typename lid_t,typename user_t>
121 node_map = src.node_map;
122 return 0;
123}
124
125template <typename gid_t,typename lid_t,typename user_t>
127 size_t count, const gid_t * gid, const lid_t * lid,
128 const user_t * user, const int * partition,
129 Update_Mode update_mode)
130{
131 // for conveniece store but maybe this should be a construct property
132 // should a directory allow mixed modes (Replace, then Aggregate... for ex)
133 this->mode = update_mode;
134
135 const char * yo = "Zoltan2_Directory::update";
136
137 if (debug_level > 4) {
138 ZOLTAN2_TRACE_IN(comm->getRank(), yo, NULL);
139 }
140
141 // part of initializing the error checking process
142 // for each linked list head, walk its list resetting errcheck
143 if(debug_level) {
144 for(size_t n = 0; n < node_map.size(); ++n) {
145 node_map.value_at(n).errcheck = -1; // not possible processor
146 }
147 }
148
149 if (debug_level > 6) {
150 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After reset errcheck");
151 }
152
153 int err = 0;
154
155 // allocate memory for list of processors to contact
156 Teuchos::ArrayRCP<int> procs;
157 if(count > 0) {
158 procs = Teuchos::arcp(new int[count], 0, count, true);
159 }
160
161 // set up the msg sizes for vector mode only
162 Teuchos::ArrayRCP<int> msg_sizes;
163 int sum_msg_size = 0;
164 if(is_Zoltan2_Directory_Vector() && count > 0) {
165 msg_sizes = Teuchos::arcp(new int[count], 0, count, true);
166 for (size_t i = 0; i < count; i++) {
167 size_t msg_size = get_update_msg_size(user[i]);
168 sum_msg_size += msg_size;
169 msg_sizes[i] = msg_size;
170 }
171 }
172 else {
173 sum_msg_size = update_msg_size * count; // simple case
174 }
175
176 Teuchos::ArrayRCP<char> sbuff;
177 if(sum_msg_size) {
178 sbuff = Teuchos::arcp(new char[sum_msg_size], 0, sum_msg_size, true);
179 }
180
182
183 // build update messages
184 int track_offset = 0;
185 char * trackptr = sbuff.getRawPtr();
186 for (size_t i = 0; i < count; i++) {
187 // hash the gid to determine which proc will own it
188 procs[i] = hash_proc(gid[i]);
189
190 // this ptr is the beginning of the message which can be different
191 // lengths for different gids if using Zoltan2_Directory_Vector
192 msg_t *ptr = reinterpret_cast<msg_t*>(trackptr);
193
194 // write in all my info
195 ptr->lid_flag = lid ? 1 : 0;
196 ptr->user_flag = user ? 1 : 0;
197 ptr->partition_flag = partition ? 1 : 0;
198 ptr->partition = partition ? partition[i] : -1;
199 ptr->owner = comm->getRank();
200
201 // now deal with the gid
202 gid_t * pgid = ptr->adjData;
203 *pgid = gid[i];
204
205 // optionally write the lid if we are using that
206 if(use_lid) {
207 if(!lid) {
208 throw std::logic_error(
209 "Did not pass lid values but directory was created to use them!");
210 }
211 lid_t * plid = reinterpret_cast<lid_t*>(ptr->adjData + 1);
212 *plid = lid[i];
213 }
214 else {
215 if(lid) {
216 throw std::logic_error(
217 "Passed lid values but directory was created not to use them!");
218 }
219 }
220
221 // find the spot where the user data begins
222 user_t * puser = reinterpret_cast<user_t*>(
223 reinterpret_cast<char*>(ptr->adjData) + sizeof(gid_t) +
224 (use_lid?sizeof(lid_t):0));
225
226 // write in the user data - for Zoltan2_Directory_Simple this is a trival
227 // copy but for Zoltan2_Directory_Vector we write length and then write all
228 // of the elements in the vector.
229 if (user) {
230 user_to_raw(user[i], puser);
231 }
232 else {
233 // The update msg contains space for the vector length (sizeof(size_t))
234 // if it's a Zoltan2_Directory_Vector
235 *puser = user_t(); // create an empty result
236 }
237
238 // vector will have different lengths but the simple mode will have all
239 // lengths just update_msg_size
240 size_t new_update_msg_size =
241 is_Zoltan2_Directory_Vector() ? msg_sizes[i] : update_msg_size;
242 track_offset += new_update_msg_size;
243 trackptr += new_update_msg_size;
244 }
245
246 // this check just makes sure our internal logic above is correct
247 // we should have looped to total size sum_msg_size
248 if(track_offset != sum_msg_size) {
249 throw std::logic_error("Bad summing!");
250 }
251
252 if (debug_level > 6) {
253 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After fill contact list");
254 }
255
256 // now create efficient communication plan
257
258 // Kokkos mode uses the new refactored C++ communicator class
259 Zoltan2_Directory_Comm directoryComm(count, procs, comm,
261 int nrec = directoryComm.getNRec();
262
263 if (err) {
264 throw std::logic_error("Zoltan2_Directory::update() Comm_Create error");
265 }
266
267 if (debug_level > 6) {
268 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After Comm_Create");
269 }
270
271 int sum_recv_sizes = 0;
272 if(is_Zoltan2_Directory_Vector()) {
273 // Only vector mode has to use the resizing options for getting receive info
274 err = directoryComm.resize(msg_sizes,
275 ZOLTAN2_DD_RESIZE_MSG_TAG, &sum_recv_sizes);
276 }
277 else {
278 sum_recv_sizes = update_msg_size * nrec;
279 }
280
281 if (err) {
282 throw std::logic_error("directoryComm.execute_resize error");
283 }
284
285 // If dd has no nodes allocated (e.g., first call to DD_Update;
286 // create the nodelist and freelist
287
288 // TODO upgrade nrec as size_t and all corresponding changes...
289 if(nrec && static_cast<int>(node_map.size()) < nrec) {
290 // some things to consider here if we will have multiple update calls in
291 // series... how will subsequent calls optimally manage this list since the
292 // new gid set may be partially overlapped with the original. Currently the
293 // update_local has a mechanism to rehash and increase this size if we run
294 // out so skipping this call would be logically ok (but not best performance)
295 rehash_node_map(nrec);
296 }
297
298 // allocate receive buffer for nrec DD_Update_Msg structures
299 Teuchos::ArrayRCP<char> rbuff;
300 if(sum_recv_sizes > 0) {
301 rbuff = Teuchos::arcp(
302 new char[sum_recv_sizes], 0, sum_recv_sizes, true); // receive buffer
303 }
304
305 // send my update messages & receive updates directed to me
306 //if resizing we send size 1 because the sizes will be built individually
307 const int nbytes = is_Zoltan2_Directory_Vector() ? 1 : update_msg_size;
308
309 err = directoryComm.do_forward(ZOLTAN2_DD_UPDATE_MSG_TAG+1,
310 sbuff, nbytes, rbuff);
311
312 if (err) {
313 throw std::logic_error("Zoltan2_Directory::update() Comm_Do error");
314 }
315
316 if (debug_level > 6) {
317 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After Comm_Do");
318 }
319
320 int errcount = 0;
321
322 // for each message rec'd, update local directory information
323 track_offset = 0;
324 trackptr = rbuff.getRawPtr();
325 for (int i = 0; i < nrec; i++) {
326 msg_t *ptr = reinterpret_cast<msg_t*>(trackptr);
327
328 user_t * puser = (ptr->user_flag) ?
329 (user_t*)(reinterpret_cast<char*>(ptr->adjData) +
330 sizeof(gid_t) + (use_lid?sizeof(lid_t):0)) : NULL;
331
332 err = update_local(ptr->adjData,
333 (ptr->lid_flag) ? reinterpret_cast<lid_t*>(ptr->adjData + 1) : NULL,
334 puser,
335 (ptr->partition_flag) ? (ptr->partition) : -1, // illegal partition
336 ptr->owner);
337
338 if (err)
339 ++errcount;
340
341 // in this case we are reading the raw data so we calculate the message
342 // size from it. For vector mode, this will find the length of the vector
343 // and calculate the full message size from that.
344 size_t delta_msg_size = get_update_msg_size(puser);
345 trackptr += delta_msg_size;
346 track_offset += delta_msg_size;
347 }
348
349 // safety check - if all this internal logic is correct the ptr offset should
350 // not be exactly at the end of the recv buffer.
351 if(track_offset != sum_recv_sizes) {
352 throw std::logic_error("Did not sum!");
353 }
354
355 if (debug_level > 6) {
356 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After Local update");
357 }
358
359 err = 0;
360
361 if (debug_level) { // overwrite error return if extra checking is on
362 err = (errcount) ? 1 : 0;
363 }
364
365 if (debug_level) {
366 char str[100]; // used to build message string
367 sprintf (str, "Processed %lu GIDs (%d local), %d GID errors", count,
368 directoryComm.getNRec(),
369 errcount);
370 ZOLTAN2_PRINT_INFO (comm->getRank(), yo, str);
371 }
372
373 if (debug_level > 4) {
374 ZOLTAN2_TRACE_OUT(comm->getRank(), yo, NULL);
375 }
376
377 return err;
378}
379
380template <typename gid_t,typename lid_t,typename user_t>
382 gid_t* gid, /* GID to update (in) */
383 lid_t* lid, /* gid's LID (in), NULL if not needed */
384 user_t *user, /* gid's user data (in), NULL if not needed */
385 int partition, /* gid's partition (in), -1 if not used */
386 int owner) /* gid's current owner (proc number) (in) */
387{
388 const char * yo = "Zoltan2_Directory::update_local";
389
390 // input sanity checking
391 if (owner < 0) {
392 throw std::logic_error(
393 "Zoltan2_Directory::update_local() owner < 0");
394 }
395 if (owner >= comm->getSize()) {
396 throw std::logic_error(
397 "Zoltan2_Directory::update_local() owner >= comm->getSize()");
398 }
399 if (gid == NULL) {
400 throw std::logic_error(
401 "Zoltan2_Directory::update_local() gid == NULL");
402 }
403
404 if (debug_level > 5) {
405 ZOLTAN2_TRACE_IN (comm->getRank(), yo, NULL);
406 }
407
408 // compute offset into hash table to find head of linked list
409 size_t node_index = node_map.find(*gid);
410 if(node_map.valid_at(node_index)) {
412 node_map.value_at(node_index);
413
414 // found match, update directory information
415 if (lid) {
416 node.lid = *lid;
417 }
418 if (user) {
419 update_local_user(user, node.userData);
420 }
421
422 node.owner = owner;
423 if (partition != -1) {
424 node.partition = partition;
425 }
426
427 // errcheck is reset to -1 at beginning of update for debug mode
428 // then it will get set to the provider of the data when the node is
429 // created or on the first call to be updated locally.
430 // So if errcheck -1, then we do nothing - it's the first action.
431 if(debug_level) {
432 // if node.errcheck is -1 we are detecting the first update to a node
433 // which already existed at the beginning of the update call so it's ok.
434 // if mode is not Replace then duplicate calls are ok (expected) since
435 // Add and Aggregate combine from many procs are the outcome is not
436 // order dependent.
437 if(node.errcheck != -1 && mode == Replace) {
438 // here we have detected two actions on a single node in the same
439 // update call for replace.
440 // The actions could have been: create node -> change node
441 // or if the node already existed: change node -> change node
442
443 // in Replace mode, two actions are potentially problematic.
444 // If two procs update the same gid with different data it will
445 // be order dependent.
446
447 // For performance testing it's convenient to allow
448 // Replace to be called from different procs but expect the data
449 // to always be identical. That means we can compare Replace and
450 // Aggregate more meaningfully since the gid setup for update is
451 // the same.
452
453 // To discuss - should one proc be allowed to submit duplicate
454 // gids in an update call using Replace mode. Should two different
455 // procs be allowed to submit duplicate gids with the same data
456 // for a replace call, in which case the outcome is determined
457 // regardless of order but we would be relying on the user to have
458 // accurate data submission. Then we might consider a debug check
459 // here to validate the data is coming in matches the local data.
460
461 // TODO: Probably add this throw in, but currently the testing
462 // framework will feed multiple Replace calls with the same data
463 // from different gids - so we would have to change the tests
464 // to guarantee Replace was a unique gid lists for each proc.
465 // That is easy to do but not ideal at the moment for performance
466 // testing reasons. I'd like the pattern of calls to be the same
467 // as Add and Aggregate as a reference.
468
469 // throw std::logic_error( "Two replace calls were detected on."
470 // " the same gid which can be an undefined results.");
471 }
472 }
473
474 node.errcheck = owner;
475
476 if (debug_level > 5) {
477 ZOLTAN2_TRACE_OUT (comm->getRank(), yo, NULL);
478 }
479
480 // TODO: Change logic flow to avoid this internal return
481 return 0; // ignore all errors
482 }
483
484 // gid not found.
485 // Create new Zoltan2_Directory_Node<gid_t,lid_t> and fill it in
486 Zoltan2_Directory_Node<gid_t,lid_t,user_t> node; // will add to hash at end
487 node.free = 0; // TODO - is this necessary - see notes in rehash_node_map
488 node.lid = lid ? (*lid) : lid_t();
489
490 // this is more or less doing what above relic commands did except for
491 // vector mode there is special handling to account for the variable length
492 // of the std::vector
493 if(user) {
494 raw_to_user(user, node.userData);
495 }
496 else {
497 node.userData = user_t();
498 }
499
500 node.partition = partition;
501 node.owner = owner;
502 node.errcheck = owner;
503
504 if(node_map.insert(*gid, node).failed()) {
505 // Need more nodes (that's the assumption here if we have failure)
506 // A new update has added more to our local list and we need more capacity.
507 // TODO: Decide most efficient scheme. Here we bump to at least 10 or if
508 // we're already at the level, increase by 10% increments. I think this will
509 // be less efficient for small scale problems, when we probably care less,
510 // but more efficient as we scale up.
511 size_t new_guess_size = (node_map.size() < 10) ? 10 :
512 ( node_map.size() + node_map.size()/10); // adds a minimum of 1
513 rehash_node_map(new_guess_size);
514 if(node_map.insert(*gid, node).failed()) {
515 throw std::logic_error("Hash insert failed. Mem sufficient?....");
516 }
517 }
518
519 if (debug_level > 6) {
520 ZOLTAN2_PRINT_INFO (comm->getRank(), yo, "Created new directory item");
521 }
522
523 if (debug_level > 5) {
524 ZOLTAN2_TRACE_OUT (comm->getRank(), yo, NULL);
525 }
526
527 return 0;
528}
529
530template <typename gid_t,typename lid_t,typename user_t>
532 size_t count,
533 const gid_t * gid, /* Incoming list of GIDs to get owners proc */
534 lid_t * lid, /* Outgoing corresponding list of LIDs */
535 user_t * user, /* Outgoing optional corresponding user data */
536 int * partition, /* Outgoing optional partition information */
537 int * owner, /* Outgoing optional list of data owners */
538 bool throw_if_missing) /* default true - throw if gid is not found. */
539{
540 const char * yo = "Zoltan2_Directory::find";
541
542 if (debug_level > 4) {
543 ZOLTAN2_TRACE_IN(comm->getRank(), yo, NULL);
544 }
545
546 int err = 0;
547
548 /* allocate memory for processors to contact for directory info */
549 Teuchos::ArrayRCP<int> procs;
550 if(count > 0) {
551 procs = Teuchos::arcp(
552 new int[count], 0, count, true); // processors to contact
553 }
554
555 /* Setup procs list */
556 for (size_t i = 0; i < count; i++) {
557 procs[i] = hash_proc(gid[i]); // determines the owner
558 }
559
560 // create efficient communication plan
561 Zoltan2_Directory_Comm directoryComm(count, procs, comm,
563 int nrec = directoryComm.getNRec();
564
565 if (debug_level > 6) {
566 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After Comm_Create");
567 }
568
569 if (err) {
570 throw std::logic_error("Zoltan2_Directory::find() error");
571 }
572
573 Teuchos::ArrayRCP<char> sbuff;
574 if(count > 0) {
575 sbuff = Teuchos::arcp(new char[find_msg_size*count],
576 0, find_msg_size*count, true); // send buffer
577 }
578
580
581 /* for each GID, fill DD_Find_Msg buffer and contact list */
582 char *trackptr = sbuff.getRawPtr();
583 for (size_t i = 0; i < count; i++) {
584 msg_t *ptr = reinterpret_cast<msg_t*>(trackptr);
585 ptr->index = i;
586 ptr->proc = procs[i];
587 *(ptr->adjData) = gid[i];
588 trackptr += find_msg_size;
589 }
590
591 if (debug_level > 6) {
592 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After fill");
593 }
594
595 if (err) {
596 throw std::logic_error("directoryComm.execute_resize error");
597 }
598
599 // allocate receive buffer
600 Teuchos::ArrayRCP<char> rbuff;
601 if(nrec > 0) {
602 rbuff = Teuchos::arcp(new char[nrec * find_msg_size],
603 0, nrec * find_msg_size, true);
604 }
605
606 const int nbytes = find_msg_size; // just getting length not full vector data
607
608 err = directoryComm.do_forward(ZOLTAN2_DD_FIND_MSG_TAG+1, sbuff,
609 nbytes, rbuff);
610
611 if (err) {
612 throw std::logic_error("Zoltan2_Directory::find() error");
613 }
614
615 if (debug_level > 6) {
616 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After Comm_Do");
617 }
618
619 // get find messages directed to me and determine total recv size
620 // and a list of individual sizes (rmsg_sizes_resized)
621 Teuchos::ArrayRCP<int> rmsg_sizes_resized;
622 if(nrec > 0) {
623 rmsg_sizes_resized = Teuchos::arcp(new int[nrec], 0, nrec, true);
624 }
625 Teuchos::ArrayRCP<int>::size_type sum_rmsg_sizes_resized = 0;
626
627 char *track_ptr = rbuff.getRawPtr();
628 for (int i = 0; i < nrec; i++) {
629 msg_t *msg = reinterpret_cast<msg_t*>(track_ptr);
630 track_ptr += find_msg_size;
631 size_t find_rmsg_size_resized = get_local_find_msg_size(msg->adjData,
632 throw_if_missing);
633 rmsg_sizes_resized[i] = find_rmsg_size_resized;
634 sum_rmsg_sizes_resized += find_rmsg_size_resized;
635 }
636
637 Teuchos::ArrayRCP<char>::size_type track_offset_resized = 0;
638
639 Teuchos::ArrayRCP<char> rbuff_resized_build;
640
641 if(is_Zoltan2_Directory_Vector()) {
642
643 if(sum_rmsg_sizes_resized > 0) {
644 rbuff_resized_build = Teuchos::arcp(new char[sum_rmsg_sizes_resized],
645 0, sum_rmsg_sizes_resized, true);
646 }
647
648 track_ptr = rbuff.getRawPtr();
649 char * track_ptr_resized = rbuff_resized_build.getRawPtr();
650 for (int i = 0; i < nrec; i++) {
651 memcpy(track_ptr_resized, track_ptr, find_msg_size);
652 track_ptr += find_msg_size;
653 track_ptr_resized += rmsg_sizes_resized[i];
654 }
655 }
656
657 // for performance, when not using variable sized we can optimize this step
658 // and use the original rbuff (there is no resizing to consider)
659 Teuchos::ArrayRCP<char> rbuff_resized = is_Zoltan2_Directory_Vector() ?
660 rbuff_resized_build : rbuff;
661
662 // Fill it with the true array data
663 track_offset_resized = 0; // for debugging
664 track_ptr = rbuff_resized.getRawPtr();
665 for (int i = 0; i < nrec; i++) {
666 typedef Zoltan2_DD_Find_Msg<gid_t,lid_t> find_msg_t;
667 find_msg_t *ptr = reinterpret_cast<find_msg_t*>(track_ptr);
668 user_t * puser = reinterpret_cast<user_t*>(
669 reinterpret_cast<char*>(ptr->adjData) + max_id_size);
670
671 // In original DD_Find_Local the first two values (gid and lid) are
672 // passed as the same, we send in gid and collect lid if it's used.
673 // that is why the find_msg_size is setup originally using max of
674 // sizeof(gid_t) and sizeof(lid_t)
675 err = find_local(ptr->adjData, (lid_t*)ptr->adjData,
676 puser, &ptr->partition, &ptr->proc, throw_if_missing);
677
678 const size_t & size_shift = rmsg_sizes_resized[i];
679 track_offset_resized += size_shift;
680 track_ptr += size_shift;
681 }
682
683 if(track_offset_resized != sum_rmsg_sizes_resized) {
684 throw std::logic_error("Bad sum!");
685 }
686
687 // This section is handled differently if it's variable sized array
688 size_t size_scale = is_Zoltan2_Directory_Vector() ? 1 : find_msg_size;
689
690 Teuchos::ArrayRCP<char> sbuff_resized;
691 if(!is_Zoltan2_Directory_Vector()) {
692 sbuff_resized = sbuff; // vector mode will set this and fill it
693 }
694
695 if(is_Zoltan2_Directory_Vector()) {
696 err = directoryComm.do_reverse(ZOLTAN2_DD_FIND_MSG_TAG+2,
697 rbuff_resized, size_scale, rmsg_sizes_resized, sbuff_resized);
698 }
699 else {
700 err = directoryComm.do_reverse(ZOLTAN2_DD_FIND_MSG_TAG+2,
701 rbuff_resized, size_scale, Teuchos::null, sbuff_resized);
702 }
703
704 if (err) {
705 throw std::logic_error("Zoltan2_Directory::find() do reverse failed");
706 }
707
708 if (debug_level > 6) {
709 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After Comm_Reverse");
710 }
711
712 // fill in user supplied lists with returned information
713 track_offset_resized = 0;
714
715 // it's not going to be in order... so don't use gid[i]
716 char * trackptr_resized = sbuff_resized.getRawPtr();
717 for (size_t i = 0; i < count; i++) {
718
719 if(track_offset_resized >= sbuff_resized.size()) {
720 printf(
721 "%d has gid.size() %d track_offset_resized: %d sbuff_resized: %d\n",
722 comm->getRank(),
723 (int) count, (int) track_offset_resized, (int) sbuff_resized.size());
724 throw std::logic_error("Bad buffer overflow! Internal error.");
725 }
726
727 msg_t *ptr = reinterpret_cast<msg_t*>(trackptr_resized);
728
729 if (owner)
730 owner[ptr->index] = ptr->proc;
731 if (partition)
732 partition[ptr->index] = ptr->partition ;
733 if (lid) {
734 memcpy (&lid[ptr->index], ptr->adjData, sizeof(lid_t));
735 }
736
737 user_t * pRead = reinterpret_cast<user_t*>(
738 reinterpret_cast<char*>(ptr->adjData) + max_id_size);
739
740 // if find_local failed proc is set to -1. Then we can leave the data
741 // untouched - the default behavior is to throw but the unit tests are
742 // set up to track and verify each remove id was properly taken out. To do
743 // this the test overrides with an optional flag on find() and says do not
744 // throw - and expects the data to remain untouched - then validates the
745 // data is not changed.
746 if(ptr->proc != -1 && user) {
747 raw_to_user(pRead, user[ptr->index]);
748 }
749
750 // don't use smsg_sizes_resized here because we don't get them back
751 // in the same order
752 size_t incoming_size = get_incoming_find_msg_size(ptr);
753 trackptr_resized += incoming_size;
754 track_offset_resized += incoming_size;
755 }
756
757 if(track_offset_resized != sbuff_resized.size()) {
758 throw std::logic_error("Bad buffer sum!");
759 }
760
761 if (debug_level > 6) {
762 ZOLTAN2_PRINT_INFO(comm->getRank(), yo, "After fill return lists");
763 }
764
765 int errcount = 0;
766 Teuchos::reduceAll<int>(*comm, Teuchos::REDUCE_SUM, 1, &errcount, &err);
767 err = (err) ? 1 : 0;
768
769 // if at least one GID was not found, potentially notify caller of error
770 if (debug_level > 0) {
771 char str[100]; /* diagnostic message string */
772 sprintf(str, "Processed %lu GIDs, GIDs not found: %d", count, errcount);
773 ZOLTAN2_PRINT_INFO (comm->getRank(), yo, str);
774 }
775
776 if (debug_level > 4) {
777 ZOLTAN2_TRACE_OUT(comm->getRank(), yo, NULL);
778 }
779
780 return err;
781}
782
783template <typename gid_t,typename lid_t,typename user_t>
785 gid_t* gid, /* incoming GID to locate (in) */
786 lid_t* lid, /* gid's LID (out) */
787 user_t *user, /* gid's user data (out) */
788 int *partition, /* gid's partition number (out) */
789 int *owner, /* gid's owner (processor number) (out) */
790 bool throw_if_missing) const
791{
792 const char * yo = "Zoltan2_Directory::find_local";
793
794 /* input sanity check */
795 if (owner == NULL || gid == NULL) {
796 throw std::logic_error("Zoltan2_Directory::find_local() Invalid input");
797 }
798
799 if (debug_level > 5) {
800 ZOLTAN2_TRACE_IN (comm->getRank(), yo, NULL);
801 }
802
803 /* compute offset into hash table to find head of linked list */
804 // Note performance is better if we first get index, then check valid_at,
805 // and use index to call value_at. Alternative is to call exists(*gid) and
806 // then node_map.value_at(node_map.find(*gid))) which is slower.
807 // TODO: Can this be optimized further?
808 size_t node_index = node_map.find(*gid);
809 if(node_map.valid_at(node_index))
810 {
812 node_map.value_at(node_index);
813 /* matching global ID found! Return gid's information */
814 if(lid) {
815 *lid = node.lid;
816 }
817
818 if (user) {
819 user_to_raw(node.userData, user);
820 }
821
822 if (owner) *owner = node.owner;
823 if (partition) *partition = node.partition;
824
825 if (debug_level > 5) {
826 ZOLTAN2_TRACE_OUT (comm->getRank(), yo, NULL);
827 }
828 return 0; // success point
829 }
830
831 // failure point
832 if (owner != NULL)
833 *owner = -1; /* JDT Added -1 owner not found */
834
835 if (debug_level > 5) {
836 ZOLTAN2_TRACE_OUT (comm->getRank(), yo, NULL);
837 }
838
839 if(throw_if_missing) {
840 // TODO: This used to be linked to debug_level but wanted to discuss as it
841 // seems we would want an error always if this failed.
842 throw std::logic_error("find_local did not succeed");
843 }
844
845 return 0;
846}
847
848// Print block
849template <typename gid_t,typename lid_t,typename user_t>
851{
852 const char * yo = "Zoltan2_Directory::print";
853
854 if (debug_level > 4) {
855 ZOLTAN2_TRACE_IN (comm->getRank(), yo, NULL);
856 }
857
858 for (size_t i = 0; i < node_map.size(); i++) {
860 node_map.value_at(i);
861 printf ("ZOLTAN DD Print(%d): \tList, \tGID ", comm->getRank());
862 printf("(");
863 // the issue here is that gid could be a simple type (int/long) or it
864 // could be an arbitrary structure such as:
865 // struct some_type {
866 // int x[4];
867 // }
868 // Directory works for such arbitrary type but only knows sizeof,
869 // not details of the internal implementation. In the testing framework
870 // the length of above x array is stored so it can be printed.
871 // TODO: Decide how to handle this - if we want directory to be able to
872 // print nice output of the gid (and lid) then perhaps we need to require
873 // that such structs define a to_string() method.
874 printf( "TODO: Decide how to print gid of arbitrary type.");
875 printf(") ");
876 if (use_lid) {
877 printf("\tLID (");
878 // see above note on the gid and printing
879 printf( "TODO: Decide how to print lid of arbitrary type.");
880 printf(") ");
881 }
882 printf ("\tPart %d\n", node.partition);
883 printf ("\tOwner %d\n", node.owner);
884 }
885
886 if (debug_level > 4) {
887 ZOLTAN2_TRACE_OUT (comm->getRank(), yo, NULL);
888 }
889 return 0;
890}
891
892// Stats block
893template <typename gid_t,typename lid_t,typename user_t>
895{
896 const char *yo = "Zoltan2_Directory::stats";
897
898 if (debug_level > 4) {
899 ZOLTAN2_TRACE_IN (comm->getRank(), yo, NULL);
900 }
901
902 char str[100]; // used to build message string
903 // not much to do here for equivalent to stats
904 // TODO: Consider removing stats()
905 sprintf(str, "Kokkos unordered map %d nodes.", node_map.size());
906 ZOLTAN2_PRINT_INFO (comm->getRank(), yo, str);
907 if (debug_level > 4) {
908 ZOLTAN2_TRACE_OUT (comm->getRank(), yo, NULL);
909 }
910}
911
912template <typename gid_t,typename lid_t,typename user_t>
914 size_t count,
915 const gid_t * gid) /* Incoming list of GIDs to remove */
916{
917 const char * yo = "Zoltan2_Directory::remove";
918
919 if (debug_level > 4) {
920 ZOLTAN2_TRACE_IN (comm->getRank(), yo, NULL);
921 }
922
923 int err = 0;
924
925 // allocate memory for processor contact list
926 Teuchos::ArrayRCP<int> procs;
927 Teuchos::ArrayRCP<char> sbuff;
928 if(count > 0) {
929 procs = Teuchos::arcp( // list of processors to contact
930 new int[count], 0, count, true);
931 sbuff = Teuchos::arcp( // send buffer
932 new char[count*remove_msg_size], 0, count*remove_msg_size, true);
933 }
934
936
937 // for each GID, fill in contact list and then message structure
938 char * trackptr = sbuff.getRawPtr();
939 for (size_t i = 0; i < count; i++) {
940 procs[i] = hash_proc(gid[i]);
941 msg_t *ptr = reinterpret_cast<msg_t*>(trackptr);
942 ptr->owner = comm->getRank();
943 *(ptr->adjData) = gid[i];
944 trackptr += remove_msg_size;
945 }
946
947 // now create efficient communication plan
948 Zoltan2_Directory_Comm directoryComm(count, procs, comm,
950 int nrec = directoryComm.getNRec();
951
952 if (err) {
953 throw std::logic_error("Zoltan_Comm_Create failed");
954 }
955
956 if (debug_level > 6) {
957 ZOLTAN2_PRINT_INFO (comm->getRank(), yo, "After Zoltan_Comm_Create");
958 }
959
960 // allocate receive buffer for nrec DD_Remove_Msg structures
961 Teuchos::ArrayRCP<char> rbuff;
962 if(nrec > 0) {
963 rbuff = Teuchos::arcp(new char[nrec*remove_msg_size],
964 0, nrec*remove_msg_size, true); // receive buffer
965 }
966
967 // send my update messages & receive updates directed to me
968 err = directoryComm.do_forward(ZOLTAN2_DD_UPDATE_MSG_TAG+1,
969 sbuff, remove_msg_size, rbuff);
970
971 if (err) {
972 throw std::logic_error("Comm_Do error");
973 }
974
975 if (debug_level > 6) {
976 ZOLTAN2_PRINT_INFO (comm->getRank(), yo, "After Zoltan_Comm_Do");
977 }
978
979 /* for each message rec'd, remove local directory info */
980 int errcount = 0;
981
982 // Note calling begin_erase() and end_erase() without actually erasing
983 // something leads to confusing seg faults. Hence nrec>0 check.
984 // Pulling begin_erase and end_erase out of the loop is important for
985 // performance. Since the actual erase is fairly simple we may consider
986 // eliminating remove_local and just calling here but will keep for now to
987 // keep symmetry with other modes.
988 if(nrec > 0) {
989 node_map.begin_erase();
990 for (int i = 0; i < nrec; i++) {
991 msg_t *ptr = reinterpret_cast<msg_t*>(&(rbuff[i*remove_msg_size]));
992 err = remove_local(ptr->adjData);
993 if (err == 1) { // TODO eliminate warns (1) make all errors
994 ++errcount;
995 }
996 }
997 node_map.end_erase();
998 } // if nrec > 0
999
1000 err = 0;
1001
1002 if (debug_level) {
1003 char str[100]; // used to build message string
1004 sprintf (str, "Processed %zu GIDs (%d local), %d GIDs not found",
1005 count, nrec, errcount);
1006 ZOLTAN2_PRINT_INFO (comm->getRank(), yo, str);
1007 err = (errcount) ? 1 : 0;
1008 }
1009
1010 return err;
1011}
1012
1013template <typename gid_t,typename lid_t,typename user_t>
1015 gid_t* gid) /* GID to be removed (in) */
1016{
1017 const char * yo = "Zoltan2_Directory::remove_local";
1018
1019 /* input sanity checking */
1020 if (gid == NULL) {
1021 throw std::logic_error("Invalid input argument");
1022 }
1023
1024 if (debug_level > 5) {
1025 ZOLTAN2_TRACE_IN (comm->getRank(), yo, NULL);
1026 }
1027
1028 if(node_map.exists(*gid)) {
1029 node_map.erase(*gid);
1030 return 0;
1031 }
1032
1033 /* We get here only if the global ID has not been found */
1034 if (debug_level > 5) {
1035 ZOLTAN2_TRACE_OUT (comm->getRank(), yo, NULL);
1036 }
1037
1038 return 1;
1039}
1040
1041template <typename gid_t,typename lid_t,typename user_t>
1043 const gid_t & gid) const
1044{
1045 uint32_t k;
1046
1047 // Copied from murmurc.3
1048 // TODO: Will be removed as Kokkos form develops
1049 #define ZOLTAN2_ROTL32(x,r) (uint32_t) \
1050 (((uint32_t)(x) << (int8_t)(r)) | ((uint32_t)(x) >> (32 - (int8_t)(r))))
1051
1052 const void * key = (void *)(&gid);
1053 int len = sizeof(gid_t);
1054 uint32_t seed = 14;
1055 void * out = (void *)&k;
1056
1057 const uint8_t * data = (const uint8_t*)key;
1058 const int nblocks = len / 4;
1059 int i;
1060 uint32_t h1 = seed;
1061 uint32_t c1 = 0xcc9e2d51;
1062 uint32_t c2 = 0x1b873593;
1063 const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
1064 for(i = -nblocks; i; i++)
1065 {
1066 uint32_t k1 = blocks[i];
1067 k1 *= c1;
1068 k1 = ZOLTAN2_ROTL32(k1,15);
1069 k1 *= c2;
1070 h1 ^= k1;
1071 h1 = ZOLTAN2_ROTL32(h1,13);
1072 h1 = h1*5+0xe6546b64;
1073 }
1074 const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
1075 uint32_t k1 = 0;
1076 switch(len & 3)
1077 {
1078 case 3: k1 ^= tail[2] << 16;
1079 case 2: k1 ^= tail[1] << 8;
1080 case 1: k1 ^= tail[0];
1081 k1 *= c1; k1 = ZOLTAN2_ROTL32(k1,15); k1 *= c2; h1 ^= k1;
1082 };
1083 h1 ^= len;
1084 h1 ^= h1 >> 16;
1085 h1 *= 0x85ebca6b;
1086 h1 ^= h1 >> 13;
1087 h1 *= 0xc2b2ae35;
1088 h1 ^= h1 >> 16;
1089 *(uint32_t*)out = h1;
1090
1091 return(k % comm->getSize());
1092}
1093
1094
1095/* TODO: Currently disabled - I need to review the benefits of this and see if
1096 we need this in the new version. Also how do we best handle this for variable
1097 sized data. */
1098/*
1099template <typename gid_t,typename lid_t,typename user_t>
1100size_t Zoltan2_Directory<gid_t,lid_t,user_t>::align_size_t(size_t a) const
1101{
1102 #define ZOLTAN2_ALIGN_VAL 7U
1103 return((ZOLTAN2_ALIGN_VAL + a) & ~ZOLTAN2_ALIGN_VAL);
1104}
1105*/
1106
1107} // end namespace Zoltan2
1108
1109#endif // ZOLTAN2_DIRECTORY_IMPL_H_
Zoltan2::BasicUserTypes< zscalar_t, zlno_t, zgno_t > user_t
Definition Metric.cpp:39
#define ZOLTAN2_TRACE_IN(proc, yo, str)
#define ZOLTAN2_DD_FIND_MSG_TAG
#define ZOLTAN2_DD_RESIZE_MSG_TAG
#define ZOLTAN2_PRINT_INFO(proc, yo, str)
#define ZOLTAN2_DD_UPDATE_MSG_TAG
#define ZOLTAN2_TRACE_OUT(proc, yo, str)
#define ZOLTAN2_ROTL32(x, r)
int do_reverse(int tag, const Teuchos::ArrayRCP< char > &send_data, int nbytes, const Teuchos::ArrayRCP< int > &sizes, Teuchos::ArrayRCP< char > &recv_data)
int do_forward(int tag, const Teuchos::ArrayRCP< char > &send_data, int nbytes, Teuchos::ArrayRCP< char > &recv_data)
int resize(const Teuchos::ArrayRCP< int > &sizes, int tag, int *sum_recv_sizes)
Zoltan2_Directory is an abstract base class.
Update_Mode
Update_Mode determines how update executes.
int update_local(gid_t *gid, lid_t *lid, user_t *user, int partition, int owner)
int print() const
gids to remove.
int remove(size_t length, const gid_t *gid)
if true will throw if a gid is not found. This is used by the unit tests to properly assess if remove...
int update(size_t length, const gid_t *gid, const lid_t *lid, const user_t *user, const int *partition, Update_Mode update_mode)
update is called by user to submit new data.
int copy(const Zoltan2_Directory< gid_t, lid_t, user_t > &dd)
int find_local(gid_t *gid, lid_t *lid, user_t *user, int *partition, int *owner, bool throw_if_missing=true) const
void stats() const
stats. New Kokkos mode needs further development.
unsigned int hash_proc(const gid_t &gid) const
int find(size_t length, const gid_t *gid, lid_t *lid, user_t *user, int *partition, int *owner, bool throw_if_missing=true)
Can be Replace, Add, or Aggregate.
Created by mbenlioglu on Aug 31, 2020.