TIMPI
packing.h
Go to the documentation of this file.
1 // The TIMPI Message-Passing Parallelism Library.
2 // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 
19 #ifndef TIMPI_PACKING_H
20 #define TIMPI_PACKING_H
21 
22 // TIMPI Includes
23 #include "timpi/packing_decl.h"
24 
25 #include "timpi/timpi_assert.h"
26 
27 // C++ includes
28 #include <climits> // CHAR_BIT
29 #include <cstring> // memcpy
30 #include <iterator>
31 #include <type_traits> // is_same
32 
33 // C++17 gives us "if constexpr", which nvc++ really seems to need in
34 // order to determine that warnings shouldn't be emitted inside a
35 // block instantiated as "if (32 < 32)", but let's see if we can
36 // stay backwards compatible a while longer
37 #if __cplusplus >= 201703
38 # define timpi_if_constexpr if constexpr
39 #else
40 # define timpi_if_constexpr if
41 #endif
42 
43 
44 // FIXME: This *should* be in TIMPI namespace but we have libMesh
45 // users which already partially specialized it
46 namespace libMesh
47 {
48 
49 namespace Parallel
50 {
51 
59 template <typename T, typename Enable>
60 class Packing {
61 public:
62  // Should be an MPI sendable type in specializations, e.g.
63  // typedef char buffer_type;
64  // typedef unsigned int buffer_type;
65 
66  // The following methods should be defined in every specialization,
67  // but there's no good generic definition we can fall back on.
68  // Leaving undefined generic methods declared here would turn
69  // missing-header bugs into link-time failures rather than
70  // compile-time failures - accordingly harder to diagnose.
71 #if 0
72  // Should copy an encoding of the provided object into the provided
73  // output iterator (which is of type buffer_type)
74  template <typename OutputIter, typename Context>
75  static void pack(const T & object,
76  OutputIter data_out,
77  const Context * context);
78 
79  // Should return the number of array entries (of type buffer_type)
80  // required to encode the provided object
81  template <typename Context>
82  static unsigned int packable_size(const T & object,
83  const Context * context);
84 
85  // Should return the number of array entries which were used to
86  // encode the provided serialization of an object which begins at
87  // \p iter
88  template <typename BufferIter>
89  static unsigned int packed_size(BufferIter iter);
90 
91  // Decode a potentially-variable-size object from a subsequence of a
92  // data array, returning a heap-allocated pointer to the result.
93  template <typename BufferIter, typename Context>
94  static T unpack(BufferIter in, Context * ctx);
95 #endif
96 };
97 
98 
99 // Utility functions for encoding and decoding lengths into buffers
100 // with data types that may be too small to hold an unsigned int. For
101 // MPI compatibility we assume that lengths do fit into an int.
102 template <typename buffer_type>
103 inline
104 constexpr int
106 {
107  return
108  (sizeof(unsigned int) + (sizeof(buffer_type)-1)) /
109  sizeof(buffer_type);
110 }
111 
112 
113 template <typename buffer_type, typename Iter>
114 inline
115 void
116 put_packed_len (unsigned int len, Iter data_out)
117 {
118  // I hoped decltype(*data_out) would always be buffer_type, but no dice
119 
120  // If we're using 2-byte or 1-byte buffer type then we have to split
121  // into multiple entries
122  constexpr int n_bits = (sizeof(buffer_type) * CHAR_BIT);
123 
124  // We may have a small signed buffer type into which we stuffed
125  // an unsigned value
126  if (n_bits < sizeof(unsigned int) * CHAR_BIT)
127  {
128  constexpr int size_entries = get_packed_len_entries<buffer_type>();
129 
130  // Some compilers warn about shifting by too many bits even when
131  // that's unreachable code. After we require C++17 we should
132  // see if "if constexpr" is a better ifx.
133  constexpr int compiler_workaround =
134  std::max(n_bits-1, int(sizeof(unsigned int) * CHAR_BIT));
135 
136  const std::size_t max_entry = std::size_t(1) <<
137  compiler_workaround;
138 
139  for (unsigned int i=0; i != size_entries; ++i)
140  {
141  *data_out++ = (len % max_entry);
142  len /= max_entry;
143  }
144 
145  return;
146  }
147 
148  // With 32 bits or more this is trivial
149  timpi_assert_equal_to(get_packed_len_entries<buffer_type>(), 1);
150  *data_out++ = len;
151 }
152 
153 
154 template <typename buffer_type>
155 inline
156 unsigned int
157 get_packed_len (typename std::vector<buffer_type>::const_iterator in)
158 {
159  // If we're using 2-byte or 1-byte buffer type then we have to split
160  // into multiple entries
161  constexpr int n_bits = (sizeof(buffer_type) * CHAR_BIT);
162 
163  // We may have a small signed buffer type into which we stuffed
164  // an unsigned value. Try to use constexpr here for efficiency and
165  // to avoid shift size warnings.
166  timpi_if_constexpr (n_bits < sizeof(unsigned int) * CHAR_BIT)
167  {
168  const int n_size_entries = get_packed_len_entries<buffer_type>();
169  unsigned int packed_len = 0;
170 
171  for (signed int i = n_size_entries-1; i >= 0; --i)
172  {
173  packed_len <<= n_bits;
174 
175  const auto next_entry = in[i];
176 
177  if (next_entry < 0)
178  packed_len += 1 << n_bits;
179 
180  packed_len += next_entry;
181  }
182  return packed_len;
183  }
184 
185  // With 32 bits or more this is trivial
186 
187  timpi_assert_equal_to(get_packed_len_entries<buffer_type>(), 1);
188  timpi_assert_greater_equal(*in, 0);
189 
190  return *in;
191 }
192 
193 
194 // Metafunction to get a value_type from map and unordered_map with
195 // non-const keys, so we can create a key/value pair more easily
196 template <typename ValueType>
198  typedef ValueType type;
199 };
200 
201 template <typename K, typename V>
202 struct DefaultValueType<std::pair<const K, V>> {
203  typedef std::pair<K, V> type;
204 };
205 
206 
207 // Superclass with utility methods for use with Packing partial
208 // specializations that mix fixed-size with Packing-required inner
209 // classes.
210 template <typename BufferType>
212 {
213  typedef BufferType buffer_type;
214 
215  template <typename T3>
216  struct IsFixed
217  {
218  static const bool value =
220  <typename DefaultValueType<T3>::type>::is_fixed_type;
221  };
222 
223  template <typename T3>
225  {
226  static const unsigned int value = (sizeof(T3) + sizeof(buffer_type) - 1) / sizeof(buffer_type);
227  };
228 
229  template <typename T3,
230  typename Context,
231  typename std::enable_if<IsFixed<T3>::value, int>::type = 0>
232  static unsigned int packable_size_comp(const T3 &, const Context *)
233  {
235  }
236 
237  // By not just doing memcpy here we can drop any padding
238  template <typename T1, typename T2,
239  typename Context,
240  typename std::enable_if<IsFixed<std::pair<T1, T2>>::value, int>::type = 0>
241  static unsigned int packable_size_comp(const std::pair<T1, T2> & comp, const Context * ctx)
242  {
243  return packable_size_comp(comp.first, ctx) +
244  packable_size_comp(comp.second, ctx);
245  }
246 
247  template <typename T3,
248  typename Context,
249  typename std::enable_if<!IsFixed<T3>::value, int>::type = 0>
250  static unsigned int packable_size_comp(const T3 & comp, const Context * ctx)
251  {
252  return Packing<T3>::packable_size(comp, ctx);
253  }
254 
255 // g++ 11.2.0 gives "not protecting ... less than 8 bytes long" here,
256 // and that's more paranoid than we wanted --enable-paranoid-warnings
257 // to be...
258 
259 #if defined(__GNUC__) && !defined(__INTEL_COMPILER) && !defined(__clang__)
260 #pragma GCC diagnostic push
261 #pragma GCC diagnostic ignored "-Wstack-protector"
262 #endif
263 
264  template <typename T3,
265  typename OutputIter,
266  typename Context,
267  typename std::enable_if<IsFixed<T3>::value, int>::type = 0>
268  static void pack_comp(const T3 & comp, OutputIter data_out, const Context *)
269  {
270  buffer_type T3_as_buffer_types[BufferTypesPer<T3>::value];
271  std::memcpy(T3_as_buffer_types, &comp, sizeof(T3));
272  for (unsigned int i = 0; i != BufferTypesPer<T3>::value; ++i)
273  *data_out++ = T3_as_buffer_types[i];
274  }
275 
276 #if defined(__GNUC__) && !defined(__INTEL_COMPILER) && !defined(__clang__)
277 #pragma GCC diagnostic pop
278 #endif
279 
280  // By not just doing memcpy here we can drop any padding
281  template <typename T1, typename T2,
282  typename OutputIter,
283  typename Context,
284  typename std::enable_if<IsFixed<std::pair<T1, T2>>::value, int>::type = 0>
285  static void pack_comp(const std::pair<T1, T2> & comp, OutputIter data_out, const Context * ctx)
286  {
287  pack_comp(comp.first, data_out, ctx);
288  pack_comp(comp.second, data_out, ctx);
289  }
290 
291  template <typename T3,
292  typename OutputIter,
293  typename Context,
294  typename std::enable_if<!IsFixed<T3>::value, int>::type = 0>
295  static void pack_comp(const T3 & comp, OutputIter data_out, const Context * ctx)
296  {
297  Packing<T3>::pack(comp, data_out, ctx);
298  }
299 
300 #if defined(__GNUC__) && !defined(__INTEL_COMPILER) && !defined(__clang__)
301 #pragma GCC diagnostic push
302 #pragma GCC diagnostic ignored "-Wstack-protector"
303 #endif
304 
305  template <typename T3,
306  typename BufferIter,
307  typename Context,
308  typename std::enable_if<IsFixed<T3>::value, int>::type = 0>
309  static void unpack_comp(T3 & comp, BufferIter in, Context *)
310  {
311  // memcpy is only safe to use with classes that are trivial to
312  // copy construct
313  //
314  // In this function overload, the enable_if<IsFixed> has already
315  // determined that we're safe in that respect.
316  //
317  // But the C++ standards don't mandate that important types like
318  // std::tuple ever satisfy is_trivially_copyable, and gcc goes so
319  // far as to emit warnings based on is_trivial instead, so we need
320  // to work around that here.
321  // https://gcc.gnu.org/legacy-ml/gcc-patches/2017-07/msg00299.html
322  char * comp_bytes = reinterpret_cast<char *>(&comp);
323  std::memcpy(comp_bytes, &(*in), sizeof(T3));
324  }
325 
326 #if defined(__GNUC__) && !defined(__INTEL_COMPILER) && !defined(__clang__)
327 #pragma GCC diagnostic pop
328 #endif
329 
330  template <typename T1, typename T2,
331  typename BufferIter,
332  typename Context,
333  typename std::enable_if<IsFixed<std::pair<T1, T2>>::value, int>::type = 0>
334  static void unpack_comp(std::pair<T1, T2> & comp, BufferIter in, Context * ctx)
335  {
336  unpack_comp(comp.first, in, ctx);
337 
338  in += packable_size_comp(comp.first, ctx);
339 
340  unpack_comp(comp.second, in, ctx);
341  }
342 
343 
344  template <typename T3,
345  typename BufferIter,
346  typename Context,
347  typename std::enable_if<!IsFixed<T3>::value, int>::type = 0>
348  static void unpack_comp(T3 & comp, BufferIter in, Context * ctx)
349  {
350  comp = Packing<T3>::unpack(in, ctx);
351  }
352 };
353 
354 
355 
356 
357 // Utility metafunction for use in Packing<std::pair>
358 template <typename T1, bool T1_has_buffer_type, typename T2, bool T2_has_buffer_type>
360 
361 template <typename T1, typename T2>
362 struct PairBufferTypeHelper<T1, true, T2, true>
363 {
364  static_assert(std::is_same<typename Packing<T1>::buffer_type, typename Packing<T2>::buffer_type>::value,
365  "For ease of use we cannot pack two types that use two different buffer types");
366 
367  typedef typename Packing<T1>::buffer_type buffer_type;
368 };
369 
370 template <typename T1, typename T2>
371 struct PairBufferTypeHelper<T1, true, T2, false>
372 {
374 };
375 
376 template <typename T1, typename T2>
377 struct PairBufferTypeHelper<T1, false, T2, true>
378 {
380 };
381 
382 template <typename T1, typename T2>
383 struct PairBufferTypeHelper<T1, false, T2, false>
384 {
385  typedef unsigned int buffer_type;
386 };
387 
388 
389 // specialization for std::pair
390 template <typename T1, typename T2>
391 class Packing<std::pair<T1, T2>,
392  typename std::enable_if<PairHasPacking<T1,T2>::value>::type>
393 {
394 public:
395  typedef typename std::remove_const<T1>::type cT1;
396 
397  typedef typename PairBufferTypeHelper
400 
402 
403  template <typename OutputIter, typename Context>
404  static void pack(const std::pair<T1, T2> & pr, OutputIter data_out, const Context * context);
405 
406  template <typename Context>
407  static unsigned int packable_size(const std::pair<T1, T2> & pr, const Context * context);
408 
409  template <typename BufferIter>
410  static unsigned int packed_size(BufferIter iter);
411 
412  template <typename BufferIter, typename Context>
413  static std::pair<T1, T2> unpack(BufferIter in, Context * ctx);
414 };
415 
416 template <typename T1, typename T2>
417 template <typename Context>
418 unsigned int
420  typename std::enable_if<PairHasPacking<T1,T2>::value>::type>::
421  packable_size(const std::pair<T1, T2> & pr, const Context * ctx)
422 {
423  return get_packed_len_entries<buffer_type>() +
424  Mixed::packable_size_comp(pr.first, ctx) +
425  Mixed::packable_size_comp(pr.second, ctx);
426 }
427 
428 template <typename T1, typename T2>
429 template <typename BufferIter>
430 unsigned int
432  typename std::enable_if<PairHasPacking<T1,T2>::value>::type>::
433  packed_size(BufferIter iter)
434 {
435  // We recorded the size in the first buffer entries
436  return get_packed_len<buffer_type>(iter);
437 }
438 
439 template <typename T1, typename T2>
440 template <typename OutputIter, typename Context>
441 void
443  typename std::enable_if<PairHasPacking<T1,T2>::value>::type>::
444  pack(const std::pair<T1, T2> & pr, OutputIter data_out, const Context * ctx)
445 {
446  unsigned int size = packable_size(pr, ctx);
447 
448  // First write out info about the buffer size
449  put_packed_len<buffer_type>(size, data_out);
450 
451  // Now pack the data
452  Mixed::pack_comp(pr.first, data_out, ctx);
453 
454  // TIMPI uses a back_inserter for `pack_range` so we don't (and can't)
455  // actually increment the iterator with operator+=. operator++ is a no-op
456  //
457  // data_out += packable_size_comp(pr.first, ctx);
458 
459  Mixed::pack_comp(pr.second, data_out, ctx);
460 }
461 
462 template <typename T1, typename T2>
463 template <typename BufferIter, typename Context>
464 std::pair<T1, T2>
466  typename std::enable_if<PairHasPacking<T1,T2>::value>::type>::
467  unpack(BufferIter in, Context * ctx)
468 {
469  std::pair<T1, T2> pr;
470 
471  // We ignore total size here but we have to increment past it
472  constexpr int size_bytes = get_packed_len_entries<buffer_type>();
473  in += size_bytes;
474 
475  // Unpack the data
476  Mixed::unpack_comp(pr.first, in, ctx);
477 
478  // Make sure we increment the iterator
479  in += Mixed::packable_size_comp(pr.first, ctx);
480 
481  Mixed::unpack_comp(pr.second, in, ctx);
482 
483  return pr;
484 }
485 
486 
487 
488 
489 template <bool T1_has_buffer_type, bool MoreTypes_have_buffer_Type, typename T1, typename... MoreTypes>
491 
492 template <typename T1, bool MoreTypes_have_buffer_Type>
493 struct TupleBufferTypeHelper<true, MoreTypes_have_buffer_Type, T1> {
495 };
496 
497 template <typename T1, typename... MoreTypes>
498 struct TupleBufferTypeHelper<true, true, T1, MoreTypes...> {
499  static_assert(std::is_same<typename Packing<T1>::buffer_type,
500  typename Packing<std::tuple<MoreTypes...>>::buffer_type>::value,
501  "For ease of use we cannot pack two types that use two different buffer types");
502 
503  typedef typename Packing<T1>::buffer_type buffer_type;
504 };
505 
506 template <typename T1, typename... MoreTypes>
507 struct TupleBufferTypeHelper<true, false, T1, MoreTypes...> {
509 };
510 
511 template <typename T1, typename... MoreTypes>
512 struct TupleBufferTypeHelper<false, true, T1, MoreTypes...> {
513  typedef typename Packing<std::tuple<MoreTypes...>>::buffer_type buffer_type;
514 };
515 
516 template <typename... Types>
518 
519 
520 template <typename T1>
521 struct TupleBufferType<T1>
522 {
524 };
525 
526 
527 template <typename T1, typename... MoreTypes>
528 struct TupleBufferType<T1, MoreTypes...>
529 {
530  typedef typename
532  Has_buffer_type<Packing<std::tuple<MoreTypes...>>>::value,
533  T1, MoreTypes...>::buffer_type buffer_type;
534 };
535 
536 
537 
538 // specializations for std::tuple
539 template <typename Enable>
540 class Packing<std::tuple<>, Enable> {};
541 
542 template <typename T, typename... Types>
543 class Packing<std::tuple<T, Types...>,
544  typename std::enable_if<TupleHasPacking<T, Types...>::value>::type>
545 {
546 public:
547  typedef typename TupleBufferType<T, Types...>::buffer_type buffer_type;
548 
550 
551  template <typename OutputIter, typename Context>
552  static void pack(const std::tuple<T, Types...> & tup, OutputIter data_out, const Context * context);
553 
554  template <typename Context>
555  static unsigned int packable_size(const std::tuple<T, Types...> & tup, const Context * context);
556 
557  template <typename BufferIter>
558  static unsigned int packed_size(BufferIter iter);
559 
560  template <typename BufferIter, typename Context>
561  static std::tuple<T, Types...> unpack(BufferIter in, Context * ctx);
562 
563  template <typename Context,
564  std::size_t I>
565  static typename std::enable_if<I == sizeof...(Types)+1, unsigned int>::type
566  tail_packable_size(const std::tuple<T, Types...> &,
567  const Context *)
568  { return 0; }
569 
570  template <typename Context,
571  std::size_t I>
572  static typename std::enable_if<I < sizeof...(Types)+1, unsigned int>::type
573  tail_packable_size(const std::tuple<T, Types...> &tup,
574  const Context * ctx)
575  {
576  return Mixed::packable_size_comp(std::get<I>(tup), ctx) +
577  tail_packable_size<Context, I+1>(tup, ctx);
578  }
579 
580  template <typename Context,
581  typename OutputIter,
582  std::size_t I>
583  static typename std::enable_if<I == sizeof...(Types)+1, void>::type
584  tail_pack_comp(const std::tuple<T, Types...> &,
585  OutputIter,
586  const Context *) {}
587 
588  template <typename Context,
589  typename OutputIter,
590  std::size_t I>
591  static typename std::enable_if<I < sizeof...(Types)+1, void>::type
592  tail_pack_comp(const std::tuple<T, Types...> &tup,
593  OutputIter data_out,
594  const Context * ctx)
595  {
596  Mixed::pack_comp(std::get<I>(tup), data_out, ctx);
597  tail_pack_comp<Context, OutputIter, I+1>(tup, data_out, ctx);
598  }
599 
600  template <typename Context,
601  typename BufferIter,
602  std::size_t I>
603  static typename std::enable_if<I == sizeof...(Types)+1, void>::type
604  tail_unpack_comp(std::tuple<T, Types...> &,
605  BufferIter &,
606  Context *) {}
607 
608  template <typename Context,
609  typename BufferIter,
610  std::size_t I>
611  static typename std::enable_if<I < sizeof...(Types)+1, void>::type
612  tail_unpack_comp(std::tuple<T, Types...> &tup,
613  BufferIter & in,
614  Context * ctx)
615  {
616  Mixed::unpack_comp(std::get<I>(tup), in, ctx);
617 
618  // Make sure we increment the iterator. The last increment will
619  // be unnecessary, since this is a copy of the iterator in the
620  // higher level unpacking code, but the first N-1 are critical.
621  in += Mixed::packable_size_comp(std::get<I>(tup), ctx);
622 
623  tail_unpack_comp<Context, BufferIter, I+1>(tup, in, ctx);
624  }
625 };
626 
627 
628 template <typename T, typename... Types>
629 template <typename Context>
630 unsigned int
631 Packing<std::tuple<T, Types...>,
632  typename std::enable_if<TupleHasPacking<T, Types...>::value>::type>::
633  packable_size(const std::tuple<T, Types...> & tup, const Context * ctx)
634 {
635  return get_packed_len_entries<buffer_type>() +
636  tail_packable_size<Context, 0>(tup, ctx);
637 }
638 
639 template <typename T, typename... Types>
640 template <typename BufferIter>
641 unsigned int
642 Packing<std::tuple<T, Types...>,
643  typename std::enable_if<TupleHasPacking<T, Types...>::value>::type>::
644  packed_size(BufferIter iter)
645 {
646  // We recorded the size in the first buffer entries
647  return get_packed_len<buffer_type>(iter);
648 }
649 
650 template <typename T, typename... Types>
651 template <typename OutputIter, typename Context>
652 void
653 Packing<std::tuple<T, Types...>,
654  typename std::enable_if<TupleHasPacking<T, Types...>::value>::type>::
655  pack(const std::tuple<T, Types...> & tup, OutputIter data_out, const Context * ctx)
656 {
657  unsigned int size = packable_size(tup, ctx);
658 
659  // First write out info about the buffer size
660  put_packed_len<buffer_type>(size, data_out);
661 
662  // Now pack the data
663  tail_pack_comp<Context, OutputIter, 0>(tup, data_out, ctx);
664 }
665 
666 template <typename T, typename... Types>
667 template <typename BufferIter, typename Context>
668 std::tuple<T, Types...>
669 Packing<std::tuple<T, Types...>,
670  typename std::enable_if<TupleHasPacking<T, Types...>::value>::type>::
671  unpack(BufferIter in, Context * ctx)
672 {
673  std::tuple<T, Types...> tup;
674 
675  // We ignore total size here but we have to increment past it
676  constexpr int size_bytes = get_packed_len_entries<buffer_type>();
677  in += size_bytes;
678 
679  // Unpack the data
680  tail_unpack_comp<Context, BufferIter, 0>(tup, in, ctx);
681 
682  return tup;
683 }
684 
685 
686 
687 // specialization for std::array
688 template <typename T, std::size_t N>
689 class Packing<std::array<T, N>,
690  typename std::enable_if<Has_buffer_type<Packing<T>>::value>::type>
691 {
692 public:
694 
696 
697  template <typename OutputIter, typename Context>
698  static void pack(const std::array<T, N> & a, OutputIter data_out, const Context * context);
699 
700  template <typename Context>
701  static unsigned int packable_size(const std::array<T, N> & a, const Context * context);
702 
703  template <typename BufferIter>
704  static unsigned int packed_size(BufferIter iter);
705 
706  template <typename BufferIter, typename Context>
707  static std::array<T, N> unpack(BufferIter in, Context * ctx);
708 };
709 
710 template <typename T, std::size_t N>
711 template <typename Context>
712 unsigned int
714  typename std::enable_if<Has_buffer_type<Packing<T>>::value>::type>::
715  packable_size(const std::array<T, N> & a, const Context * ctx)
716 {
717  unsigned int returnval = get_packed_len_entries<buffer_type>(); // size
718  for (const auto & entry : a)
719  returnval += Mixed::packable_size_comp(entry, ctx);
720  return returnval;
721 }
722 
723 template <typename T, std::size_t N>
724 template <typename BufferIter>
725 unsigned int
727  typename std::enable_if<Has_buffer_type<Packing<T>>::value>::type>::
728  packed_size(BufferIter iter)
729 {
730  // We recorded the size in the first buffer entries
731  return get_packed_len<buffer_type>(iter);
732 }
733 
734 template <typename T, std::size_t N>
735 template <typename OutputIter, typename Context>
736 void
738  typename std::enable_if<Has_buffer_type<Packing<T>>::value>::type>::
739  pack(const std::array<T, N> & a, OutputIter data_out, const Context * ctx)
740 {
741  unsigned int size = packable_size(a, ctx);
742 
743  // First write out info about the buffer size
744  put_packed_len<buffer_type>(size, data_out);
745 
746  // Now pack the data
747  for (const auto & entry : a)
748  Mixed::pack_comp(entry, data_out, ctx);
749 }
750 
751 template <typename T, std::size_t N>
752 template <typename BufferIter, typename Context>
753 std::array<T, N>
755  typename std::enable_if<Has_buffer_type<Packing<T>>::value>::type>::
756  unpack(BufferIter in, Context * ctx)
757 {
758  std::array<T, N> a;
759 
760  // We ignore total size here but we have to increment past it
761  constexpr int size_bytes = get_packed_len_entries<buffer_type>();
762  in += size_bytes;
763 
764  // Unpack the data
765  for (auto & entry : a)
766  {
767  Mixed::unpack_comp(entry, in, ctx);
768 
769  // Make sure we increment the iterator
770  in += Mixed::packable_size_comp(entry, ctx);
771  }
772 
773  return a;
774 }
775 
776 
777 
778 // Metafunction to choose buffer types: use a specified
779 // Packing<class>::buffer_type for any class that has one; use
780 // unsigned int otherwise.
781 template <typename T, typename Enable=void>
783 
784 template <typename T>
785 struct DefaultBufferType <T, typename std::enable_if<Has_buffer_type<Packing<T>>::value>::type>
786 {
787  typedef typename Packing<T>::buffer_type type;
788 };
789 
790 template <typename T>
791 struct DefaultBufferType <T, typename std::enable_if<!Has_buffer_type<Packing<T>>::value>::type>
792 {
793  typedef unsigned int type;
794 };
795 
796 
797 // helper class for any homogeneous-type variable-size containers
798 // which define the usual iterator ranges, value_type, etc.
799 template <typename Container>
801 {
802 public:
803  typedef typename
806 
808 
809  template <typename OutputIter, typename Context>
810  static void pack(const Container & a,
811  OutputIter data_out, const Context * context);
812 
813  template <typename Context>
814  static unsigned int packable_size(const Container & a,
815  const Context * context);
816 
817  template <typename BufferIter>
818  static unsigned int packed_size(BufferIter iter);
819 
820  template <typename BufferIter, typename Context>
821  static Container unpack(BufferIter in, Context * ctx);
822 };
823 
824 
825 template <typename Container>
826 template <typename Context>
827 unsigned int
828 PackingRange<Container>::packable_size(const Container & c, const Context * ctx)
829 {
830  unsigned int returnval = get_packed_len_entries<buffer_type>(); // size
831  for (const auto & entry : c)
832  returnval += Mixed::packable_size_comp(entry, ctx);
833  return returnval;
834 }
835 
836 template <typename Container>
837 template <typename BufferIter>
838 unsigned int
840 {
841  // We recorded the size in the first buffer entries
842  return get_packed_len<buffer_type>(iter);
843 }
844 
845 template <typename Container>
846 template <typename OutputIter, typename Context>
847 void
848 PackingRange<Container>::pack(const Container & c, OutputIter data_out, const Context * ctx)
849 {
850  unsigned int size = packable_size(c, ctx);
851 
852  // First write out info about the buffer size
853  put_packed_len<buffer_type>(size, data_out);
854 
855  // Now pack the data
856  for (const auto & entry : c)
857  Mixed::pack_comp(entry, data_out, ctx);
858 }
859 
860 template <typename Container>
861 template <typename BufferIter, typename Context>
862 Container
863 PackingRange<Container>::unpack(BufferIter in, Context * ctx)
864 {
865  Container c;
866 
867  unsigned int size = packed_size(in);
868 
869  timpi_assert_greater(size, 0);
870 
871  // Get the total size
872  constexpr int size_bytes = get_packed_len_entries<buffer_type>();
873  in += size_bytes;
874  size -= size_bytes;
875 
876  // Unpack the data
877  std::size_t unpacked_size = 0;
878  while (unpacked_size < size)
879  {
881  Mixed::unpack_comp(entry, in, ctx);
882 
883  c.insert(c.end(), entry);
884 
885  // Make sure we increment the iterator
886  const std::size_t unpacked_size_comp =
887  Mixed::packable_size_comp(entry, ctx);
888  in += unpacked_size_comp;
889  unpacked_size += unpacked_size_comp;
890  }
891 
892  // We should always finish at exactly the size we expected, not
893  // proceed past it
894  timpi_assert_equal_to(unpacked_size, size);
895 
896  return c;
897 }
898 
899 
900 
901 #define TIMPI_PACKING_RANGE_SUBCLASS(Container) \
902 class Packing<Container, \
903  typename std::enable_if<Has_buffer_type<Packing<typename Container::value_type>>::value || \
904  TIMPI::StandardType<typename Container::value_type>::is_fixed_type>::type> : \
905  public PackingRange<Container> \
906 { \
907 public: \
908  using typename PackingRange<Container>::buffer_type; \
909  \
910  using typename PackingRange<Container>::Mixed; \
911  \
912  using PackingRange<Container>::pack; \
913  using PackingRange<Container>::packable_size; \
914  using PackingRange<Container>::packed_size; \
915  using PackingRange<Container>::unpack; \
916 }
917 
918 
919 template <typename T, typename A>
920 TIMPI_PACKING_RANGE_SUBCLASS(std::vector<T TIMPI_P_COMMA A>);
921 
922 template <typename T, typename A>
923 TIMPI_PACKING_RANGE_SUBCLASS(std::list<T TIMPI_P_COMMA A>);
924 
925 template <typename K, typename T, typename C, typename A>
926 TIMPI_PACKING_RANGE_SUBCLASS(std::map<K TIMPI_P_COMMA T TIMPI_P_COMMA C TIMPI_P_COMMA A>);
927 
928 template <typename K, typename T, typename C, typename A>
929 TIMPI_PACKING_RANGE_SUBCLASS(std::multimap<K TIMPI_P_COMMA T TIMPI_P_COMMA C TIMPI_P_COMMA A>);
930 
931 template <typename K, typename C, typename A>
932 TIMPI_PACKING_RANGE_SUBCLASS(std::multiset<K TIMPI_P_COMMA C TIMPI_P_COMMA A>);
933 
934 template <typename K, typename C, typename A>
935 TIMPI_PACKING_RANGE_SUBCLASS(std::set<K TIMPI_P_COMMA C TIMPI_P_COMMA A>);
936 
937 template <typename K, typename T, typename H, typename KE, typename A>
938 TIMPI_PACKING_RANGE_SUBCLASS(std::unordered_map<K TIMPI_P_COMMA T TIMPI_P_COMMA H TIMPI_P_COMMA KE TIMPI_P_COMMA A>);
939 
940 template <typename K, typename T, typename H, typename KE, typename A>
941 TIMPI_PACKING_RANGE_SUBCLASS(std::unordered_multimap<K TIMPI_P_COMMA T TIMPI_P_COMMA H TIMPI_P_COMMA KE TIMPI_P_COMMA A>);
942 
943 template <typename K, typename H, typename KE, typename A>
944 TIMPI_PACKING_RANGE_SUBCLASS(std::unordered_multiset<K TIMPI_P_COMMA H TIMPI_P_COMMA KE TIMPI_P_COMMA A>);
945 
946 template <typename K, typename H, typename KE, typename A>
947 TIMPI_PACKING_RANGE_SUBCLASS(std::unordered_set<K TIMPI_P_COMMA H TIMPI_P_COMMA KE TIMPI_P_COMMA A>);
948 
949 
950 template <typename T>
951 class Packing<std::basic_string<T>,
952  typename std::enable_if<TIMPI::StandardType<T>::is_fixed_type>::type>
953 {
954 public:
955 
956  typedef unsigned int buffer_type;
957 
958  static_assert(sizeof(T) <= sizeof(buffer_type),
959  "We don't support strings with larger characters than unsigned int");
960 
961  static constexpr int T_per_buffer_type = (sizeof(buffer_type) + sizeof(T) - 1)/sizeof(T);
962 
963  static unsigned int
964  packed_size (typename std::vector<buffer_type>::const_iterator in)
965  {
966  // One entry for size, then pack the data tightly.
967  return 1 + (*in+T_per_buffer_type-1)/T_per_buffer_type;
968  }
969 
970  static unsigned int packable_size
971  (const std::basic_string<T> & s,
972  const void *)
973  {
974  return 1 + (s.size()+T_per_buffer_type-1)/T_per_buffer_type;
975  }
976 
977 
978  template <typename Iter>
979  static void pack (const std::basic_string<T> & b, Iter data_out,
980  const void *)
981  {
982  *data_out++ = b.size();
983  std::size_t i = 0;
984  for (; i + T_per_buffer_type < b.size(); i += T_per_buffer_type)
985  *data_out++ = *reinterpret_cast<const buffer_type *>(&b[i]);
986  if (i != b.size())
987  {
988  T with_padding[T_per_buffer_type] = {};
989  std::copy(b.begin()+i, b.end(), &with_padding[0]);
990  *data_out++ = *reinterpret_cast<const buffer_type *>(&with_padding[0]);
991  }
992  }
993 
994  static std::basic_string<T>
995  unpack (typename std::vector<buffer_type>::const_iterator in, void *)
996  {
997  const unsigned int string_len = *in++;
998 
999  const T * buf =
1000  reinterpret_cast<const T *>(&(*in));
1001  in += string_len / T_per_buffer_type;
1002  return {buf, buf+string_len};
1003  }
1004 
1005 };
1006 
1007 } // namespace Parallel
1008 
1009 } // namespace libMesh
1010 
1011 
1012 namespace TIMPI {
1013 
1015 
1016 // ------------------------------------------------------------
1017 // Packing member functions, global functions
1018 
1022 template <typename Context, typename Iter>
1023 inline std::size_t packed_range_size (const Context * context,
1024  Iter range_begin,
1025  const Iter range_end)
1026 {
1027  typedef typename std::iterator_traits<Iter>::value_type T;
1028 
1029  std::size_t buffer_size = 0;
1030  for (Iter range_count = range_begin;
1031  range_count != range_end;
1032  ++range_count)
1033  {
1034  buffer_size += Packing<T>::packable_size(*range_count, context);
1035  }
1036  return buffer_size;
1037 }
1038 
1039 
1043 template <typename Context, typename buffertype, typename Iter>
1044 inline Iter pack_range (const Context * context,
1045  Iter range_begin,
1046  const Iter range_end,
1047  std::vector<buffertype> & buffer,
1048  // When we serialize into buffers, we need to use large buffers to optimize MPI
1049  // bandwidth, but not so large as to risk allocation failures. max_buffer_size
1050  // is measured in number of buffer type entries; number of bytes may be 4 or 8
1051  // times larger depending on configuration.
1052  std::size_t approx_buffer_size)
1053 {
1054  typedef typename std::iterator_traits<Iter>::value_type T;
1055 
1056  // Count the total size of and preallocate buffer for efficiency.
1057  // Prepare to stop early if the buffer would be too large.
1058  std::size_t buffer_size = 0;
1059  Iter range_stop = range_begin;
1060  for (; range_stop != range_end && buffer_size < approx_buffer_size;
1061  ++range_stop)
1062  {
1063  std::size_t next_buffer_size =
1064  Packing<T>::packable_size(*range_stop, context);
1065  buffer_size += next_buffer_size;
1066  }
1067  buffer.reserve(buffer.size() + buffer_size);
1068 
1069  // Pack the objects into the buffer
1070  for (; range_begin != range_stop; ++range_begin)
1071  {
1072 #ifndef NDEBUG
1073  std::size_t old_size = buffer.size();
1074 #endif
1075 
1077  (*range_begin, std::back_inserter(buffer), context);
1078 
1079 #ifndef NDEBUG
1080  unsigned int my_packable_size =
1081  Packing<T>::packable_size(*range_begin, context);
1082  unsigned int my_packed_size =
1083  Packing<T>::packed_size (buffer.begin() + old_size);
1084  timpi_assert_equal_to (my_packable_size, my_packed_size);
1085  timpi_assert_equal_to (buffer.size(), old_size + my_packable_size);
1086 #endif
1087  }
1088 
1089  return range_stop;
1090 }
1091 
1092 
1093 
1101 template <typename Context, typename buffertype,
1102  typename OutputIter, typename T>
1103 inline OutputIter unpack_range (const std::vector<buffertype> & buffer,
1104  Context * context,
1105  OutputIter out_iter,
1106  const T * /* output_type */)
1107 {
1108  // Loop through the buffer and unpack each object, returning the
1109  // object pointer via the output iterator
1110  typename std::vector<buffertype>::const_iterator
1111  next_object_start = buffer.begin();
1112 
1113  while (next_object_start < buffer.end())
1114  {
1115  *out_iter++ = Packing<T>::unpack(next_object_start, context);
1116  next_object_start +=
1117  Packing<T>::packed_size(next_object_start);
1118  }
1119 
1120  // We should have used up the exact amount of data in the buffer
1121  timpi_assert (next_object_start == buffer.end());
1122 
1123  return out_iter;
1124 }
1125 
1126 } // namespace TIMPI
1127 
1128 #endif // TIMPI_PACKING_H
OutputIter unpack_range(const std::vector< buffertype > &buffer, Context *context, OutputIter out_iter, const T *)
Helper function for range unpacking.
Definition: packing.h:1103
void put_packed_len(unsigned int len, Iter data_out)
Definition: packing.h:116
static unsigned int packable_size(const Container &a, const Context *context)
Definition: packing.h:828
static std::basic_string< T > unpack(typename std::vector< buffer_type >::const_iterator in, void *)
Definition: packing.h:995
static Container unpack(BufferIter in, Context *ctx)
Definition: packing.h:863
static void pack_comp(const std::pair< T1, T2 > &comp, OutputIter data_out, const Context *ctx)
Definition: packing.h:285
Templated class to provide the appropriate MPI datatype for use with built-in C types or simple C++ c...
Definition: standard_type.h:83
static void pack_comp(const T3 &comp, OutputIter data_out, const Context *ctx)
Definition: packing.h:295
Define data types and (un)serialization functions for use when encoding a potentially-variable-size o...
Definition: packing.h:60
static T unpack(BufferIter in, Context *ctx)
static unsigned int packable_size(const T &object, const Context *context)
unsigned int get_packed_len(typename std::vector< buffer_type >::const_iterator in)
Definition: packing.h:157
Packing< T1 >::buffer_type buffer_type
Definition: packing.h:523
static std::enable_if< I==sizeof...(Types)+1, unsigned int >::type tail_packable_size(const std::tuple< T, Types... > &, const Context *)
Definition: packing.h:566
Packing< std::tuple< MoreTypes... > >::buffer_type buffer_type
Definition: packing.h:513
static unsigned int packable_size_comp(const std::pair< T1, T2 > &comp, const Context *ctx)
Definition: packing.h:241
static unsigned int packed_size(BufferIter iter)
Definition: packing.h:839
static unsigned int packed_size(BufferIter iter)
static void pack(const Container &a, OutputIter data_out, const Context *context)
Definition: packing.h:848
Iter pack_range(const Context *context, Iter range_begin, const Iter range_end, std::vector< buffertype > &buffer, std::size_t approx_buffer_size)
Helper function for range packing.
Definition: packing.h:1044
static void pack(const T &object, OutputIter data_out, const Context *context)
constexpr int get_packed_len_entries()
Definition: packing.h:105
static void unpack_comp(std::pair< T1, T2 > &comp, BufferIter in, Context *ctx)
Definition: packing.h:334
TupleBufferTypeHelper< Has_buffer_type< Packing< T1 > >::value, Has_buffer_type< Packing< std::tuple< MoreTypes... > > >::value, T1, MoreTypes... >::buffer_type buffer_type
Definition: packing.h:533
static void unpack_comp(T3 &comp, BufferIter in, Context *ctx)
Definition: packing.h:348
static void unpack_comp(T3 &comp, BufferIter in, Context *)
Definition: packing.h:309
static void pack_comp(const T3 &comp, OutputIter data_out, const Context *)
Definition: packing.h:268
PairBufferTypeHelper< cT1, Has_buffer_type< Packing< cT1 > >::value, T2, Has_buffer_type< Packing< T2 > >::value >::buffer_type buffer_type
Definition: packing.h:399
static unsigned int packable_size_comp(const T3 &, const Context *)
Definition: packing.h:232
PackingMixedType< buffer_type > Mixed
Definition: packing.h:807
static unsigned int packable_size_comp(const T3 &comp, const Context *ctx)
Definition: packing.h:250
std::size_t packed_range_size(const Context *context, Iter range_begin, const Iter range_end)
Helper function for range packing.
Definition: packing.h:1023
TIMPI_PACKING_RANGE_SUBCLASS(std::vector< T TIMPI_P_COMMA A >)
DefaultBufferType< typename Container::value_type >::type buffer_type
Definition: packing.h:805