deal.II version GIT relicensing-6809-ge913b9bb34 2026-09-25 17:20:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
work_stream.h
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2009 - 2025 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13#ifndef dealii_work_stream_h
14# define dealii_work_stream_h
15
16
17# include <deal.II/base/config.h>
18
28
29# ifdef DEAL_II_WITH_TBB
30# ifdef DEAL_II_TBB_WITH_ONEAPI
31# include <tbb/parallel_pipeline.h>
32# else
33# include <tbb/pipeline.h>
34# endif
35# include <tbb/blocked_range.h>
36# endif
37
38# ifdef DEAL_II_WITH_TASKFLOW
39# include <taskflow/taskflow.hpp>
40# endif
41
42# include <functional>
43# include <iterator>
44# include <list>
45# include <memory>
46# include <utility>
47# include <vector>
48
50
51
52
165namespace WorkStream
166{
171 namespace internal
172 {
178 template <typename Iterator, typename ScratchData, typename CopyData>
180 {
181 std::unique_ptr<ScratchData> scratch_data;
182 std::unique_ptr<CopyData> copy_data;
184
191
192 ScratchAndCopyDataObjects(std::unique_ptr<ScratchData> &&p,
193 std::unique_ptr<CopyData> &&q,
194 const bool in_use)
195 : scratch_data(std::move(p))
196 , copy_data(std::move(q))
197 , currently_in_use(in_use)
198 {}
199
200 // Provide a copy constructor that actually doesn't copy the
201 // internal state. This makes handling ScratchAndCopyDataObjects
202 // easier to handle with STL containers.
206 };
207
213 template <typename ScratchData>
215 {
216 std::unique_ptr<ScratchData> scratch_data;
218
223 : currently_in_use(false)
224 {}
225
226 ScratchDataObject(std::unique_ptr<ScratchData> &&p, const bool in_use)
227 : scratch_data(std::move(p))
228 , currently_in_use(in_use)
229 {}
230
231 ScratchDataObject(ScratchData *p, const bool in_use)
232 : scratch_data(p)
233 , currently_in_use(in_use)
234 {}
235
236 // Provide a copy constructor that actually doesn't copy the
237 // internal state. This makes handling ScratchAndCopyDataObjects
238 // easier to handle with STL containers.
242
243 ScratchDataObject(ScratchDataObject &&o) noexcept = default;
244 };
245
246# ifdef DEAL_II_WITH_TBB
261 namespace tbb_no_coloring
262 {
266 template <typename Iterator, typename ScratchData, typename CopyData>
268 {
269 public:
276 struct ItemType
277 {
282 using ScratchDataList = std::list<ScratchDataObject<ScratchData>>;
283
288 std::vector<Iterator> iterators;
289
295 std::vector<CopyData> copy_datas;
296
302 unsigned int n_iterators;
303
336
341 const ScratchData *sample_scratch_data;
342
348
349
355 : n_iterators(0)
356 , scratch_data(nullptr)
357 , sample_scratch_data(nullptr)
358 , currently_in_use(false)
359 {}
360 };
361
362
369 const Iterator &end,
370 const unsigned int buffer_size,
371 const unsigned int chunk_size,
372 const ScratchData &sample_scratch_data,
373 const CopyData &sample_copy_data)
375 , item_buffer(buffer_size)
378 {
379 // initialize the elements of the ring buffer
380 for (auto &item : item_buffer)
381 {
382 Assert(item.n_iterators == 0, ExcInternalError());
383
384 item.iterators.resize(chunk_size,
386 item.scratch_data = &thread_local_scratch;
387 item.sample_scratch_data = &sample_scratch_data;
388 item.copy_datas.resize(chunk_size, sample_copy_data);
389 item.currently_in_use = false;
390 }
391 }
392
393
397 ItemType *
399 {
400 // find first unused item. we know that there must be one
401 // because we have set the maximal number of tokens in flight
402 // and have set the ring buffer to have exactly this size. so
403 // if this function is called, we know that less than the
404 // maximal number of items in currently in flight
405 //
406 // note that we need not lock access to this array since
407 // the current stage is run sequentially and we can therefore
408 // enter the following block only once at any given time.
409 // thus, there can be no race condition between checking that
410 // a flag is false and setting it to true. (there may be
411 // another thread where we release items and set 'false'
412 // flags to 'true', but that too does not produce any
413 // problems)
414 ItemType *current_item = nullptr;
415 for (unsigned int i = 0; i < item_buffer.size(); ++i)
416 if (item_buffer[i].currently_in_use == false)
417 {
419 current_item = &item_buffer[i];
420 break;
421 }
422 Assert(current_item != nullptr,
423 ExcMessage("This can't be. There must be a free item!"));
424
425 // initialize the next item. it may
426 // consist of at most chunk_size
427 // elements
428 current_item->n_iterators = 0;
429 while ((remaining_iterator_range.first !=
430 remaining_iterator_range.second) &&
431 (current_item->n_iterators < chunk_size))
432 {
433 current_item->iterators[current_item->n_iterators] =
435
437 ++current_item->n_iterators;
438 }
439
440 if (current_item->n_iterators == 0)
441 // there were no items
442 // left. terminate the pipeline
443 return nullptr;
444 else
445 return current_item;
446 }
447
448 private:
453 std::pair<Iterator, Iterator> remaining_iterator_range;
454
458 std::vector<ItemType> item_buffer;
459
492
498 const ScratchData &sample_scratch_data;
499
506 const unsigned int chunk_size;
507 };
508
509
510
511 template <typename Worker,
512 typename Copier,
513 typename Iterator,
514 typename ScratchData,
515 typename CopyData>
516 void
517 run(const Iterator &begin,
519 Worker worker,
520 Copier copier,
521 const ScratchData &sample_scratch_data,
522 const CopyData &sample_copy_data,
523 const unsigned int queue_length,
524 const unsigned int chunk_size)
525 {
526 using ItemType = typename IteratorRangeToItemStream<Iterator,
527 ScratchData,
528 CopyData>::ItemType;
529
530 // Define the three stages of the pipeline:
531
532 //
533 // ----- Stage 1 -----
534 //
535 // The first stage is the one that provides us with chunks of data
536 // to work on (the stream of "items"). This stage will run sequentially.
538 iterator_range_to_item_stream(begin,
539 end,
540 queue_length,
541 chunk_size,
542 sample_scratch_data,
543 sample_copy_data);
544 auto item_generator = [&](tbb::flow_control &fc) -> ItemType * {
545 if (const auto item = iterator_range_to_item_stream.get_item())
546 return item;
547 else
548 {
549 fc.stop();
550 return nullptr;
551 }
552 };
553
554 //
555 // ----- Stage 2 -----
556 //
557 // The second stage is the one that does the actual work. This is the
558 // stage that runs in parallel
559 auto item_worker =
560 [worker =
561 std::function<void(const Iterator &, ScratchData &, CopyData &)>(
562 worker),
563 copier_exists =
564 static_cast<bool>(std::function<void(const CopyData &)>(copier))](
565 ItemType *current_item) {
566 // we need to find an unused scratch data object in the list that
567 // corresponds to the current thread and then mark it as used. if
568 // we can't find one, create one
569 //
570 // as discussed in the discussion of the documentation of the
571 // IteratorRangeToItemStream::scratch_data variable, there is no
572 // need to synchronize access to this variable using a mutex
573 // as long as we have no yield-point in between. this means that
574 // we can't take an iterator into the list now and expect it to
575 // still be valid after calling the worker, but we at least do
576 // not have to lock the following section
577 ScratchData *scratch_data = nullptr;
578 {
579 // see if there is an unused object. if so, grab it and mark
580 // it as used
581 for (auto &p : current_item->scratch_data->get())
582 if (p.currently_in_use == false)
583 {
584 scratch_data = p.scratch_data.get();
585 p.currently_in_use = true;
586
587 break;
588 }
589
590 // if no object was found, create one and mark it as used
591 if (scratch_data == nullptr)
592 {
593 scratch_data =
594 new ScratchData(*current_item->sample_scratch_data);
595 current_item->scratch_data->get().emplace_back(scratch_data,
596 true);
597 }
598 };
599
600 // then call the worker function on each element of the chunk we
601 // were given. since these worker functions are called on separate
602 // threads, nothing good can happen if they throw an exception and
603 // we are best off catching it and showing an error message
604 for (unsigned int i = 0; i < current_item->n_iterators; ++i)
605 {
606 try
607 {
608 if (worker)
609 worker(current_item->iterators[i],
610 *scratch_data,
611 current_item->copy_datas[i]);
612 }
613 catch (const std::exception &exc)
614 {
616 }
617 catch (...)
618 {
620 }
621 }
622
623 // finally mark the scratch object as unused again. as above, there
624 // is no need to lock anything here since the object we work on
625 // is thread-local
626 for (auto &p : current_item->scratch_data->get())
627 if (p.scratch_data.get() == scratch_data)
628 {
629 Assert(p.currently_in_use == true, ExcInternalError());
630 p.currently_in_use = false;
631
632 break;
633 }
634
635 // if there is no copier, mark current item as usable again
636 if (copier_exists == false)
637 current_item->currently_in_use = false;
638
639
640 // Then return the original pointer
641 // to the now modified object. The copier will work on it next.
642 return current_item;
643 };
644
645 //
646 // ----- Stage 3 -----
647 //
648 // The last stage is the one that copies data from the CopyData objects
649 // to the final destination. This stage runs sequentially again.
650 auto item_copier = [copier = std::function<void(const CopyData &)>(
651 copier)](ItemType *current_item) {
652 if (copier)
653 {
654 // Initiate copying data. For the same reasons as in the worker
655 // class above, catch exceptions rather than letting them
656 // propagate into unknown territories:
657 for (unsigned int i = 0; i < current_item->n_iterators; ++i)
658 {
659 try
660 {
661 copier(current_item->copy_datas[i]);
662 }
663 catch (const std::exception &exc)
664 {
666 }
667 catch (...)
668 {
670 }
671 }
672 }
673 // mark current item as usable again
674 current_item->currently_in_use = false;
675 };
676
677
678 // Now we just have to set up the pipeline and run it:
679 auto tbb_item_stream_filter = tbb::make_filter<void, ItemType *>(
681 tbb::filter_mode::serial_in_order,
682# else
683 tbb::filter::serial,
684# endif
685 item_generator);
686
687 auto tbb_worker_filter = tbb::make_filter<ItemType *, ItemType *>(
689 tbb::filter_mode::parallel,
690# else
691 tbb::filter::parallel,
692# endif
693 item_worker);
694
695 auto tbb_copier_filter = tbb::make_filter<ItemType *, void>(
697 tbb::filter_mode::serial_in_order,
698# else
699 tbb::filter::serial,
700# endif
701 item_copier);
702
703 tbb::parallel_pipeline(queue_length,
704 tbb_item_stream_filter & tbb_worker_filter &
705 tbb_copier_filter);
706 }
707
708 } // namespace tbb_no_coloring
709# endif // DEAL_II_WITH_TBB
710
711
712
713# ifdef DEAL_II_WITH_TASKFLOW
721 namespace taskflow_no_coloring
722 {
729 template <typename Worker,
730 typename Copier,
731 typename Iterator,
732 typename ScratchData,
733 typename CopyData>
734 void
735 run(const Iterator &begin,
737 Worker worker,
738 Copier copier,
739 const ScratchData &sample_scratch_data,
740 const CopyData &sample_copy_data,
741 const unsigned int /*queue_length*/ = 2 *
743 const unsigned int chunk_size = 8)
744
745 {
746 tf::Executor &executor = MultithreadInfo::get_taskflow_executor();
747 tf::Taskflow taskflow;
748
750 thread_safe_scratch_datas;
751
752 tf::Task last_copier;
753
754
755 // A collection of chunk_size copy objects which each represent the
756 // contribution of a single worker. Chunk_size workers are thus chunked
757 // together by having their outputs stored in this object in a single
758 // task.
759 struct CopyChunk
760 {
761 std::vector<CopyData> copy_datas;
762
763 CopyChunk(const unsigned int chunk_size,
764 const CopyData &sample_copy_data)
765 : copy_datas(chunk_size, sample_copy_data)
766 {}
767 };
768
769 // CopyData objects are processed in the order they are created: record
770 // that with a unique chunk_no for each one
771 std::size_t chunk_no = 0;
772
773 // A collection of handles to a CopyChunk object for each chunk. The
774 // actual data will be allocated only when a worker arrives at this
775 // chunk and will be freed as soon as the result has been copied.
776 std::vector<std::unique_ptr<CopyChunk>> copy_chunks;
777
778 // Generate a static task graph. Here we generate a task for each cell
779 // that will be worked on. The tasks are not executed until all of them
780 // are created, this code runs sequentially.
781 for (Iterator it = begin; it != end;)
782 {
783 // Since Iterator can be an iterator, or an int, etc. (anything
784 // incrementable) we can't rely on anything but operator++ being
785 // defined (and it being copyable). We can also assume that it isn't
786 // an input iterator since we can run workers in an arbitrary order.
787 // Hence, we can't use std::distance(), so instead just calculate
788 // each chunk size directly.
789 std::size_t current_chunk_size = 0;
790 Iterator current_chunk_start = it;
791 for (unsigned int i = 0; (i < chunk_size) && (it != end); ++it, ++i)
792 ++current_chunk_size;
793 // Create a worker task.
794 auto worker_task =
795 taskflow
796 .emplace([current_chunk_start,
797 current_chunk_size,
798 chunk_no,
799 &thread_safe_scratch_datas,
800 &sample_scratch_data,
801 &sample_copy_data,
802 &copy_chunks,
803 &worker]() {
804 ScratchData *scratch_data = nullptr;
805
806 auto &scratch_datas = thread_safe_scratch_datas.get();
807 // We need to find an unused scratch data object in the list
808 // that corresponds to the current thread and then mark it as
809 // used. if we can't find one, create one. There is no need to
810 // synchronize access to this variable using a mutex since
811 // each object is local to its own thread.
812 for (auto &p : scratch_datas)
813 {
814 if (p.currently_in_use == false)
815 {
816 scratch_data = p.scratch_data.get();
817 p.currently_in_use = true;
818 break;
819 }
820 }
821 // If no element in the list was found, create
822 // one and mark it as used.
823 if (scratch_data == nullptr)
824 {
825 scratch_datas.emplace_back(std::make_unique<ScratchData>(
826 sample_scratch_data),
827 true);
828 scratch_data = scratch_datas.back().scratch_data.get();
829 }
830
831 // Create a unique copy chunk object where this
832 // worker's work will be stored.
833 copy_chunks[chunk_no] =
834 std::make_unique<CopyChunk>(current_chunk_size,
835 sample_copy_data);
836 auto &copy_chunk = copy_chunks[chunk_no];
837 auto it = current_chunk_start;
838 for (std::size_t i = 0; i < current_chunk_size; ++i)
839 {
840 worker(it, *scratch_data, (copy_chunk->copy_datas)[i]);
841 ++it;
842 }
843
844 // Find our currently used scratch data and
845 // mark it as unused.
846 for (auto &p : scratch_datas)
847 {
848 if (p.scratch_data.get() == scratch_data)
849 {
850 Assert(p.currently_in_use == true,
852 p.currently_in_use = false;
853 }
854 }
855 })
856 .name("worker");
857
858 // Create a copier task. This task is a separate object from the
859 // worker task.
860 tf::Task copier_task =
861 taskflow
862 .emplace([chunk_no, &copy_chunks, &copier]() {
863 auto copy_chunk = copy_chunks[chunk_no].get();
864 for (auto &copy_data : copy_chunk->copy_datas)
865 {
866 copier(copy_data);
867 }
868 // Finally free the memory.
869 copy_chunks[chunk_no].reset();
870 })
871 .name("copy");
872
873 // Ensure the copy task runs after the worker task.
874 worker_task.precede(copier_task);
875
876 // Ensure that only one copy task can run at a time. The code
877 // below makes each copy task wait until the previous one has
878 // finished before it can start
879 if (!last_copier.empty())
880 last_copier.precede(copier_task);
881
882 // Keep a handle to the last copier. Tasks in taskflow are
883 // basically handles to internally stored data, so this does not
884 // perform a copy:
885 last_copier = copier_task;
886 ++chunk_no;
887 }
888 copy_chunks.resize(chunk_no);
889 // Now we run all the tasks in the task graph. They will be run in
890 // parallel and are eligible to run when their dependencies established
891 // above are met.
892
893 // Schedule the work. If we are within a task, we are required to use
894 // corun() without wait() to avoid a potential deadlock as described in
895 // https://taskflow.github.io/taskflow/ExecuteTaskflow.html#ExecuteATaskflowFromAnInternalWorker:
896 if (executor.this_worker_id() != -1)
897 executor.corun(taskflow);
898 else
899 executor.run(taskflow).wait();
900 }
901 } // namespace taskflow_no_coloring
902# endif
903
910 namespace sequential
911 {
915 template <typename Worker,
916 typename Copier,
917 typename Iterator,
918 typename ScratchData,
919 typename CopyData>
920 void
921 run(const Iterator &begin,
923 Worker worker,
924 Copier copier,
925 const ScratchData &sample_scratch_data,
926 const CopyData &sample_copy_data)
927 {
928 // need to copy the sample since it is marked const
929 ScratchData scratch_data = sample_scratch_data;
930 CopyData copy_data = sample_copy_data; // NOLINT
931
932 // Optimization: Check if the functions are not the zero function. To
933 // check zero-ness, create a C++ function out of it:
934 const bool have_worker =
935 (static_cast<const std::function<
936 void(const Iterator &, ScratchData &, CopyData &)> &>(worker)) !=
937 nullptr;
938 const bool have_copier =
939 (static_cast<const std::function<void(const CopyData &)> &>(
940 copier)) != nullptr;
941
942 // Finally loop over all items and perform the necessary work:
943 for (Iterator i = begin; i != end; ++i)
944 {
945 if (have_worker)
946 worker(i, scratch_data, copy_data);
947 if (have_copier)
948 copier(copy_data);
949 }
950 }
951
952
953
957 template <typename Worker,
958 typename Copier,
959 typename Iterator,
960 typename ScratchData,
961 typename CopyData>
962 void
963 run(const std::vector<std::vector<Iterator>> &colored_iterators,
964 Worker worker,
965 Copier copier,
966 const ScratchData &sample_scratch_data,
967 const CopyData &sample_copy_data)
968 {
969 // need to copy the sample since it is marked const
970 ScratchData scratch_data = sample_scratch_data;
971 CopyData copy_data = sample_copy_data; // NOLINT
972
973 // Optimization: Check if the functions are not the zero function. To
974 // check zero-ness, create a C++ function out of it:
975 const bool have_worker =
976 (static_cast<const std::function<
977 void(const Iterator &, ScratchData &, CopyData &)> &>(worker)) !=
978 nullptr;
979 const bool have_copier =
980 (static_cast<const std::function<void(const CopyData &)> &>(
981 copier)) != nullptr;
982
983 // Finally loop over all items and perform the necessary work:
984 for (unsigned int color = 0; color < colored_iterators.size(); ++color)
985 if (colored_iterators[color].size() > 0)
986 for (auto &it : colored_iterators[color])
987 {
988 if (have_worker)
989 worker(it, scratch_data, copy_data);
990 if (have_copier)
991 copier(copy_data);
992 }
993 }
994
995 } // namespace sequential
996
997
998
999# ifdef DEAL_II_WITH_TBB
1007 namespace tbb_colored
1008 {
1014 template <typename Iterator, typename ScratchData, typename CopyData>
1016 {
1017 public:
1022 const std::function<void(const Iterator &, ScratchData &, CopyData &)>
1023 &worker,
1024 const std::function<void(const CopyData &)> &copier,
1025 const ScratchData &sample_scratch_data,
1026 const CopyData &sample_copy_data)
1027 : worker(worker)
1028 , copier(copier)
1031 {}
1032
1033
1038 void
1039 operator()(const tbb::blocked_range<
1040 typename std::vector<Iterator>::const_iterator> &range)
1041 {
1042 // we need to find an unused scratch and corresponding copy
1043 // data object in the list that corresponds to the current
1044 // thread and then mark it as used. If we can't find one,
1045 // create one as discussed in the discussion of the documentation
1046 // of the IteratorRangeToItemStream::scratch_data variable,
1047 // there is no need to synchronize access to this variable
1048 // using a mutex as long as we have no yield-point in between.
1049 // This means that we can't take an iterator into the list
1050 // now and expect it to still be valid after calling the worker,
1051 // but we at least do not have to lock the following section.
1052 ScratchData *scratch_data = nullptr;
1053 CopyData *copy_data = nullptr;
1054 {
1055 ScratchAndCopyDataList &scratch_and_copy_data_list = data.get();
1056
1057 // see if there is an unused object. if so, grab it and mark
1058 // it as used
1059 for (typename ScratchAndCopyDataList::iterator p =
1060 scratch_and_copy_data_list.begin();
1061 p != scratch_and_copy_data_list.end();
1062 ++p)
1063 if (p->currently_in_use == false)
1064 {
1065 scratch_data = p->scratch_data.get();
1066 copy_data = p->copy_data.get();
1067 p->currently_in_use = true;
1068 break;
1069 }
1070
1071 // if no element in the list was found, create one and mark it as
1072 // used
1073 if (scratch_data == nullptr)
1074 {
1075 Assert(copy_data == nullptr, ExcInternalError());
1076
1077 scratch_and_copy_data_list.emplace_back(
1078 std::make_unique<ScratchData>(sample_scratch_data),
1079 std::make_unique<CopyData>(sample_copy_data),
1080 true);
1081 scratch_data =
1082 scratch_and_copy_data_list.back().scratch_data.get();
1083 copy_data = scratch_and_copy_data_list.back().copy_data.get();
1084 }
1085 }
1086
1087 // then call the worker and copier functions on each
1088 // element of the chunk we were given.
1089 for (typename std::vector<Iterator>::const_iterator p = range.begin();
1090 p != range.end();
1091 ++p)
1092 {
1093 try
1094 {
1095 if (worker)
1096 worker(*p, *scratch_data, *copy_data);
1097 if (copier)
1098 copier(*copy_data);
1099 }
1100 catch (const std::exception &exc)
1101 {
1103 }
1104 catch (...)
1105 {
1107 }
1108 }
1109
1110 // finally mark the scratch object as unused again. as above, there
1111 // is no need to lock anything here since the object we work on
1112 // is thread-local
1113 {
1114 ScratchAndCopyDataList &scratch_and_copy_data_list = data.get();
1115
1116 for (typename ScratchAndCopyDataList::iterator p =
1117 scratch_and_copy_data_list.begin();
1118 p != scratch_and_copy_data_list.end();
1119 ++p)
1120 if (p->scratch_data.get() == scratch_data)
1121 {
1122 Assert(p->currently_in_use == true, ExcInternalError());
1123 p->currently_in_use = false;
1124 }
1125 }
1126 }
1127
1128 private:
1129 using ScratchAndCopyDataObjects = typename internal::
1130 ScratchAndCopyDataObjects<Iterator, ScratchData, CopyData>;
1131
1136 using ScratchAndCopyDataList = std::list<ScratchAndCopyDataObjects>;
1137
1139
1144 const std::function<void(const Iterator &, ScratchData &, CopyData &)>
1146
1151 const std::function<void(const CopyData &)> copier;
1152
1156 const ScratchData &sample_scratch_data;
1157 const CopyData &sample_copy_data;
1158 };
1159
1163 template <typename Worker,
1164 typename Copier,
1165 typename Iterator,
1166 typename ScratchData,
1167 typename CopyData>
1168 void
1169 run(const std::vector<std::vector<Iterator>> &colored_iterators,
1170 Worker worker,
1171 Copier copier,
1172 const ScratchData &sample_scratch_data,
1173 const CopyData &sample_copy_data,
1174 const unsigned int chunk_size)
1175 {
1176 // loop over the various colors of what we're given
1177 for (unsigned int color = 0; color < colored_iterators.size(); ++color)
1178 if (colored_iterators[color].size() > 0)
1179 {
1180 using WorkerAndCopier = internal::tbb_colored::
1181 WorkerAndCopier<Iterator, ScratchData, CopyData>;
1182
1183 WorkerAndCopier worker_and_copier(worker,
1184 copier,
1185 sample_scratch_data,
1186 sample_copy_data);
1187
1189 colored_iterators[color].begin(),
1190 colored_iterators[color].end(),
1191 [&worker_and_copier](
1192 const tbb::blocked_range<
1193 typename std::vector<Iterator>::const_iterator> &range) {
1194 worker_and_copier(range);
1195 },
1196 chunk_size);
1197 }
1198 }
1199
1200 } // namespace tbb_colored
1201# endif // DEAL_II_WITH_TBB
1202
1203
1204
1205# ifdef DEAL_II_WITH_TASKFLOW
1211 namespace taskflow_colored
1212 {
1218 template <typename Iterator, typename ScratchData, typename CopyData>
1220 {
1221 public:
1226 const std::function<void(const Iterator &, ScratchData &, CopyData &)>
1227 &worker,
1228 const std::function<void(const CopyData &)> &copier,
1229 const ScratchData &sample_scratch_data,
1230 const CopyData &sample_copy_data)
1231 : worker(worker)
1232 , copier(copier)
1235 {}
1236
1237
1242 void
1244 {
1245 // we need to find an unused scratch and corresponding copy
1246 // data object in the list that corresponds to the current
1247 // thread and then mark it as used. If we can't find one,
1248 // create one as discussed in the discussion of the documentation
1249 // of the IteratorRangeToItemStream::scratch_data variable,
1250 // there is no need to synchronize access to this variable
1251 // using a mutex as long as we have no yield-point in between.
1252 // This means that we can't take an iterator into the list
1253 // now and expect it to still be valid after calling the worker,
1254 // but we at least do not have to lock the following section.
1255 ScratchData *scratch_data = nullptr;
1256 CopyData *copy_data = nullptr;
1257 {
1258 ScratchAndCopyDataList &scratch_and_copy_data_list = data.get();
1259
1260 // see if there is an unused object. if so, grab it and mark
1261 // it as used
1262 for (typename ScratchAndCopyDataList::iterator p =
1263 scratch_and_copy_data_list.begin();
1264 p != scratch_and_copy_data_list.end();
1265 ++p)
1266 if (p->currently_in_use == false)
1267 {
1268 scratch_data = p->scratch_data.get();
1269 copy_data = p->copy_data.get();
1270 p->currently_in_use = true;
1271 break;
1272 }
1273
1274 // if no element in the list was found, create one and mark it as
1275 // used
1276 if (scratch_data == nullptr)
1277 {
1278 Assert(copy_data == nullptr, ExcInternalError());
1279
1280 scratch_and_copy_data_list.emplace_back(
1281 std::make_unique<ScratchData>(sample_scratch_data),
1282 std::make_unique<CopyData>(sample_copy_data),
1283 true);
1284 scratch_data =
1285 scratch_and_copy_data_list.back().scratch_data.get();
1286 copy_data = scratch_and_copy_data_list.back().copy_data.get();
1287 }
1288 }
1289
1290 // then call the worker and copier functions on each
1291 // element of the chunk we were given.
1292
1293 try
1294 {
1295 for (const auto &it : range)
1296 {
1297 if (worker)
1298 worker(it, *scratch_data, *copy_data);
1299 if (copier)
1300 copier(*copy_data);
1301 }
1302 }
1303 catch (const std::exception &exc)
1304 {
1306 }
1307 catch (...)
1308 {
1310 }
1311
1312
1313
1314 // finally mark the scratch object as unused again. as above, there
1315 // is no need to lock anything here since the object we work on
1316 // is thread-local
1317 {
1318 ScratchAndCopyDataList &scratch_and_copy_data_list = data.get();
1319
1320 for (typename ScratchAndCopyDataList::iterator p =
1321 scratch_and_copy_data_list.begin();
1322 p != scratch_and_copy_data_list.end();
1323 ++p)
1324 if (p->scratch_data.get() == scratch_data)
1325 {
1326 Assert(p->currently_in_use == true, ExcInternalError());
1327 p->currently_in_use = false;
1328 }
1329 }
1330 }
1331
1332 private:
1333 using ScratchAndCopyDataObjects = typename internal::
1334 ScratchAndCopyDataObjects<Iterator, ScratchData, CopyData>;
1335
1340 using ScratchAndCopyDataList = std::list<ScratchAndCopyDataObjects>;
1341
1343
1348 const std::function<void(const Iterator &, ScratchData &, CopyData &)>
1350
1355 const std::function<void(const CopyData &)> copier;
1356
1360 const ScratchData &sample_scratch_data;
1361 const CopyData &sample_copy_data;
1362 };
1363
1367 template <typename Worker,
1368 typename Copier,
1369 typename Iterator,
1370 typename ScratchData,
1371 typename CopyData>
1372 void
1373 run(const std::vector<std::vector<Iterator>> &colored_iterators,
1374 const Worker &worker,
1375 const Copier &copier,
1376 const ScratchData &sample_scratch_data,
1377 const CopyData &sample_copy_data,
1378 const unsigned int chunk_size)
1379 {
1380 using WorkerAndCopier = internal::taskflow_colored::
1381 WorkerAndCopier<Iterator, ScratchData, CopyData>;
1382
1383 WorkerAndCopier worker_and_copier(worker,
1384 copier,
1385 sample_scratch_data,
1386 sample_copy_data);
1387
1388 tf::Executor &executor = MultithreadInfo::get_taskflow_executor();
1389 tf::Taskflow taskflow;
1390
1391 tf::Task last_task;
1392 for (auto &color : colored_iterators)
1393 if (color.size() > 0)
1394 {
1395 const tf::IndexRange<int> range(0, color.size(), 1);
1396 auto task = taskflow.for_each_by_index(
1397 range,
1398 [&color,
1399 &worker_and_copier](const tf::IndexRange<int> &subrange) {
1400 const ArrayView<const Iterator> chunk(
1401 &color[subrange.begin()], subrange.size());
1402 worker_and_copier(chunk);
1403 },
1404 tf::GuidedPartitioner(chunk_size));
1405
1406 if (!last_task.empty())
1407 last_task.precede(task);
1408
1409 last_task = task;
1410 }
1411
1412 // Schedule the work. If we are within a task, we are required to use
1413 // corun() without wait() to avoid a potential deadlock as described in
1414 // https://taskflow.github.io/taskflow/ExecuteTaskflow.html#ExecuteATaskflowFromAnInternalWorker:
1415 if (executor.this_worker_id() != -1)
1416 executor.corun(taskflow);
1417 else
1418 executor.run(taskflow).wait();
1419 }
1420
1421 } // namespace taskflow_colored
1422# endif // DEAL_II_WITH_TASKFLOW
1423
1424
1425 } // namespace internal
1426
1427
1428
1476 template <typename Worker,
1477 typename Copier,
1478 typename Iterator,
1479 typename ScratchData,
1480 typename CopyData>
1481 void
1482 run(const std::vector<std::vector<Iterator>> &colored_iterators,
1483 Worker worker,
1484 Copier copier,
1485 const ScratchData &sample_scratch_data,
1486 const CopyData &sample_copy_data,
1487 const unsigned int queue_length = 2 * MultithreadInfo::n_threads(),
1488 const unsigned int chunk_size = 8);
1489
1490
1540 template <typename Worker,
1541 typename Copier,
1542 typename Iterator,
1543 typename ScratchData,
1544 typename CopyData>
1545 void
1546 run(const Iterator &begin,
1548 Worker worker,
1549 Copier copier,
1550 const ScratchData &sample_scratch_data,
1551 const CopyData &sample_copy_data,
1552 const unsigned int queue_length = 2 * MultithreadInfo::n_threads(),
1553 const unsigned int chunk_size = 8)
1554 {
1555 Assert(queue_length > 0,
1556 ExcMessage("The queue length must be at least one, and preferably "
1557 "larger than the number of processors on this system."));
1558 (void)queue_length; // removes -Wunused-parameter warning in optimized mode
1559 Assert(chunk_size > 0, ExcMessage("The chunk_size must be at least one."));
1560 (void)chunk_size; // removes -Wunused-parameter warning in optimized mode
1561
1562 // If no work then skip. (only use operator!= for iterators since we may
1563 // not have an equality comparison operator)
1564 if (!(begin != end))
1565 return;
1566
1568 {
1569# if defined(DEAL_II_WITH_TBB) || defined(DEAL_II_WITH_TASKFLOW)
1570 if (static_cast<const std::function<void(const CopyData &)> &>(copier))
1571 {
1572 // If we have a copier, run the algorithm:
1573# if defined(DEAL_II_WITH_TASKFLOW)
1575 end,
1576 worker,
1577 copier,
1578 sample_scratch_data,
1579 sample_copy_data,
1580 queue_length,
1581 chunk_size);
1582# elif defined(DEAL_II_WITH_TBB)
1584 end,
1585 worker,
1586 copier,
1587 sample_scratch_data,
1588 sample_copy_data,
1589 queue_length,
1590 chunk_size);
1591# endif
1592 }
1593 else
1594 {
1595 // There is no copier function. in this case, we have an
1596 // embarrassingly parallel problem where we can
1597 // essentially apply parallel_for. because parallel_for
1598 // requires subdividing the range for which operator- is
1599 // necessary between iterators, it is often inefficient to
1600 // apply it directly to cell ranges and similar iterator
1601 // types for which operator- is expensive or, in fact,
1602 // nonexistent. rather, in that case, we simply copy the
1603 // iterators into a large array and use operator- on
1604 // iterators to this array of iterators.
1605 //
1606 // instead of duplicating code, this is essentially the
1607 // same situation we have in the colored implementation below, so we
1608 // just defer to that place
1609 std::vector<std::vector<Iterator>> all_iterators(1);
1610 for (Iterator p = begin; p != end; ++p)
1611 all_iterators[0].push_back(p);
1612
1613 run(all_iterators,
1614 worker,
1615 copier,
1616 sample_scratch_data,
1617 sample_copy_data,
1618 queue_length,
1619 chunk_size);
1620 }
1621
1622 // exit this function to not run the sequential version below:
1623 return;
1624# endif
1625 }
1626
1627 // no TBB or Taskflow installed or we are requested to run sequentially:
1629 begin, end, worker, copier, sample_scratch_data, sample_copy_data);
1630 }
1631
1632
1633
1641 template <
1642 typename Worker,
1643 typename Copier,
1644 typename IteratorRangeType,
1645 typename ScratchData,
1646 typename CopyData,
1647 typename = std::enable_if_t<
1648 has_begin_and_end<IteratorRangeType> &&
1649 !std::is_same_v<IteratorRangeType,
1651 void
1652 run(IteratorRangeType iterator_range,
1653 Worker worker,
1654 Copier copier,
1655 const ScratchData &sample_scratch_data,
1656 const CopyData &sample_copy_data,
1657 const unsigned int queue_length = 2 * MultithreadInfo::n_threads(),
1658 const unsigned int chunk_size = 8)
1659 {
1660 // Call the function above
1661 run(iterator_range.begin(),
1662 iterator_range.end(),
1663 worker,
1664 copier,
1665 sample_scratch_data,
1666 sample_copy_data,
1667 queue_length,
1668 chunk_size);
1669 }
1670
1671
1672
1676 template <typename Worker,
1677 typename Copier,
1678 typename Iterator,
1679 typename ScratchData,
1680 typename CopyData>
1681 void
1682 run(const IteratorRange<Iterator> &iterator_range,
1683 Worker worker,
1684 Copier copier,
1685 const ScratchData &sample_scratch_data,
1686 const CopyData &sample_copy_data,
1687 const unsigned int queue_length = 2 * MultithreadInfo::n_threads(),
1688 const unsigned int chunk_size = 8)
1689 {
1690 // Call the function above
1691 run(iterator_range.begin(),
1692 iterator_range.end(),
1693 worker,
1694 copier,
1695 sample_scratch_data,
1696 sample_copy_data,
1697 queue_length,
1698 chunk_size);
1699 }
1700
1701
1702
1703 template <typename Worker,
1704 typename Copier,
1705 typename Iterator,
1706 typename ScratchData,
1707 typename CopyData>
1708 void
1709 run(const std::vector<std::vector<Iterator>> &colored_iterators,
1710 Worker worker,
1711 Copier copier,
1712 const ScratchData &sample_scratch_data,
1713 const CopyData &sample_copy_data,
1714 const unsigned int queue_length,
1715 const unsigned int chunk_size)
1716 {
1717 Assert(queue_length > 0,
1718 ExcMessage("The queue length must be at least one, and preferably "
1719 "larger than the number of processors on this system."));
1720 (void)queue_length; // removes -Wunused-parameter warning in optimized mode
1721 Assert(chunk_size > 0, ExcMessage("The chunk_size must be at least one."));
1722 (void)chunk_size; // removes -Wunused-parameter warning in optimized mode
1723
1724
1726 {
1727# ifdef DEAL_II_WITH_TASKFLOW
1728 internal::taskflow_colored::run(colored_iterators,
1729 worker,
1730 copier,
1731 sample_scratch_data,
1732 sample_copy_data,
1733 chunk_size);
1734
1735 // exit this function to not run the sequential version below:
1736 return;
1737# elif defined(DEAL_II_WITH_TBB)
1738 internal::tbb_colored::run(colored_iterators,
1739 worker,
1740 copier,
1741 sample_scratch_data,
1742 sample_copy_data,
1743 chunk_size);
1744
1745 // exit this function to not run the sequential version below:
1746 return;
1747# endif
1748 }
1749
1750 // run all colors sequentially:
1751 {
1752 internal::sequential::run(colored_iterators,
1753 worker,
1754 copier,
1755 sample_scratch_data,
1756 sample_copy_data);
1757 }
1758 }
1759
1760
1761
1803 template <typename MainClass,
1804 typename Iterator,
1805 typename ScratchData,
1806 typename CopyData>
1807 void
1808 run(const Iterator &begin,
1810 MainClass &main_object,
1811 void (MainClass::*worker)(const Iterator &, ScratchData &, CopyData &),
1812 void (MainClass::*copier)(const CopyData &),
1813 const ScratchData &sample_scratch_data,
1814 const CopyData &sample_copy_data,
1815 const unsigned int queue_length = 2 * MultithreadInfo::n_threads(),
1816 const unsigned int chunk_size = 8)
1817 {
1818 // forward to the other function
1819 run(
1820 begin,
1821 end,
1822 [&main_object, worker](const Iterator &iterator,
1823 ScratchData &scratch_data,
1824 CopyData &copy_data) {
1825 (main_object.*worker)(iterator, scratch_data, copy_data);
1826 },
1827 [&main_object, copier](const CopyData &copy_data) {
1828 (main_object.*copier)(copy_data);
1829 },
1830 sample_scratch_data,
1831 sample_copy_data,
1832 queue_length,
1833 chunk_size);
1834 }
1835
1836
1837 template <typename MainClass,
1838 typename Iterator,
1839 typename ScratchData,
1840 typename CopyData>
1841 void
1844 MainClass &main_object,
1845 void (MainClass::*worker)(const Iterator &, ScratchData &, CopyData &),
1846 void (MainClass::*copier)(const CopyData &),
1847 const ScratchData &sample_scratch_data,
1848 const CopyData &sample_copy_data,
1849 const unsigned int queue_length = 2 * MultithreadInfo::n_threads(),
1850 const unsigned int chunk_size = 8)
1851 {
1852 // forward to the other function
1853 run(
1854 begin,
1855 end,
1856 [&main_object, worker](const Iterator &iterator,
1857 ScratchData &scratch_data,
1858 CopyData &copy_data) {
1859 (main_object.*worker)(iterator, scratch_data, copy_data);
1860 },
1861 [&main_object, copier](const CopyData &copy_data) {
1862 (main_object.*copier)(copy_data);
1863 },
1864 sample_scratch_data,
1865 sample_copy_data,
1866 queue_length,
1867 chunk_size);
1868 }
1869
1870
1871
1879 template <
1880 typename MainClass,
1881 typename IteratorRangeType,
1882 typename ScratchData,
1883 typename CopyData,
1884 typename = std::enable_if_t<
1885 has_begin_and_end<IteratorRangeType> &&
1886 !std::is_same_v<IteratorRangeType,
1888 void
1890 IteratorRangeType iterator_range,
1891 MainClass &main_object,
1892 void (MainClass::*worker)(
1893 const typename std_cxx20::type_identity_t<IteratorRangeType>::iterator &,
1894 ScratchData &,
1895 CopyData &),
1896 void (MainClass::*copier)(const CopyData &),
1897 const ScratchData &sample_scratch_data,
1898 const CopyData &sample_copy_data,
1899 const unsigned int queue_length = 2 * MultithreadInfo::n_threads(),
1900 const unsigned int chunk_size = 8)
1901 {
1902 // Call the function above
1903 run(std::begin(iterator_range),
1904 std::end(iterator_range),
1905 main_object,
1906 worker,
1907 copier,
1908 sample_scratch_data,
1909 sample_copy_data,
1910 queue_length,
1911 chunk_size);
1912 }
1913
1914
1915
1919 template <typename MainClass,
1920 typename Iterator,
1921 typename ScratchData,
1922 typename CopyData>
1923 void
1925 MainClass &main_object,
1926 void (MainClass::*worker)(const Iterator &, ScratchData &, CopyData &),
1927 void (MainClass::*copier)(const CopyData &),
1928 const ScratchData &sample_scratch_data,
1929 const CopyData &sample_copy_data,
1930 const unsigned int queue_length = 2 * MultithreadInfo::n_threads(),
1931 const unsigned int chunk_size = 8)
1932 {
1933 // Call the function above
1934 run(std::begin(iterator_range),
1935 std::end(iterator_range),
1936 main_object,
1937 worker,
1938 copier,
1939 sample_scratch_data,
1940 sample_copy_data,
1941 queue_length,
1942 chunk_size);
1943 }
1944
1945} // namespace WorkStream
1946
1947
1948
1950
1951
1952
1953//---------------------------- work_stream.h ---------------------------
1954// end of #ifndef dealii_work_stream_h
1955#endif
1956//---------------------------- work_stream.h ---------------------------
*  iterator end()
*  *  iterator begin()
*  *  iterator()=default
***mech_lbc_system increment_interpolation_handlers push_back(scale_z_handler)
IteratorOverIterators end() const
IteratorOverIterators begin()
static unsigned int n_threads()
static tf::Executor & get_taskflow_executor()
A class that provides a separate storage location on each thread that accesses the object.
typename internal::ScratchAndCopyDataObjects< Iterator, ScratchData, CopyData > ScratchAndCopyDataObjects
Threads::ThreadLocalStorage< ScratchAndCopyDataList > data
std::list< ScratchAndCopyDataObjects > ScratchAndCopyDataList
void operator()(const ArrayView< const Iterator > &range)
const std::function< void(const Iterator &, ScratchData &, CopyData &)> worker
WorkerAndCopier(const std::function< void(const Iterator &, ScratchData &, CopyData &)> &worker, const std::function< void(const CopyData &)> &copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data)
const std::function< void(const CopyData &)> copier
std::list< ScratchAndCopyDataObjects > ScratchAndCopyDataList
typename internal::ScratchAndCopyDataObjects< Iterator, ScratchData, CopyData > ScratchAndCopyDataObjects
WorkerAndCopier(const std::function< void(const Iterator &, ScratchData &, CopyData &)> &worker, const std::function< void(const CopyData &)> &copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data)
Threads::ThreadLocalStorage< ScratchAndCopyDataList > data
void operator()(const tbb::blocked_range< typename std::vector< Iterator >::const_iterator > &range)
const std::function< void(const Iterator &, ScratchData &, CopyData &)> worker
const std::function< void(const CopyData &)> copier
IteratorRangeToItemStream(const Iterator &begin, const Iterator &end, const unsigned int buffer_size, const unsigned int chunk_size, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data)
Threads::ThreadLocalStorage< typename ItemType::ScratchDataList > thread_local_scratch
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
#define DEAL_II_TBB_WITH_ONEAPI
Definition config.h:142
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
#define Assert(cond, exc)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcMessage(std::string arg1)
std::size_t size
Definition mpi.cc:733
void handle_std_exception(const std::exception &exc)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data)
void run(const std::vector< std::vector< Iterator > > &colored_iterators, const Worker &worker, const Copier &copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int chunk_size)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int=2 *MultithreadInfo::n_threads(), const unsigned int chunk_size=8)
void run(const std::vector< std::vector< Iterator > > &colored_iterators, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int chunk_size)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length, const unsigned int chunk_size)
void run(const std::vector< std::vector< Iterator > > &colored_iterators, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length=2 *MultithreadInfo::n_threads(), const unsigned int chunk_size=8)
void parallel_for(Iterator x_begin, Iterator x_end, const Functor &functor, const unsigned int grainsize)
Definition parallel.h:127
typename type_identity< T >::type type_identity_t
Definition type_traits.h:93
STL namespace.
std::unique_ptr< ScratchData > scratch_data
ScratchAndCopyDataObjects(const ScratchAndCopyDataObjects &)
ScratchAndCopyDataObjects(std::unique_ptr< ScratchData > &&p, std::unique_ptr< CopyData > &&q, const bool in_use)
ScratchDataObject(std::unique_ptr< ScratchData > &&p, const bool in_use)
ScratchDataObject(ScratchDataObject &&o) noexcept=default
ScratchDataObject(ScratchData *p, const bool in_use)
ScratchDataObject(const ScratchDataObject &)
std::unique_ptr< ScratchData > scratch_data
std::list< ScratchDataObject< ScratchData > > ScratchDataList
Threads::ThreadLocalStorage< ScratchDataList > * scratch_data