Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ah-parallel.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31
32#ifndef AH_PARALLEL_H
33#define AH_PARALLEL_H
34
115#include <vector>
116#include <atomic>
117#include <optional>
118#include <algorithm>
119#include <numeric>
120#include <type_traits>
121#include <iterator>
122#include <functional>
123#include <thread_pool.H>
124#include <ah-ranges.H>
125
126namespace Aleph
127{
128 // =============================================================================
129 // Implementation Details
130 // =============================================================================
131
132 namespace parallel_detail
133 {
135 {
136 size_t offset;
137 size_t end;
138 };
139
141 {
142 return options.pool == nullptr ? default_pool() : *options.pool;
143 }
144
145 inline size_t chunk_count(const size_t n, const size_t chunk_size) noexcept
146 {
147 return n == 0 ? 0 : (n + chunk_size - 1) / chunk_size;
148 }
149
150 inline chunk_bounds bounds_for_chunk(const size_t idx, const size_t n,
151 const size_t chunk_size) noexcept
152 {
153 const size_t offset = idx * chunk_size;
154 return {offset, std::min(offset + chunk_size, n)};
155 }
156
158 inline size_t chunk_size(const size_t n, const size_t num_threads, const size_t min_chunk = 64)
159 {
160 if (n == 0) return 1;
161 // Use more chunks than threads for better load balancing
162 const size_t chunks = num_threads * 4;
163 const size_t size = (n + chunks - 1) / chunks;
164 return std::max(size, min_chunk);
165 }
166
168 template <typename Container>
169 constexpr bool has_random_access()
170 {
171 using It = decltype(std::begin(std::declval<Container &>()));
172 return std::is_base_of_v<std::random_access_iterator_tag,
173 typename std::iterator_traits<It>::iterator_category>;
174 }
175
178 template <typename Container>
180 {
181 if constexpr (has_random_access<Container>())
182 return &c; // Return pointer to original
183 else
184 return std::make_unique<std::vector<typename Container::value_type>>(std::begin(c), std::end(c));
185 }
186
188 template <typename T>
189 decltype(auto) deref(T && ptr)
190 {
191 if constexpr (std::is_pointer_v<std::decay_t<T>>)
192 return *ptr;
193 else
194 return *ptr; // unique_ptr also supports *
195 }
196
197 inline size_t effective_parallel_chunk_size(const size_t n,
198 const ThreadPool & pool,
199 const ParallelOptions & options,
200 const size_t min_chunk = 64)
201 {
202 size_t effective = options.chunk_size == 0 ? chunk_size(n, pool.num_threads(), min_chunk) : options.chunk_size;
203
204 if (options.max_tasks > 0)
205 {
206 const size_t min_for_cap = (n + options.max_tasks - 1) / options.max_tasks;
207 effective = std::max(effective, std::max(min_chunk, min_for_cap));
208 }
209
210 return std::max<size_t>(1, effective);
211 }
212
213 inline bool use_sequential_parallel_path(const size_t n,
214 const ThreadPool & pool,
215 const ParallelOptions & options) noexcept
216 {
217 if (n == 0)
218 return true;
219 if (options.cancel_token.stop_requested())
220 return true;
221 if (options.max_tasks == 1)
222 return true;
223 if (options.min_size > 0 and n < options.min_size)
224 return true;
225 return pool.num_threads() <= 1;
226 }
227
229 {
231 }
232 } // namespace parallel_detail
233
234 // =============================================================================
235 // Parallel Map
236 // =============================================================================
237
268 template <typename ResultT = void, typename Container, typename Op>
269 [[nodiscard]] auto pmaps(ThreadPool & pool, const Container & c, Op op,
270 size_t chunk_size = 0)
271 {
273 options.pool = &pool;
274 options.chunk_size = chunk_size;
275 return pmaps<ResultT>(c, op, options);
276 }
277
278 template <typename ResultT = void, typename Container, typename Op>
279 [[nodiscard]] auto pmaps(const Container & c, Op op,
280 const ParallelOptions & options = {})
281 {
283 using InputT = std::decay_t<decltype(*std::begin(c))>;
284 using ActualResultT = std::conditional_t<
285 std::is_void_v<ResultT>,
286 std::invoke_result_t<Op, const InputT &>,
287 ResultT>;
288
289 const size_t n = std::distance(std::begin(c), std::end(c));
290 if (n == 0)
291 return std::vector<ActualResultT>{};
292
294 {
295 std::vector<ActualResultT> result;
296 result.reserve(n);
297 for (auto it = std::begin(c); it != std::end(c); ++it)
298 {
300 result.push_back(op(*it));
301 }
303 return result;
304 }
305
306 const size_t chunk_size =
308
309 // Ensure random access for parallel processing
311 const auto & data = parallel_detail::deref(data_holder);
312
313 std::vector<std::optional<ActualResultT>> slots(n);
314 std::vector<std::future<void>> futures;
315
316 size_t offset = 0;
317
318 while (offset < n)
319 {
320 size_t chunk_end = std::min(offset + chunk_size, n);
321
322 futures.push_back(pool.enqueue([&slots, &data, op, offset, chunk_end,
323 token = options.cancel_token]()
324 {
325 auto in_it = std::begin(data);
326 std::advance(in_it, offset);
327 for (size_t i = offset; i < chunk_end; ++i, ++in_it)
328 {
329 parallel_detail::throw_if_parallel_canceled(token);
330 slots[i] = op(*in_it);
331 }
332 }));
333
335 }
336
337 {
338 std::exception_ptr ep;
339 for (auto & f: futures)
340 try { f.get(); } catch (...) { if (not ep) ep = std::current_exception(); }
341 if (ep)
342 std::rethrow_exception(ep);
343 }
344
345 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
346
347 std::vector<ActualResultT> result;
348 result.reserve(n);
349 for (auto & slot : slots)
350 result.push_back(std::move(*slot));
351
352 return result;
353 }
354
355 // =============================================================================
356 // Parallel Filter
357 // =============================================================================
358
386 template <typename Container, typename Pred>
387 [[nodiscard]] auto pfilter(ThreadPool & pool, const Container & c, Pred pred,
388 size_t chunk_size = 0)
389 {
391 options.pool = &pool;
392 options.chunk_size = chunk_size;
393 return pfilter(c, pred, options);
394 }
395
396 template <typename Container, typename Pred>
397 [[nodiscard]] auto pfilter(const Container & c, Pred pred,
398 const ParallelOptions & options = {})
399 {
400 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
401 using T = std::decay_t<decltype(*std::begin(c))>;
402
403 const size_t n = std::distance(std::begin(c), std::end(c));
404 if (n == 0)
405 return std::vector<T>{};
406
407 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
408 {
409 std::vector<T> result;
410 for (auto it = std::begin(c); it != std::end(c); ++it)
411 {
412 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
413 if (pred(*it))
414 result.push_back(*it);
415 }
416 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
417 return result;
418 }
419
420 const size_t chunk_size =
421 parallel_detail::effective_parallel_chunk_size(n, pool, options);
422
423 auto data_holder = parallel_detail::ensure_random_access(c);
424 const auto & data = parallel_detail::deref(data_holder);
425
426 std::vector<std::future<std::vector<T>>> futures;
427 const size_t num_chunks = parallel_detail::chunk_count(n, chunk_size);
428 futures.reserve(num_chunks);
429
430 for (size_t chunk_idx = 0; chunk_idx < num_chunks; ++chunk_idx)
431 {
432 const auto bounds = parallel_detail::bounds_for_chunk(chunk_idx, n, chunk_size);
433
434 futures.push_back(pool.enqueue([&data, pred,
435 offset = bounds.offset,
436 chunk_end = bounds.end,
437 token = options.cancel_token]()
438 {
439 std::vector<T> chunk_result;
440 auto it = std::begin(data);
441 std::advance(it, offset);
442 for (size_t i = offset; i < chunk_end; ++i, ++it)
443 {
444 parallel_detail::throw_if_parallel_canceled(token);
445 if (pred(*it))
446 chunk_result.push_back(*it);
447 }
448 return chunk_result;
449 }));
450 }
451
452 std::vector<T> result;
453 for (auto & f: futures)
454 {
455 auto chunk_result = f.get();
456 result.insert(result.end(),
457 std::make_move_iterator(chunk_result.begin()),
458 std::make_move_iterator(chunk_result.end()));
459 }
460
461 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
462
463 return result;
464 }
465
466 // =============================================================================
467 // Parallel Fold/Reduce
468 // =============================================================================
469
507 template <typename T, typename Container, typename BinaryOp>
508 [[nodiscard]] T pfoldl(ThreadPool & pool, const Container & c, T init, BinaryOp op,
509 size_t chunk_size = 0)
510 {
512 options.pool = &pool;
513 options.chunk_size = chunk_size;
514 return pfoldl(c, init, op, options);
515 }
516
517 template <typename T, typename Container, typename BinaryOp>
518 [[nodiscard]] T pfoldl(const Container & c, T init, BinaryOp op,
519 const ParallelOptions & options = {})
520 {
521 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
522 const size_t n = std::distance(std::begin(c), std::end(c));
523 if (n == 0)
524 return init;
525
526 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
527 {
528 T result = init;
529 for (auto it = std::begin(c); it != std::end(c); ++it)
530 {
531 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
532 result = op(result, *it);
533 }
534 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
535 return result;
536 }
537
538 const size_t chunk_size =
539 parallel_detail::effective_parallel_chunk_size(n, pool, options);
540
541 auto data_holder = parallel_detail::ensure_random_access(c);
542 const auto & data = parallel_detail::deref(data_holder);
543
544 std::vector<std::future<T>> futures;
545
546 size_t offset = 0;
547 while (offset < n)
548 {
549 size_t chunk_end = std::min(offset + chunk_size, n);
550
551 futures.push_back(pool.enqueue([&data, op, offset, chunk_end,
552 token = options.cancel_token]()
553 {
554 auto it = std::begin(data);
555 std::advance(it, offset);
556 parallel_detail::throw_if_parallel_canceled(token);
557 T local = *it++;
558 for (size_t i = offset + 1; i < chunk_end; ++i, ++it)
559 {
560 parallel_detail::throw_if_parallel_canceled(token);
561 local = op(local, *it);
562 }
563 return local;
564 }));
565
566 offset = chunk_end;
567 }
568
569 // Combine partial results
570 T result = init;
571 for (auto & f: futures)
572 result = op(result, f.get());
573
574 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
575
576 return result;
577 }
578
579 // =============================================================================
580 // Parallel For Each
581 // =============================================================================
582
613 template <typename Container, typename Op>
614 void pfor_each(ThreadPool & pool, Container & c, Op op, size_t chunk_size = 0)
615 {
617 options.pool = &pool;
618 options.chunk_size = chunk_size;
619 pfor_each(c, op, options);
620 }
621
622 template <typename Container, typename Op>
623 void pfor_each(Container & c, Op op, const ParallelOptions & options = {})
624 {
625 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
626 const size_t n = std::distance(std::begin(c), std::end(c));
627 if (n == 0)
628 return;
629
630 const size_t chunk_size =
631 parallel_detail::effective_parallel_chunk_size(n, pool, options);
632
633 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
634 {
635 for (auto it = std::begin(c); it != std::end(c); ++it)
636 {
637 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
638 op(*it);
639 }
640 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
641 return;
642 }
643
644 std::vector<std::future<void>> futures;
645 size_t offset = 0;
646
647 while (offset < n)
648 {
649 size_t chunk_end = std::min(offset + chunk_size, n);
650
651 futures.push_back(pool.enqueue([&c, op, offset, chunk_end,
652 token = options.cancel_token]()
653 {
654 auto it = std::begin(c);
655 std::advance(it, offset);
656 for (size_t i = offset; i < chunk_end; ++i, ++it)
657 {
658 parallel_detail::throw_if_parallel_canceled(token);
659 op(*it);
660 }
661 }));
662
663 offset = chunk_end;
664 }
665
666 for (auto & f: futures)
667 f.get();
668
669 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
670 }
671
684 template <typename Container, typename Op>
685 void pfor_each(ThreadPool & pool, const Container & c, Op op, size_t chunk_size = 0)
686 {
688 options.pool = &pool;
689 options.chunk_size = chunk_size;
690 pfor_each(c, op, options);
691 }
692
693 template <typename Container, typename Op>
694 void pfor_each(const Container & c, Op op, const ParallelOptions & options = {})
695 {
696 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
697 const size_t n = std::distance(std::begin(c), std::end(c));
698 if (n == 0)
699 return;
700
701 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
702 {
703 for (auto it = std::begin(c); it != std::end(c); ++it)
704 {
705 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
706 op(*it);
707 }
708 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
709 return;
710 }
711
712 const size_t chunk_size =
713 parallel_detail::effective_parallel_chunk_size(n, pool, options);
714
715 auto data_holder = parallel_detail::ensure_random_access(c);
716 const auto & data = parallel_detail::deref(data_holder);
717
718 std::vector<std::future<void>> futures;
719
720 size_t offset = 0;
721 while (offset < n)
722 {
723 size_t chunk_end = std::min(offset + chunk_size, n);
724
725 futures.push_back(pool.enqueue([&data, op, offset, chunk_end,
726 token = options.cancel_token]()
727 {
728 auto it = std::begin(data);
729 std::advance(it, offset);
730 for (size_t i = offset; i < chunk_end; ++i, ++it)
731 {
732 parallel_detail::throw_if_parallel_canceled(token);
733 op(*it);
734 }
735 }));
736
737 offset = chunk_end;
738 }
739
740 for (auto & f: futures)
741 f.get();
742
743 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
744 }
745
746 // =============================================================================
747 // Parallel Predicates (all, exists, none)
748 // =============================================================================
749
772 template <typename Container, typename Pred>
773 [[nodiscard]] bool pall(ThreadPool & pool, const Container & c, Pred pred,
774 size_t chunk_size = 0)
775 {
777 options.pool = &pool;
778 options.chunk_size = chunk_size;
779 return pall(c, pred, options);
780 }
781
782 template <typename Container, typename Pred>
783 [[nodiscard]] bool pall(const Container & c, Pred pred,
784 const ParallelOptions & options = {})
785 {
786 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
787 const size_t n = std::distance(std::begin(c), std::end(c));
788 if (n == 0)
789 return true;
790
791 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
792 {
793 for (auto it = std::begin(c); it != std::end(c); ++it)
794 {
795 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
796 if (not pred(*it))
797 return false;
798 }
799 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
800 return true;
801 }
802
803 const size_t chunk_size =
804 parallel_detail::effective_parallel_chunk_size(n, pool, options);
805
806 auto data_holder = parallel_detail::ensure_random_access(c);
807 const auto & data = parallel_detail::deref(data_holder);
808
809 std::atomic<bool> found_false{false};
810 std::vector<std::future<void>> futures;
811
812 size_t offset = 0;
813 while (offset < n)
814 {
815 size_t chunk_end = std::min(offset + chunk_size, n);
816
817 futures.push_back(pool.enqueue([&data, pred, &found_false, offset, chunk_end,
818 token = options.cancel_token]()
819 {
820 parallel_detail::throw_if_parallel_canceled(token);
821 if (found_false.load(std::memory_order_relaxed))
822 return; // Short-circuit
823
824 auto it = std::begin(data);
825 std::advance(it, offset);
826 for (size_t i = offset; i < chunk_end; ++i, ++it)
827 {
828 if (not pred(*it))
829 {
830 found_false.store(true, std::memory_order_relaxed);
831 return;
832 }
833 parallel_detail::throw_if_parallel_canceled(token);
834 if (found_false.load(std::memory_order_relaxed))
835 return;
836 }
837 }));
838
839 offset = chunk_end;
840 }
841
842 for (auto & f: futures)
843 f.get();
844
845 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
846 return not found_false.load();
847 }
848
870 template <typename Container, typename Pred>
871 [[nodiscard]] bool pexists(ThreadPool & pool, const Container & c, Pred pred,
872 size_t chunk_size = 0)
873 {
875 options.pool = &pool;
876 options.chunk_size = chunk_size;
877 return pexists(c, pred, options);
878 }
879
880 template <typename Container, typename Pred>
881 [[nodiscard]] bool pexists(const Container & c, Pred pred,
882 const ParallelOptions & options = {})
883 {
884 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
885 const size_t n = std::distance(std::begin(c), std::end(c));
886 if (n == 0)
887 return false;
888
889 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
890 {
891 for (auto it = std::begin(c); it != std::end(c); ++it)
892 {
893 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
894 if (pred(*it))
895 return true;
896 }
897 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
898 return false;
899 }
900
901 const size_t chunk_size =
902 parallel_detail::effective_parallel_chunk_size(n, pool, options);
903
904 auto data_holder = parallel_detail::ensure_random_access(c);
905 const auto & data = parallel_detail::deref(data_holder);
906
907 std::atomic<bool> found{false};
908 std::vector<std::future<void>> futures;
909
910 size_t offset = 0;
911 while (offset < n)
912 {
913 size_t chunk_end = std::min(offset + chunk_size, n);
914
915 futures.push_back(pool.enqueue([&data, pred, &found, offset, chunk_end,
916 token = options.cancel_token]()
917 {
918 parallel_detail::throw_if_parallel_canceled(token);
919 if (found.load(std::memory_order_relaxed))
920 return; // Short-circuit
921
922 auto it = std::begin(data);
923 std::advance(it, offset);
924 for (size_t i = offset; i < chunk_end; ++i, ++it)
925 {
926 if (pred(*it))
927 {
928 found.store(true, std::memory_order_relaxed);
929 return;
930 }
931 parallel_detail::throw_if_parallel_canceled(token);
932 if (found.load(std::memory_order_relaxed))
933 return;
934 }
935 }));
936
937 offset = chunk_end;
938 }
939
940 for (auto & f: futures)
941 f.get();
942
943 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
944 return found.load();
945 }
946
960 template <typename Container, typename Pred>
961 [[nodiscard]] bool pnone(ThreadPool & pool, const Container & c, Pred pred,
962 size_t chunk_size = 0)
963 {
964 return not pexists(pool, c, pred, chunk_size);
965 }
966
967 template <typename Container, typename Pred>
968 [[nodiscard]] bool pnone(const Container & c, Pred pred,
969 const ParallelOptions & options = {})
970 {
971 return not pexists(c, pred, options);
972 }
973
974 // =============================================================================
975 // Parallel Count
976 // =============================================================================
977
999 template <typename Container, typename Pred>
1000 [[nodiscard]] size_t pcount_if(ThreadPool & pool, const Container & c, Pred pred,
1001 size_t chunk_size = 0)
1002 {
1004 options.pool = &pool;
1005 options.chunk_size = chunk_size;
1006 return pcount_if(c, pred, options);
1007 }
1008
1009 template <typename Container, typename Pred>
1010 [[nodiscard]] size_t pcount_if(const Container & c, Pred pred,
1011 const ParallelOptions & options = {})
1012 {
1013 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
1014 const size_t n = std::distance(std::begin(c), std::end(c));
1015 if (n == 0)
1016 return 0;
1017
1018 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
1019 {
1020 size_t total = 0;
1021 for (auto it = std::begin(c); it != std::end(c); ++it)
1022 {
1023 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1024 if (pred(*it))
1025 ++total;
1026 }
1027 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1028 return total;
1029 }
1030
1031 const size_t chunk_size =
1032 parallel_detail::effective_parallel_chunk_size(n, pool, options);
1033
1034 auto data_holder = parallel_detail::ensure_random_access(c);
1035 const auto & data = parallel_detail::deref(data_holder);
1036
1037 std::vector<std::future<size_t>> futures;
1038
1039 size_t offset = 0;
1040 while (offset < n)
1041 {
1042 size_t chunk_end = std::min(offset + chunk_size, n);
1043
1044 futures.push_back(pool.enqueue([&data, pred, offset, chunk_end,
1045 token = options.cancel_token]()
1046 {
1047 size_t count = 0;
1048 auto it = std::begin(data);
1049 std::advance(it, offset);
1050 for (size_t i = offset; i < chunk_end; ++i, ++it)
1051 {
1052 parallel_detail::throw_if_parallel_canceled(token);
1053 if (pred(*it))
1054 ++count;
1055 }
1056 return count;
1057 }));
1058
1059 offset = chunk_end;
1060 }
1061
1062 size_t total = 0;
1063 for (auto & f: futures)
1064 total += f.get();
1065
1066 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1067 return total;
1068 }
1069
1070 // =============================================================================
1071 // Parallel Find
1072 // =============================================================================
1073
1099 template <typename Container, typename Pred>
1100 [[nodiscard]] std::optional<size_t> pfind(ThreadPool & pool, const Container & c,
1101 Pred pred, size_t chunk_size = 0)
1102 {
1104 options.pool = &pool;
1105 options.chunk_size = chunk_size;
1106 return pfind(c, pred, options);
1107 }
1108
1109 template <typename Container, typename Pred>
1110 [[nodiscard]] std::optional<size_t> pfind(const Container & c,
1111 Pred pred,
1112 const ParallelOptions & options = {})
1113 {
1114 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
1115 const size_t n = std::distance(std::begin(c), std::end(c));
1116 if (n == 0)
1117 return std::nullopt;
1118
1119 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
1120 {
1121 size_t idx = 0;
1122 for (auto it = std::begin(c); it != std::end(c); ++it, ++idx)
1123 {
1124 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1125 if (pred(*it))
1126 return idx;
1127 }
1128 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1129 return std::nullopt;
1130 }
1131
1132 const size_t chunk_size =
1133 parallel_detail::effective_parallel_chunk_size(n, pool, options);
1134
1135 auto data_holder = parallel_detail::ensure_random_access(c);
1136 const auto & data = parallel_detail::deref(data_holder);
1137
1138 // Track minimum found index
1139 std::atomic<size_t> min_index{n}; // n means not found
1140 std::vector<std::future<void>> futures;
1141
1142 size_t offset = 0;
1143 while (offset < n)
1144 {
1145 size_t chunk_end = std::min(offset + chunk_size, n);
1146
1147 futures.push_back(pool.enqueue([&data, pred, &min_index, offset, chunk_end,
1148 token = options.cancel_token]()
1149 {
1150 parallel_detail::throw_if_parallel_canceled(token);
1151 // Skip if we already found something earlier
1152 if (min_index.load(std::memory_order_relaxed) <= offset)
1153 return;
1154
1155 auto it = std::begin(data);
1156 std::advance(it, offset);
1157 for (size_t i = offset; i < chunk_end; ++i, ++it)
1158 {
1159 // Stop if earlier match found
1160 if (min_index.load(std::memory_order_relaxed) <= i)
1161 return;
1162
1163 parallel_detail::throw_if_parallel_canceled(token);
1164 if (pred(*it))
1165 {
1166 // Atomically update minimum
1167 size_t expected = min_index.load(std::memory_order_relaxed);
1168 while (i < expected and
1169 not min_index.compare_exchange_weak(expected, i,
1170 std::memory_order_relaxed));
1171 return;
1172 }
1173 }
1174 }));
1175
1176 offset = chunk_end;
1177 }
1178
1179 for (auto & f: futures)
1180 f.get();
1181
1182 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1183 if (size_t result = min_index.load(); result < n)
1184 return result;
1185 return std::nullopt;
1186 }
1187
1211 template <typename Container, typename Pred>
1212 [[nodiscard]] auto pfind_value(ThreadPool & pool, const Container & c,
1213 Pred pred, size_t chunk_size = 0)
1214 {
1216 options.pool = &pool;
1217 options.chunk_size = chunk_size;
1218 return pfind_value(c, pred, options);
1219 }
1220
1221 template <typename Container, typename Pred>
1222 [[nodiscard]] auto pfind_value(const Container & c,
1223 Pred pred,
1224 const ParallelOptions & options = {})
1225 {
1226 using T = std::decay_t<decltype(*std::begin(c))>;
1227
1228 auto idx = pfind(c, pred, options);
1229 if (not idx)
1230 return std::optional<T>{std::nullopt};
1231
1232 auto it = std::begin(c);
1233 std::advance(it, *idx);
1234 return std::optional<T>{*it};
1235 }
1236
1237 // =============================================================================
1238 // Parallel Numeric Operations
1239 // =============================================================================
1240
1260 template <typename Container,
1261 typename T = std::decay_t<decltype(*std::begin(std::declval<Container>()))>>
1262 [[nodiscard]] T psum(ThreadPool & pool, const Container & c, T init = T{},
1263 size_t chunk_size = 0)
1264 {
1265 return pfoldl(pool, c, init, std::plus<T>{}, chunk_size);
1266 }
1267
1268 template <typename Container,
1269 typename T = std::decay_t<decltype(*std::begin(std::declval<Container>()))>>
1270 [[nodiscard]] T psum(const Container & c, T init, const ParallelOptions & options)
1271 {
1272 return pfoldl(c, init, std::plus<T>{}, options);
1273 }
1274
1286 template <typename Container,
1287 typename T = std::decay_t<decltype(*std::begin(std::declval<Container>()))>>
1288 [[nodiscard]] T pproduct(ThreadPool & pool, const Container & c, T init = T{1},
1289 size_t chunk_size = 0)
1290 {
1291 return pfoldl(pool, c, init, std::multiplies<T>{}, chunk_size);
1292 }
1293
1294 template <typename Container,
1295 typename T = std::decay_t<decltype(*std::begin(std::declval<Container>()))>>
1296 [[nodiscard]] T pproduct(const Container & c, T init, const ParallelOptions & options)
1297 {
1298 return pfoldl(c, init, std::multiplies<T>{}, options);
1299 }
1300
1311 template <typename Container>
1312 [[nodiscard]] auto pmin(ThreadPool & pool, const Container & c, size_t chunk_size = 0)
1313 {
1315 options.pool = &pool;
1316 options.chunk_size = chunk_size;
1317 return pmin(c, options);
1318 }
1319
1320 template <typename Container>
1321 [[nodiscard]] auto pmin(const Container & c, const ParallelOptions & options = {})
1322 {
1323 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
1324 using T = std::decay_t<decltype(*std::begin(c))>;
1325
1326 const size_t n = std::distance(std::begin(c), std::end(c));
1327 if (n == 0)
1328 return std::optional<T>{std::nullopt};
1329
1330 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
1331 {
1332 auto it = std::begin(c);
1333 T result = *it++;
1334 for (; it != std::end(c); ++it)
1335 {
1336 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1337 if (*it < result)
1338 result = *it;
1339 }
1340 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1341 return std::optional<T>{result};
1342 }
1343
1344 const size_t chunk_size =
1345 parallel_detail::effective_parallel_chunk_size(n, pool, options);
1346
1347 auto data_holder = parallel_detail::ensure_random_access(c);
1348 const auto & data = parallel_detail::deref(data_holder);
1349
1350 std::vector<std::future<T>> futures;
1351
1352 size_t offset = 0;
1353 while (offset < n)
1354 {
1355 size_t chunk_end = std::min(offset + chunk_size, n);
1356
1357 futures.push_back(pool.enqueue([&data, offset, chunk_end,
1358 token = options.cancel_token]()
1359 {
1360 auto it = std::begin(data);
1361 std::advance(it, offset);
1362 parallel_detail::throw_if_parallel_canceled(token);
1363 T local_min = *it++;
1364 for (size_t i = offset + 1; i < chunk_end; ++i, ++it)
1365 {
1366 parallel_detail::throw_if_parallel_canceled(token);
1367 if (*it < local_min)
1368 local_min = *it;
1369 }
1370 return local_min;
1371 }));
1372
1373 offset = chunk_end;
1374 }
1375
1376 T result = futures[0].get();
1377 for (size_t i = 1; i < futures.size(); ++i)
1378 {
1379 T val = futures[i].get();
1380 if (val < result)
1381 result = val;
1382 }
1383
1384 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1385 return std::optional<T>{result};
1386 }
1387
1398 template <typename Container>
1399 [[nodiscard]] auto pmax(ThreadPool & pool, const Container & c, size_t chunk_size = 0)
1400 {
1402 options.pool = &pool;
1403 options.chunk_size = chunk_size;
1404 return pmax(c, options);
1405 }
1406
1407 template <typename Container>
1408 [[nodiscard]] auto pmax(const Container & c, const ParallelOptions & options = {})
1409 {
1410 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
1411 using T = std::decay_t<decltype(*std::begin(c))>;
1412
1413 const size_t n = std::distance(std::begin(c), std::end(c));
1414 if (n == 0)
1415 return std::optional<T>{std::nullopt};
1416
1417 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
1418 {
1419 auto it = std::begin(c);
1420 T result = *it++;
1421 for (; it != std::end(c); ++it)
1422 {
1423 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1424 if (*it > result)
1425 result = *it;
1426 }
1427 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1428 return std::optional<T>{result};
1429 }
1430
1431 const size_t chunk_size =
1432 parallel_detail::effective_parallel_chunk_size(n, pool, options);
1433
1434 auto data_holder = parallel_detail::ensure_random_access(c);
1435 const auto & data = parallel_detail::deref(data_holder);
1436
1437 std::vector<std::future<T>> futures;
1438
1439 size_t offset = 0;
1440 while (offset < n)
1441 {
1442 size_t chunk_end = std::min(offset + chunk_size, n);
1443
1444 futures.push_back(pool.enqueue([&data, offset, chunk_end,
1445 token = options.cancel_token]()
1446 {
1447 auto it = std::begin(data);
1448 std::advance(it, offset);
1449 parallel_detail::throw_if_parallel_canceled(token);
1450 T local_max = *it++;
1451 for (size_t i = offset + 1; i < chunk_end; ++i, ++it)
1452 {
1453 parallel_detail::throw_if_parallel_canceled(token);
1454 if (*it > local_max)
1455 local_max = *it;
1456 }
1457 return local_max;
1458 }));
1459
1460 offset = chunk_end;
1461 }
1462
1463 T result = futures[0].get();
1464 for (size_t i = 1; i < futures.size(); ++i)
1465 {
1466 T val = futures[i].get();
1467 if (val > result)
1468 result = val;
1469 }
1470
1471 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1472 return std::optional<T>{result};
1473 }
1474
1485 template <typename Container>
1486 [[nodiscard]] auto pminmax(ThreadPool & pool, const Container & c, size_t chunk_size = 0)
1487 {
1488 using T = std::decay_t<decltype(*std::begin(c))>;
1489
1490 const size_t n = std::distance(std::begin(c), std::end(c));
1491 if (n == 0)
1492 return std::optional<std::pair<T, T>>{std::nullopt};
1493
1494 if (chunk_size == 0)
1495 chunk_size = parallel_detail::chunk_size(n, pool.num_threads());
1496
1497 auto data_holder = parallel_detail::ensure_random_access(c);
1498 const auto & data = parallel_detail::deref(data_holder);
1499
1500 std::vector<std::future<std::pair<T, T>>> futures;
1501
1502 size_t offset = 0;
1503 while (offset < n)
1504 {
1505 size_t chunk_end = std::min(offset + chunk_size, n);
1506
1507 futures.push_back(pool.enqueue([&data, offset, chunk_end]()
1508 {
1509 auto it = std::begin(data);
1510 std::advance(it, offset);
1511 T local_min = *it;
1512 T local_max = *it++;
1513 for (size_t i = offset + 1; i < chunk_end; ++i, ++it)
1514 {
1515 if (*it < local_min) local_min = *it;
1516 if (*it > local_max) local_max = *it;
1517 }
1518 return std::make_pair(local_min, local_max);
1519 }));
1520
1521 offset = chunk_end;
1522 }
1523
1524 auto result = futures[0].get();
1525 for (size_t i = 1; i < futures.size(); ++i)
1526 {
1527 auto [mi, ma] = futures[i].get();
1528 if (mi < result.first) result.first = mi;
1529 if (ma > result.second) result.second = ma;
1530 }
1531
1532 return std::optional<std::pair<T, T>>{result};
1533 }
1534
1535 // =============================================================================
1536 // Parallel Sort
1537 // =============================================================================
1538
1566 template <typename Container, typename Compare = std::less<>>
1567 void psort(ThreadPool & pool, Container & c, Compare cmp = Compare{},
1568 const size_t min_parallel_size = 1024)
1569 {
1570 ParallelOptions options;
1571 options.pool = &pool;
1572 options.min_size = min_parallel_size;
1573 psort(c, std::move(cmp), options);
1574 }
1575
1576 template <typename Container, typename Compare = std::less<>>
1577 void psort(Container & c, Compare cmp = Compare{},
1578 const ParallelOptions & options = {})
1579 {
1580 const size_t n = std::distance(std::begin(c), std::end(c));
1581 if (n <= 1)
1582 return;
1583
1584 auto & pool = parallel_detail::selected_parallel_pool(options);
1585 const size_t min_parallel_size = options.min_size == 0 ? 1024 : options.min_size;
1586
1587 // For small sizes, use regular sort
1588 if (parallel_detail::use_sequential_parallel_path(n, pool, options)
1589 or n <= min_parallel_size)
1590 {
1591 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1592 sort_range(c, cmp);
1593 return;
1594 }
1595
1596 // Split into chunks, sort each in parallel, then merge
1597 size_t num_chunks = std::min(pool.num_threads() * 2, n / min_parallel_size);
1598 if (options.max_tasks > 0)
1599 num_chunks = std::min(num_chunks, options.max_tasks);
1600 num_chunks = std::max<size_t>(1, num_chunks);
1601 const size_t chunk_size = (n + num_chunks - 1) / num_chunks;
1602
1603 // Sort chunks in parallel
1604 std::vector<std::future<void>> futures;
1605 for (size_t i = 0; i < n; i += chunk_size)
1606 {
1607 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1608 size_t end = std::min(i + chunk_size, n);
1609 auto begin_it = std::begin(c);
1610 std::advance(begin_it, i);
1611 auto end_it = std::begin(c);
1612 std::advance(end_it, end);
1613
1614 futures.push_back(pool.enqueue([begin_it, end_it, cmp, token = options.cancel_token]()
1615 {
1616 parallel_detail::throw_if_parallel_canceled(token);
1617 std::sort(begin_it, end_it, cmp);
1618 }));
1619 }
1620
1621 for (auto & f: futures)
1622 f.get();
1623
1624 // Merge sorted chunks
1625 using T = std::decay_t<decltype(*std::begin(c))>;
1626 std::vector<std::optional<T>> buffer(n);
1627
1628 ParallelOptions merge_options = options;
1629 merge_options.pool = &pool;
1630
1631 for (size_t width = chunk_size; width < n; width *= 2)
1632 {
1633 for (size_t i = 0; i < n; i += 2 * width)
1634 {
1635 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1636 size_t mid = std::min(i + width, n);
1637 size_t end = std::min(i + 2 * width, n);
1638
1639 if (mid < end)
1640 {
1641 auto begin_it = std::begin(c);
1642 std::advance(begin_it, i);
1643 auto mid_it = std::begin(c);
1644 std::advance(mid_it, mid);
1645 auto end_it = std::begin(c);
1646 std::advance(end_it, end);
1647
1648 Aleph::pmerge(begin_it, mid_it, mid_it, end_it,
1649 buffer.begin() + i, cmp, merge_options);
1650 }
1651 else
1652 {
1653 // Copy remaining elements
1654 auto begin_it = std::begin(c);
1655 std::advance(begin_it, i);
1656 auto end_it = std::begin(c);
1657 std::advance(end_it, mid);
1658 std::copy(begin_it, end_it, buffer.begin() + i);
1659 }
1660 }
1661
1662 // Swap buffer back to container
1663 auto it = std::begin(c);
1664 for (size_t i = 0; i < n; ++i, ++it)
1665 *it = std::move(*buffer[i]);
1666 }
1667 }
1668
1669 // =============================================================================
1670 // Parallel Zip Operations
1671 // =============================================================================
1672
1700 template <typename Container1, typename Container2, typename Op>
1701 void pzip_for_each(ThreadPool & pool, const Container1 & c1, const Container2 & c2,
1702 Op op, size_t chunk_size = 0)
1703 {
1705 options.pool = &pool;
1706 options.chunk_size = chunk_size;
1707 pzip_for_each(c1, c2, op, options);
1708 }
1709
1710 template <typename Container1, typename Container2, typename Op>
1711 void pzip_for_each(const Container1 & c1, const Container2 & c2, Op op,
1712 const ParallelOptions & options = {})
1713 {
1714 const size_t n1 = std::distance(std::begin(c1), std::end(c1));
1715 const size_t n2 = std::distance(std::begin(c2), std::end(c2));
1716 const size_t n = std::min(n1, n2);
1717
1718 if (n == 0)
1719 return;
1720
1721 auto & pool = parallel_detail::selected_parallel_pool(options);
1722 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
1723 {
1724 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1725 auto it1 = std::begin(c1);
1726 auto it2 = std::begin(c2);
1727 for (size_t i = 0; i < n; ++i, ++it1, ++it2)
1728 {
1729 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1730 op(*it1, *it2);
1731 }
1732 return;
1733 }
1734
1735 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
1736 pool.num_threads());
1737
1738 auto h1 = parallel_detail::ensure_random_access(c1);
1739 auto h2 = parallel_detail::ensure_random_access(c2);
1740 const auto & d1 = parallel_detail::deref(h1);
1741 const auto & d2 = parallel_detail::deref(h2);
1742
1743 std::vector<std::future<void>> futures;
1744
1745 size_t offset = 0;
1746 while (offset < n)
1747 {
1748 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1749 size_t chunk_end = std::min(offset + chunk_size, n);
1750
1751 futures.push_back(pool.enqueue([&d1, &d2, op, offset, chunk_end,
1752 token = options.cancel_token]()
1753 {
1754 auto it1 = std::begin(d1);
1755 auto it2 = std::begin(d2);
1756 std::advance(it1, offset);
1757 std::advance(it2, offset);
1758 for (size_t i = offset; i < chunk_end; ++i, ++it1, ++it2)
1759 {
1760 parallel_detail::throw_if_parallel_canceled(token);
1761 op(*it1, *it2);
1762 }
1763 }));
1764
1765 offset = chunk_end;
1766 }
1767
1768 for (auto & f: futures)
1769 f.get();
1770 }
1771
1796 template <typename Container1, typename Container2, typename Op>
1797 [[nodiscard]] auto pzip_maps(ThreadPool & pool, const Container1 & c1,
1798 const Container2 & c2, Op op, size_t chunk_size = 0)
1799 {
1801 options.pool = &pool;
1802 options.chunk_size = chunk_size;
1803 return pzip_maps(c1, c2, op, options);
1804 }
1805
1806 template <typename Container1, typename Container2, typename Op>
1807 [[nodiscard]] auto pzip_maps(const Container1 & c1, const Container2 & c2, Op op,
1808 const ParallelOptions & options = {})
1809 {
1810 using T1 = std::decay_t<decltype(*std::begin(c1))>;
1811 using T2 = std::decay_t<decltype(*std::begin(c2))>;
1812 using ResultT = std::invoke_result_t<Op, const T1 &, const T2 &>;
1813
1814 const size_t n1 = std::distance(std::begin(c1), std::end(c1));
1815 const size_t n2 = std::distance(std::begin(c2), std::end(c2));
1816 const size_t n = std::min(n1, n2);
1817
1818 if (n == 0)
1819 return std::vector<ResultT>{};
1820
1821 auto & pool = parallel_detail::selected_parallel_pool(options);
1822 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
1823 {
1824 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1825 std::vector<ResultT> result;
1826 result.reserve(n);
1827 auto it1 = std::begin(c1);
1828 auto it2 = std::begin(c2);
1829 for (size_t i = 0; i < n; ++i, ++it1, ++it2)
1830 {
1831 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1832 result.push_back(op(*it1, *it2));
1833 }
1834 return result;
1835 }
1836
1837 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
1838 pool.num_threads());
1839
1840 auto h1 = parallel_detail::ensure_random_access(c1);
1841 auto h2 = parallel_detail::ensure_random_access(c2);
1842 const auto & d1 = parallel_detail::deref(h1);
1843 const auto & d2 = parallel_detail::deref(h2);
1844
1845 std::vector<std::optional<ResultT>> slots(n);
1846 std::vector<std::future<void>> futures;
1847
1848 size_t offset = 0;
1849 while (offset < n)
1850 {
1851 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1852 size_t chunk_end = std::min(offset + chunk_size, n);
1853
1854 futures.push_back(pool.enqueue([&slots, &d1, &d2, op, offset, chunk_end,
1855 token = options.cancel_token]()
1856 {
1857 auto it1 = std::begin(d1);
1858 auto it2 = std::begin(d2);
1859 std::advance(it1, offset);
1860 std::advance(it2, offset);
1861 for (size_t i = offset; i < chunk_end; ++i, ++it1, ++it2)
1862 {
1863 parallel_detail::throw_if_parallel_canceled(token);
1864 slots[i] = op(*it1, *it2);
1865 }
1866 }));
1867
1868 offset = chunk_end;
1869 }
1870
1871 for (auto & f: futures)
1872 f.get();
1873
1874 std::vector<ResultT> result;
1875 result.reserve(n);
1876 for (auto & slot : slots)
1877 result.push_back(std::move(*slot));
1878
1879 return result;
1880 }
1881
1910 template <typename Container1, typename Container2, typename T, typename Op>
1911 [[nodiscard]] T pzip_foldl(ThreadPool & pool, const Container1 & c1,
1912 const Container2 & c2, T init, Op op,
1913 size_t chunk_size = 0)
1914 {
1916 options.pool = &pool;
1917 options.chunk_size = chunk_size;
1918 return pzip_foldl(c1, c2, init, op, options);
1919 }
1920
1921 template <typename Container1, typename Container2, typename T, typename Op>
1922 [[nodiscard]] T pzip_foldl(const Container1 & c1, const Container2 & c2, T init, Op op,
1923 const ParallelOptions & options = {})
1924 {
1925 const size_t n1 = std::distance(std::begin(c1), std::end(c1));
1926 const size_t n2 = std::distance(std::begin(c2), std::end(c2));
1927 const size_t n = std::min(n1, n2);
1928
1929 if (n == 0)
1930 return init;
1931
1932 auto & pool = parallel_detail::selected_parallel_pool(options);
1933 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
1934 {
1935 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1936 auto it1 = std::begin(c1);
1937 auto it2 = std::begin(c2);
1938 T result = init;
1939 for (size_t i = 0; i < n; ++i, ++it1, ++it2)
1940 {
1941 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1942 result = op(result, *it1, *it2);
1943 }
1944 return result;
1945 }
1946
1947 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
1948 pool.num_threads());
1949
1950 auto h1 = parallel_detail::ensure_random_access(c1);
1951 auto h2 = parallel_detail::ensure_random_access(c2);
1952 const auto & d1 = parallel_detail::deref(h1);
1953 const auto & d2 = parallel_detail::deref(h2);
1954
1955 std::vector<std::future<T>> futures;
1956
1957 size_t offset = 0;
1958 while (offset < n)
1959 {
1960 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
1961 size_t chunk_end = std::min(offset + chunk_size, n);
1962
1963 futures.push_back(pool.enqueue([&d1, &d2, &init, op, offset, chunk_end,
1964 token = options.cancel_token]()
1965 {
1966 auto it1 = std::begin(d1);
1967 auto it2 = std::begin(d2);
1968 std::advance(it1, offset);
1969 std::advance(it2, offset);
1970
1971 parallel_detail::throw_if_parallel_canceled(token);
1972 T local = op(init, *it1++, *it2++);
1973 for (size_t i = offset + 1; i < chunk_end; ++i, ++it1, ++it2)
1974 {
1975 parallel_detail::throw_if_parallel_canceled(token);
1976 local = op(local, *it1, *it2);
1977 }
1978 return local;
1979 }));
1980
1981 offset = chunk_end;
1982 }
1983
1984 // Binary reduce the partial results
1985 // We need a binary op for this - derive it from the ternary op
1986 T result = futures[0].get();
1987 for (size_t i = 1; i < futures.size(); ++i)
1988 {
1989 T val = futures[i].get();
1990 // Combine using addition - user should use pfoldl + pzip_maps for complex cases
1991 result = result + val - init; // Compensate for init being added in each chunk
1992 }
1993
1994 return result;
1995 }
1996
1997 // =============================================================================
1998 // Parallel Partition
1999 // =============================================================================
2000
2022 template <typename Container, typename Pred>
2023 [[nodiscard]] auto ppartition(ThreadPool & pool, const Container & c, Pred pred,
2024 size_t chunk_size = 0)
2025 {
2027 options.pool = &pool;
2028 options.chunk_size = chunk_size;
2029 return ppartition(c, pred, options);
2030 }
2031
2032 template <typename Container, typename Pred>
2033 [[nodiscard]] auto ppartition(const Container & c, Pred pred,
2034 const ParallelOptions & options = {})
2035 {
2036 using T = std::decay_t<decltype(*std::begin(c))>;
2037 ThreadPool & pool = parallel_detail::selected_parallel_pool(options);
2038
2039 const size_t n = std::distance(std::begin(c), std::end(c));
2040 if (n == 0)
2041 return std::make_pair(std::vector<T>{}, std::vector<T>{});
2042
2043 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
2044 {
2045 std::vector<T> yes_result, no_result;
2046 for (auto it = std::begin(c); it != std::end(c); ++it)
2047 {
2048 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2049 if (pred(*it))
2050 yes_result.push_back(*it);
2051 else
2052 no_result.push_back(*it);
2053 }
2054 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2055 return std::make_pair(std::move(yes_result), std::move(no_result));
2056 }
2057
2058 const size_t chunk_size =
2059 parallel_detail::effective_parallel_chunk_size(n, pool, options);
2060
2061 auto data_holder = parallel_detail::ensure_random_access(c);
2062 const auto & data = parallel_detail::deref(data_holder);
2063
2064 const size_t num_chunks = parallel_detail::chunk_count(n, chunk_size);
2065 std::vector<size_t> yes_counts(num_chunks, 0);
2066 std::vector<size_t> no_counts(num_chunks, 0);
2067 std::vector<std::future<void>> count_futures;
2068 count_futures.reserve(num_chunks);
2069
2070 for (size_t chunk_idx = 0; chunk_idx < num_chunks; ++chunk_idx)
2071 {
2072 const auto bounds = parallel_detail::bounds_for_chunk(chunk_idx, n, chunk_size);
2073
2074 count_futures.push_back(pool.enqueue([&data, &yes_counts, &no_counts, pred,
2075 chunk_idx,
2076 offset = bounds.offset,
2077 chunk_end = bounds.end,
2078 token = options.cancel_token]()
2079 {
2080 size_t yes = 0;
2081 size_t no = 0;
2082 auto it = std::begin(data);
2083 std::advance(it, offset);
2084 for (size_t i = offset; i < chunk_end; ++i, ++it)
2085 {
2086 parallel_detail::throw_if_parallel_canceled(token);
2087 if (pred(*it))
2088 ++yes;
2089 else
2090 ++no;
2091 }
2092 yes_counts[chunk_idx] = yes;
2093 no_counts[chunk_idx] = no;
2094 }));
2095 }
2096
2097 for (auto & f: count_futures)
2098 f.get();
2099
2100 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2101
2102 std::vector<size_t> yes_offsets(num_chunks, 0);
2103 std::vector<size_t> no_offsets(num_chunks, 0);
2104 if (num_chunks > 0)
2105 {
2106 Aleph::pexclusive_scan(yes_counts.begin(), yes_counts.end(), yes_offsets.begin(),
2107 size_t{0}, std::plus<size_t>{}, options);
2108 Aleph::pexclusive_scan(no_counts.begin(), no_counts.end(), no_offsets.begin(),
2109 size_t{0}, std::plus<size_t>{}, options);
2110 }
2111
2112 const size_t yes_total = num_chunks == 0 ? 0 : yes_offsets.back() + yes_counts.back();
2113 const size_t no_total = num_chunks == 0 ? 0 : no_offsets.back() + no_counts.back();
2114
2115 std::vector<std::optional<T>> yes_slots(yes_total);
2116 std::vector<std::optional<T>> no_slots(no_total);
2117 std::vector<std::future<void>> fill_futures;
2118 fill_futures.reserve(num_chunks);
2119
2120 for (size_t chunk_idx = 0; chunk_idx < num_chunks; ++chunk_idx)
2121 {
2122 const auto bounds = parallel_detail::bounds_for_chunk(chunk_idx, n, chunk_size);
2123 const size_t yes_offset = yes_offsets[chunk_idx];
2124 const size_t no_offset = no_offsets[chunk_idx];
2125
2126 fill_futures.push_back(pool.enqueue([&data, &yes_slots, &no_slots, pred,
2127 offset = bounds.offset,
2128 chunk_end = bounds.end,
2129 yes_offset, no_offset,
2130 token = options.cancel_token]()
2131 {
2132 auto it = std::begin(data);
2133 std::advance(it, offset);
2134 size_t yes_pos = yes_offset;
2135 size_t no_pos = no_offset;
2136 for (size_t i = offset; i < chunk_end; ++i, ++it)
2137 {
2138 parallel_detail::throw_if_parallel_canceled(token);
2139 if (pred(*it))
2140 yes_slots[yes_pos++].emplace(*it);
2141 else
2142 no_slots[no_pos++].emplace(*it);
2143 }
2144 }));
2145 }
2146
2147 for (auto & f: fill_futures)
2148 f.get();
2149
2150 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2151
2152 std::vector<T> yes_result, no_result;
2153 yes_result.reserve(yes_total);
2154 no_result.reserve(no_total);
2155
2156 for (auto & slot: yes_slots)
2157 yes_result.push_back(std::move(*slot));
2158 for (auto & slot: no_slots)
2159 no_result.push_back(std::move(*slot));
2160
2161 return std::make_pair(std::move(yes_result), std::move(no_result));
2162 }
2163
2164 // =============================================================================
2165 // Parallel Scan and Merge Wrappers
2166 // =============================================================================
2167
2191 template <typename Container, typename BinaryOp>
2192 [[nodiscard]] auto pscan(ThreadPool & pool, const Container & c, BinaryOp op,
2193 size_t chunk_size = 0)
2194 {
2196 options.pool = &pool;
2197 options.chunk_size = chunk_size;
2198 return pscan(c, op, options);
2199 }
2200
2221 template <typename Container, typename BinaryOp>
2222 [[nodiscard]] auto pscan(const Container & c, BinaryOp op,
2223 const ParallelOptions & options = {})
2224 {
2225 using T = std::decay_t<decltype(*std::begin(c))>;
2226
2227 auto holder = parallel_detail::ensure_random_access(c);
2228 const auto & data = parallel_detail::deref(holder);
2229 const size_t n = static_cast<size_t>(std::distance(std::begin(data), std::end(data)));
2230 std::vector<std::optional<T>> slots(n);
2231
2232 Aleph::pscan(std::begin(data), std::end(data), slots.begin(), op, options);
2233
2234 std::vector<T> result;
2235 result.reserve(n);
2236 for (auto & slot: slots)
2237 result.push_back(std::move(*slot));
2238 return result;
2239 }
2240
2267 template <typename Container, typename T, typename BinaryOp>
2268 [[nodiscard]] auto pexclusive_scan(ThreadPool & pool, const Container & c, T init,
2269 BinaryOp op, size_t chunk_size = 0)
2270 {
2272 options.pool = &pool;
2273 options.chunk_size = chunk_size;
2274 return pexclusive_scan(c, init, op, options);
2275 }
2276
2297 template <typename Container, typename T, typename BinaryOp>
2298 [[nodiscard]] auto pexclusive_scan(const Container & c, T init, BinaryOp op,
2299 const ParallelOptions & options = {})
2300 {
2301 auto holder = parallel_detail::ensure_random_access(c);
2302 const auto & data = parallel_detail::deref(holder);
2303 const size_t n = static_cast<size_t>(std::distance(std::begin(data), std::end(data)));
2304 std::vector<std::optional<T>> slots(n);
2305
2306 Aleph::pexclusive_scan(std::begin(data), std::end(data), slots.begin(),
2307 init, op, options);
2308
2309 std::vector<T> result;
2310 result.reserve(n);
2311 for (auto & slot: slots)
2312 result.push_back(std::move(*slot));
2313 return result;
2314 }
2315
2341 template <typename Container1, typename Container2, typename Compare = std::less<>>
2342 [[nodiscard]] auto pmerge(ThreadPool & pool, const Container1 & c1, const Container2 & c2,
2343 Compare comp = Compare{}, size_t chunk_size = 0)
2344 {
2345 ParallelOptions options;
2346 options.pool = &pool;
2347 options.chunk_size = chunk_size;
2348 return pmerge(c1, c2, std::move(comp), options);
2349 }
2350
2371 template <typename Container1, typename Container2, typename Compare = std::less<>>
2372 [[nodiscard]] auto pmerge(const Container1 & c1, const Container2 & c2,
2373 Compare comp = Compare{},
2374 const ParallelOptions & options = {})
2375 {
2376 using T1 = std::decay_t<decltype(*std::begin(c1))>;
2377 using T2 = std::decay_t<decltype(*std::begin(c2))>;
2378 using ResultT = std::common_type_t<T1, T2>;
2379
2380 auto h1 = parallel_detail::ensure_random_access(c1);
2381 auto h2 = parallel_detail::ensure_random_access(c2);
2382 const auto & d1 = parallel_detail::deref(h1);
2383 const auto & d2 = parallel_detail::deref(h2);
2384
2385 const size_t total = static_cast<size_t>(std::distance(std::begin(d1), std::end(d1))
2386 + std::distance(std::begin(d2), std::end(d2)));
2387 std::vector<std::optional<ResultT>> slots(total);
2388
2389 Aleph::pmerge(std::begin(d1), std::end(d1),
2390 std::begin(d2), std::end(d2),
2391 slots.begin(), comp, options);
2392
2393 std::vector<ResultT> result;
2394 result.reserve(total);
2395 for (auto & slot: slots)
2396 result.push_back(std::move(*slot));
2397 return result;
2398 }
2399
2400 // =============================================================================
2401 // Variadic Parallel Zip Operations (N containers)
2402 // =============================================================================
2403
2404 namespace parallel_zip_detail
2405 {
2414 template <typename Container>
2416 {
2417 using value_type = std::decay_t<decltype(*std::begin(std::declval<Container &>()))>;
2418 using holder_type = std::conditional_t<
2419 parallel_detail::has_random_access<Container>(),
2420 const Container *,
2421 std::unique_ptr<std::vector<value_type>>>;
2422
2425
2426 explicit ContainerHolder(const Container & c)
2427 {
2428 if constexpr (parallel_detail::has_random_access<Container>())
2429 {
2430 data = &c;
2431 // For random access, std::distance is O(1)
2432 cached_size = static_cast<size_t>(std::distance(std::begin(c), std::end(c)));
2433 }
2434 else
2435 {
2436 // Copy to vector (O(n) - unavoidable), then get size from vector (O(1))
2437 data = std::make_unique<std::vector<value_type>>(std::begin(c), std::end(c));
2438 cached_size = data->size();
2439 }
2440 }
2441
2442 decltype(auto) get() const
2443 {
2444 if constexpr (parallel_detail::has_random_access<Container>())
2445 return *data;
2446 else
2447 return *data;
2448 }
2449
2451 [[nodiscard]] size_t size() const noexcept { return cached_size; }
2452
2453 auto begin() const { return std::begin(get()); }
2454 auto end() const { return std::end(get()); }
2455 };
2456
2458 template <typename... Holders, size_t... Is>
2459 size_t min_holder_size_impl(const std::tuple<Holders...> & holders,
2460 std::index_sequence<Is...>)
2461 {
2462 return std::min({std::get<Is>(holders).size()...});
2463 }
2464
2465 template <typename... Holders>
2466 size_t min_holder_size(const std::tuple<Holders...> & holders)
2467 {
2468 return min_holder_size_impl(holders, std::make_index_sequence<sizeof...(Holders)>{});
2469 }
2470
2472 template <typename... Holders, size_t... Is>
2473 auto make_iterators_at(size_t offset, const std::tuple<Holders...> & holders,
2474 std::index_sequence<Is...>)
2475 {
2476 return std::make_tuple([&]()
2477 {
2478 auto it = std::get<Is>(holders).begin();
2479 std::advance(it, offset);
2480 return it;
2481 }()...);
2482 }
2483
2485 template <typename... Iters, size_t... Is>
2486 void advance_all_iters(std::tuple<Iters...> & iters, std::index_sequence<Is...>)
2487 {
2488 (++std::get<Is>(iters), ...);
2489 }
2490
2492 template <typename... Iters, size_t... Is>
2493 auto deref_all_iters(const std::tuple<Iters...> & iters, std::index_sequence<Is...>)
2494 {
2495 return std::make_tuple(*std::get<Is>(iters)...);
2496 }
2497 } // namespace parallel_zip_detail
2498
2531 template <typename Op, typename... Containers>
2532 void pzip_for_each_n(ThreadPool & pool, Op op, const Containers &... cs)
2533 {
2535 options.pool = &pool;
2536 pzip_for_each_n(op, options, cs...);
2537 }
2538
2539 template <typename Op, typename... Containers>
2540 void pzip_for_each_n(Op op, const ParallelOptions & options, const Containers &... cs)
2541 {
2542 static_assert(sizeof...(Containers) >= 2,
2543 "pzip_for_each requires at least 2 containers");
2544
2545 // Convert all containers to random access FIRST
2546 // This is O(n) for non-RA containers, but unavoidable
2547 auto holders = std::make_tuple(parallel_zip_detail::ContainerHolder<Containers>(cs)...);
2548
2549 // Now get min size - O(1) because all holders have cached sizes
2550 const size_t n = parallel_zip_detail::min_holder_size(holders);
2551 if (n == 0)
2552 return;
2553
2554 auto & pool = parallel_detail::selected_parallel_pool(options);
2555 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
2556 {
2557 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2558 constexpr size_t N = sizeof...(Containers);
2559 auto iters = parallel_zip_detail::make_iterators_at(
2560 0, holders, std::make_index_sequence<N>{});
2561 for (size_t i = 0; i < n; ++i)
2562 {
2563 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2564 std::apply(op, parallel_zip_detail::deref_all_iters(
2565 iters, std::make_index_sequence<N>{}));
2566 parallel_zip_detail::advance_all_iters(iters, std::make_index_sequence<N>{});
2567 }
2568 return;
2569 }
2570
2571 const size_t chunk_size = parallel_detail::effective_parallel_chunk_size(
2572 n, pool, options, pool.num_threads());
2573
2574 std::vector<std::future<void>> futures;
2575
2576 size_t offset = 0;
2577 while (offset < n)
2578 {
2579 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2580 size_t chunk_end = std::min(offset + chunk_size, n);
2581
2582 futures.push_back(pool.enqueue([&holders, op, offset, chunk_end,
2583 token = options.cancel_token]()
2584 {
2585 constexpr size_t N = sizeof...(Containers);
2586 auto iters = parallel_zip_detail::make_iterators_at(
2587 offset, holders, std::make_index_sequence<N>{});
2588
2589 for (size_t i = offset; i < chunk_end; ++i)
2590 {
2591 parallel_detail::throw_if_parallel_canceled(token);
2592 std::apply(op, parallel_zip_detail::deref_all_iters(
2593 iters, std::make_index_sequence<N>{}));
2594 parallel_zip_detail::advance_all_iters(
2595 iters, std::make_index_sequence<N>{});
2596 }
2597 }));
2598
2599 offset = chunk_end;
2600 }
2601
2602 for (auto & f: futures)
2603 f.get();
2604 }
2605
2637 template <typename Op, typename... Containers>
2638 [[nodiscard]] auto pzip_maps_n(ThreadPool & pool, Op op, const Containers &... cs)
2639 {
2641 options.pool = &pool;
2642 return pzip_maps_n(op, options, cs...);
2643 }
2644
2645 template <typename Op, typename... Containers>
2646 [[nodiscard]] auto pzip_maps_n(Op op, const ParallelOptions & options,
2647 const Containers &... cs)
2648 {
2649 static_assert(sizeof...(Containers) >= 2,
2650 "pzip_maps requires at least 2 containers");
2651
2652 // Deduce result type from operation
2653 using ResultT = std::invoke_result_t<Op,
2654 std::decay_t<decltype(*std::begin(cs))>...>;
2655
2656 // Convert all containers to random access FIRST
2657 auto holders = std::make_tuple(parallel_zip_detail::ContainerHolder<Containers>(cs)...);
2658
2659 // Now get min size - O(1) because all holders have cached sizes
2660 const size_t n = parallel_zip_detail::min_holder_size(holders);
2661 if (n == 0)
2662 return std::vector<ResultT>{};
2663
2664 auto & pool = parallel_detail::selected_parallel_pool(options);
2665 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
2666 {
2667 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2668 std::vector<std::optional<ResultT>> slots(n);
2669 constexpr size_t N = sizeof...(Containers);
2670 auto iters = parallel_zip_detail::make_iterators_at(
2671 0, holders, std::make_index_sequence<N>{});
2672 for (size_t i = 0; i < n; ++i)
2673 {
2674 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2675 slots[i] = std::apply(op, parallel_zip_detail::deref_all_iters(
2676 iters, std::make_index_sequence<N>{}));
2677 parallel_zip_detail::advance_all_iters(iters, std::make_index_sequence<N>{});
2678 }
2679
2680 std::vector<ResultT> result;
2681 result.reserve(n);
2682 for (auto & slot: slots)
2683 result.push_back(std::move(*slot));
2684 return result;
2685 }
2686
2687 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
2688 pool.num_threads());
2689
2690 std::vector<std::optional<ResultT>> slots(n);
2691 std::vector<std::future<void>> futures;
2692
2693 size_t offset = 0;
2694 while (offset < n)
2695 {
2696 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2697 size_t chunk_end = std::min(offset + chunk_size, n);
2698
2699 futures.push_back(pool.enqueue([&slots, &holders, op, offset, chunk_end,
2700 token = options.cancel_token]()
2701 {
2702 constexpr size_t N = sizeof...(Containers);
2703 auto iters = parallel_zip_detail::make_iterators_at(
2704 offset, holders, std::make_index_sequence<N>{});
2705
2706 for (size_t i = offset; i < chunk_end; ++i)
2707 {
2708 parallel_detail::throw_if_parallel_canceled(token);
2709 slots[i] = std::apply(op, parallel_zip_detail::deref_all_iters(
2710 iters, std::make_index_sequence<N>{}));
2711 parallel_zip_detail::advance_all_iters(iters, std::make_index_sequence<N>{});
2712 }
2713 }));
2714 offset = chunk_end;
2715 }
2716
2717 for (auto & f: futures)
2718 f.get();
2719
2720 std::vector<ResultT> result;
2721 result.reserve(n);
2722 for (auto & slot: slots)
2723 result.push_back(std::move(*slot));
2724 return result;
2725
2726 }
2727
2766 template <typename T, typename Op, typename Combiner, typename... Containers>
2767 [[nodiscard]] T pzip_foldl_n(ThreadPool & pool, T init, Op op, Combiner combiner,
2768 const Containers &... cs)
2769 {
2771 options.pool = &pool;
2772 return pzip_foldl_n(init, op, combiner, options, cs...);
2773 }
2774
2775 template <typename T, typename Op, typename Combiner, typename... Containers>
2776 [[nodiscard]] T pzip_foldl_n(T init, Op op, Combiner combiner,
2777 const ParallelOptions & options, const Containers &... cs)
2778 {
2779 static_assert(sizeof...(Containers) >= 2,
2780 "pzip_foldl requires at least 2 containers");
2781
2782 // Convert all containers to random access FIRST
2783 auto holders = std::make_tuple(parallel_zip_detail::ContainerHolder<Containers>(cs)...);
2784
2785 // Now get min size - O(1) because all holders have cached sizes
2786 const size_t n = parallel_zip_detail::min_holder_size(holders);
2787 if (n == 0)
2788 return init;
2789
2790 auto & pool = parallel_detail::selected_parallel_pool(options);
2791 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
2792 {
2793 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2794 constexpr size_t N = sizeof...(Containers);
2795 auto iters = parallel_zip_detail::make_iterators_at(
2796 0, holders, std::make_index_sequence<N>{});
2797 T result = init;
2798 for (size_t i = 0; i < n; ++i)
2799 {
2800 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2801 auto tuple = parallel_zip_detail::deref_all_iters(iters, std::make_index_sequence<N>{});
2802 result = std::apply([&op, &result](auto &&... args)
2803 {
2804 return op(result, std::forward<decltype(args)>(args)...);
2805 }, tuple);
2806 parallel_zip_detail::advance_all_iters(iters, std::make_index_sequence<N>{});
2807 }
2808 return result;
2809 }
2810
2811 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
2812 pool.num_threads());
2813
2814 std::vector<std::future<T>> futures;
2815
2816 size_t offset = 0;
2817 while (offset < n)
2818 {
2819 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2820 size_t chunk_end = std::min(offset + chunk_size, n);
2821
2822 futures.push_back(pool.enqueue([&holders, init, op, offset, chunk_end,
2823 token = options.cancel_token]()
2824 {
2825 constexpr size_t N = sizeof...(Containers);
2826 auto iters = parallel_zip_detail::make_iterators_at(
2827 offset, holders, std::make_index_sequence<N>{});
2828
2829 // First element
2830 parallel_detail::throw_if_parallel_canceled(token);
2831 auto first_tuple = parallel_zip_detail::deref_all_iters(
2832 iters, std::make_index_sequence<N>{});
2833 T local = std::apply([&op, &init](auto &&... args)
2834 {
2835 return op(init, std::forward<decltype(args)>(args)
2836 ...);
2837 }, first_tuple);
2838 parallel_zip_detail::advance_all_iters(
2839 iters, std::make_index_sequence<N>{});
2840
2841 // Remaining elements
2842 for (size_t i = offset + 1; i < chunk_end; ++i)
2843 {
2844 parallel_detail::throw_if_parallel_canceled(token);
2845 auto tuple = parallel_zip_detail::deref_all_iters(
2846 iters, std::make_index_sequence<N>{});
2847 local = std::apply([&op, &local](auto &&... args)
2848 {
2849 return op(local,
2850 std::forward<decltype(args)>(args)
2851 ...);
2852 }, tuple);
2853 parallel_zip_detail::advance_all_iters(
2854 iters, std::make_index_sequence<N>{});
2855 }
2856
2857 return local;
2858 }));
2859
2860 offset = chunk_end;
2861 }
2862
2863 // Combine partial results using the combiner
2864 T result = futures[0].get();
2865 for (size_t i = 1; i < futures.size(); ++i)
2866 result = combiner(result, futures[i].get());
2867
2868 return result;
2869 }
2870
2899 template <typename Pred, typename... Containers>
2900 [[nodiscard]] bool pzip_all_n(ThreadPool & pool, Pred pred, const Containers &... cs)
2901 {
2903 options.pool = &pool;
2904 return pzip_all_n(pred, options, cs...);
2905 }
2906
2907 template <typename Pred, typename... Containers>
2908 [[nodiscard]] bool pzip_all_n(Pred pred, const ParallelOptions & options,
2909 const Containers &... cs)
2910 {
2911 static_assert(sizeof...(Containers) >= 2,
2912 "pzip_all requires at least 2 containers");
2913
2914 // Convert all containers to random access FIRST
2915 auto holders = std::make_tuple(
2917
2918 // Now get min size - O(1) because all holders have cached sizes
2919 const size_t n = parallel_zip_detail::min_holder_size(holders);
2920 if (n == 0)
2921 return true; // Vacuous truth
2922
2923 auto & pool = parallel_detail::selected_parallel_pool(options);
2924 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
2925 {
2926 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2927 constexpr size_t N = sizeof...(Containers);
2928 auto iters = parallel_zip_detail::make_iterators_at(
2929 0, holders, std::make_index_sequence<N>{});
2930 for (size_t i = 0; i < n; ++i)
2931 {
2932 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2933 auto tuple = parallel_zip_detail::deref_all_iters(iters, std::make_index_sequence<N>{});
2934 if (! std::apply(pred, tuple))
2935 return false;
2936 parallel_zip_detail::advance_all_iters(iters, std::make_index_sequence<N>{});
2937 }
2938 return true;
2939 }
2940
2941 const size_t chunk_size = parallel_detail::effective_parallel_chunk_size(
2942 n, pool, options, pool.num_threads());
2943
2944 std::atomic<bool> found_false{false};
2945 std::vector<std::future<void>> futures;
2946
2947 size_t offset = 0;
2948 while (offset < n)
2949 {
2950 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
2951 size_t chunk_end = std::min(offset + chunk_size, n);
2952
2953 futures.push_back(pool.enqueue([&holders, pred, &found_false, offset, chunk_end,
2954 token = options.cancel_token]()
2955 {
2956 if (found_false.load(std::memory_order_relaxed))
2957 return;
2958
2959 constexpr size_t N = sizeof...(Containers);
2960 auto iters = parallel_zip_detail::make_iterators_at(
2961 offset, holders, std::make_index_sequence<N>{});
2962
2963 for (size_t i = offset; i < chunk_end; ++i)
2964 {
2965 parallel_detail::throw_if_parallel_canceled(token);
2966 auto tuple = parallel_zip_detail::deref_all_iters(
2967 iters, std::make_index_sequence<N>{});
2968 if (! std::apply(pred, tuple))
2969 {
2970 found_false.store(true, std::memory_order_relaxed);
2971 return;
2972 }
2973 if (found_false.load(std::memory_order_relaxed))
2974 return;
2975 parallel_zip_detail::advance_all_iters(
2976 iters, std::make_index_sequence<N>{});
2977 }
2978 }));
2979
2980 offset = chunk_end;
2981 }
2982
2983 for (auto & f: futures)
2984 f.get();
2985
2986 return not found_false.load();
2987 }
2988
3017 template <typename Pred, typename... Containers>
3018 [[nodiscard]] bool pzip_exists_n(ThreadPool & pool, Pred pred, const Containers &... cs)
3019 {
3021 options.pool = &pool;
3022 return pzip_exists_n(pred, options, cs...);
3023 }
3024
3025 template <typename Pred, typename... Containers>
3026 [[nodiscard]] bool pzip_exists_n(Pred pred, const ParallelOptions & options,
3027 const Containers &... cs)
3028 {
3029 static_assert(sizeof...(Containers) >= 2,
3030 "pzip_exists requires at least 2 containers");
3031
3032 // Convert all containers to random access FIRST
3033 auto holders = std::make_tuple(parallel_zip_detail::ContainerHolder<Containers>(cs)...);
3034
3035 // Now get min size - O(1) because all holders have cached sizes
3036 const size_t n = parallel_zip_detail::min_holder_size(holders);
3037 if (n == 0)
3038 return false;
3039
3040 auto & pool = parallel_detail::selected_parallel_pool(options);
3041 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
3042 {
3043 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3044 constexpr size_t N = sizeof...(Containers);
3045 auto iters = parallel_zip_detail::make_iterators_at(
3046 0, holders, std::make_index_sequence<N>{});
3047 for (size_t i = 0; i < n; ++i)
3048 {
3049 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3050 auto tuple = parallel_zip_detail::deref_all_iters(iters, std::make_index_sequence<N>{});
3051 if (std::apply(pred, tuple))
3052 return true;
3053 parallel_zip_detail::advance_all_iters(iters, std::make_index_sequence<N>{});
3054 }
3055 return false;
3056 }
3057
3058 const size_t chunk_size = parallel_detail::effective_parallel_chunk_size(
3059 n, pool, options, pool.num_threads());
3060
3061 std::atomic<bool> found{false};
3062 std::vector<std::future<void>> futures;
3063
3064 size_t offset = 0;
3065 while (offset < n)
3066 {
3067 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3068 size_t chunk_end = std::min(offset + chunk_size, n);
3069
3070 futures.push_back(pool.enqueue([&holders, pred, &found, offset, chunk_end,
3071 token = options.cancel_token]()
3072 {
3073 if (found.load(std::memory_order_relaxed))
3074 return;
3075
3076 constexpr size_t N = sizeof...(Containers);
3077 auto iters = parallel_zip_detail::make_iterators_at(
3078 offset, holders, std::make_index_sequence<N>{});
3079
3080 for (size_t i = offset; i < chunk_end; ++i)
3081 {
3082 parallel_detail::throw_if_parallel_canceled(token);
3083 auto tuple = parallel_zip_detail::deref_all_iters(
3084 iters, std::make_index_sequence<N>{});
3085 if (std::apply(pred, tuple))
3086 {
3087 found.store(true, std::memory_order_relaxed);
3088 return;
3089 }
3090 if (found.load(std::memory_order_relaxed))
3091 return;
3092 parallel_zip_detail::advance_all_iters(
3093 iters, std::make_index_sequence<N>{});
3094 }
3095 }));
3096
3097 offset = chunk_end;
3098 }
3099
3100 for (auto & f: futures)
3101 f.get();
3102
3103 return found.load();
3104 }
3105
3121 template <typename Pred, typename... Containers>
3122 [[nodiscard]] size_t pzip_count_if_n(ThreadPool & pool, Pred pred,
3123 const Containers &... cs)
3124 {
3126 options.pool = &pool;
3127 return pzip_count_if_n(pred, options, cs...);
3128 }
3129
3130 template <typename Pred, typename... Containers>
3131 [[nodiscard]] size_t pzip_count_if_n(Pred pred, const ParallelOptions & options,
3132 const Containers &... cs)
3133 {
3134 static_assert(sizeof...(Containers) >= 2,
3135 "pzip_count_if requires at least 2 containers");
3136
3137 // Convert all containers to random access FIRST
3138 auto holders = std::make_tuple(parallel_zip_detail::ContainerHolder<Containers>(cs)...);
3139
3140 // Now get min size - O(1) because all holders have cached sizes
3141 const size_t n = parallel_zip_detail::min_holder_size(holders);
3142 if (n == 0)
3143 return 0;
3144
3145 auto & pool = parallel_detail::selected_parallel_pool(options);
3146 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
3147 {
3148 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3149 constexpr size_t N = sizeof...(Containers);
3150 auto iters = parallel_zip_detail::make_iterators_at(
3151 0, holders, std::make_index_sequence<N>{});
3152 size_t count = 0;
3153 for (size_t i = 0; i < n; ++i)
3154 {
3155 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3156 auto tuple = parallel_zip_detail::deref_all_iters(iters, std::make_index_sequence<N>{});
3157 if (std::apply(pred, tuple))
3158 ++count;
3159 parallel_zip_detail::advance_all_iters(iters, std::make_index_sequence<N>{});
3160 }
3161 return count;
3162 }
3163
3164 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
3165 pool.num_threads());
3166
3167 std::vector<std::future<size_t>> futures;
3168
3169 size_t offset = 0;
3170 while (offset < n)
3171 {
3172 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3173 size_t chunk_end = std::min(offset + chunk_size, n);
3174
3175 futures.push_back(pool.enqueue([&holders, pred, offset, chunk_end,
3176 token = options.cancel_token]()
3177 {
3178 constexpr size_t N = sizeof...(Containers);
3179 auto iters = parallel_zip_detail::make_iterators_at(
3180 offset, holders, std::make_index_sequence<N>{});
3181
3182 size_t count = 0;
3183 for (size_t i = offset; i < chunk_end; ++i)
3184 {
3185 parallel_detail::throw_if_parallel_canceled(token);
3186 auto tuple = parallel_zip_detail::deref_all_iters(
3187 iters, std::make_index_sequence<N>{});
3188 if (std::apply(pred, tuple))
3189 ++count;
3190 parallel_zip_detail::advance_all_iters(
3191 iters, std::make_index_sequence<N>{});
3192 }
3193 return count;
3194 }));
3195
3196 offset = chunk_end;
3197 }
3198
3199 size_t total = 0;
3200 for (auto & f: futures)
3201 total += f.get();
3202
3203 return total;
3204 }
3205
3206 // =============================================================================
3207 // Parallel Enumerate
3208 // =============================================================================
3209
3235 template <typename Container, typename Op>
3237 size_t chunk_size = 0)
3238 {
3240 options.pool = &pool;
3241 options.chunk_size = chunk_size;
3243 }
3244
3245 template <typename Container, typename Op>
3247 const ParallelOptions & options = {})
3248 {
3249 const size_t n = std::distance(std::begin(c), std::end(c));
3250 if (n == 0)
3251 return;
3252
3253 auto & pool = parallel_detail::selected_parallel_pool(options);
3254 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
3255 {
3256 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3257 auto it = std::begin(c);
3258 for (size_t i = 0; i < n; ++i, ++it)
3259 {
3260 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3261 op(i, *it);
3262 }
3263 return;
3264 }
3265
3266 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
3267 pool.num_threads());
3268
3269 std::vector<std::future<void>> futures;
3270
3271 size_t offset = 0;
3272 while (offset < n)
3273 {
3274 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3275 size_t chunk_end = std::min(offset + chunk_size, n);
3276
3277 futures.push_back(pool.enqueue([&c, op, offset, chunk_end,
3278 token = options.cancel_token]()
3279 {
3280 auto it = std::begin(c);
3281 std::advance(it, offset);
3282 for (size_t i = offset; i < chunk_end; ++i, ++it)
3283 {
3284 parallel_detail::throw_if_parallel_canceled(token);
3285 op(i, *it);
3286 }
3287 }));
3288
3289 offset = chunk_end;
3290 }
3291
3292 for (auto & f: futures)
3293 f.get();
3294 }
3295
3308 template <typename Container, typename Op>
3309 void penumerate_for_each(ThreadPool & pool, const Container & c, Op op,
3310 size_t chunk_size = 0)
3311 {
3313 options.pool = &pool;
3314 options.chunk_size = chunk_size;
3316 }
3317
3318 template <typename Container, typename Op>
3319 void penumerate_for_each(const Container & c, Op op,
3320 const ParallelOptions & options = {})
3321 {
3322 const size_t n = std::distance(std::begin(c), std::end(c));
3323 if (n == 0)
3324 return;
3325
3326 auto & pool = parallel_detail::selected_parallel_pool(options);
3327 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
3328 {
3329 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3330 auto it = std::begin(c);
3331 for (size_t i = 0; i < n; ++i, ++it)
3332 {
3333 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3334 op(i, *it);
3335 }
3336 return;
3337 }
3338
3339 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
3340 pool.num_threads());
3341
3342 auto data_holder = parallel_detail::ensure_random_access(c);
3343 const auto & data = parallel_detail::deref(data_holder);
3344
3345 std::vector<std::future<void>> futures;
3346
3347 size_t offset = 0;
3348 while (offset < n)
3349 {
3350 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3351 size_t chunk_end = std::min(offset + chunk_size, n);
3352
3353 futures.push_back(pool.enqueue([&data, op, offset, chunk_end,
3354 token = options.cancel_token]()
3355 {
3356 auto it = std::begin(data);
3357 std::advance(it, offset);
3358 for (size_t i = offset; i < chunk_end; ++i, ++it)
3359 {
3360 parallel_detail::throw_if_parallel_canceled(token);
3361 op(i, *it);
3362 }
3363 }));
3364
3365 offset = chunk_end;
3366 }
3367
3368 for (auto & f: futures)
3369 f.get();
3370 }
3371
3400 template <typename Container, typename Op>
3401 [[nodiscard]] auto penumerate_maps(ThreadPool & pool, const Container & c, Op op,
3402 size_t chunk_size = 0)
3403 {
3405 options.pool = &pool;
3406 options.chunk_size = chunk_size;
3407 return penumerate_maps(c, op, options);
3408 }
3409
3410 template <typename Container, typename Op>
3411 [[nodiscard]] auto penumerate_maps(const Container & c, Op op,
3412 const ParallelOptions & options = {})
3413 {
3414 using T = std::decay_t<decltype(*std::begin(c))>;
3415 using ResultT = std::invoke_result_t<Op, size_t, const T &>;
3416
3417 const size_t n = std::distance(std::begin(c), std::end(c));
3418 if (n == 0)
3419 return std::vector<ResultT>{};
3420
3421 auto & pool = parallel_detail::selected_parallel_pool(options);
3422 if (parallel_detail::use_sequential_parallel_path(n, pool, options))
3423 {
3424 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3425 std::vector<ResultT> result;
3426 result.reserve(n);
3427 auto it = std::begin(c);
3428 for (size_t i = 0; i < n; ++i, ++it)
3429 {
3430 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3431 result.push_back(op(i, *it));
3432 }
3433 return result;
3434 }
3435
3436 size_t chunk_size = parallel_detail::effective_parallel_chunk_size(n, pool, options,
3437 pool.num_threads());
3438
3439 auto data_holder = parallel_detail::ensure_random_access(c);
3440 const auto & data = parallel_detail::deref(data_holder);
3441
3442 std::vector<std::optional<ResultT>> slots(n);
3443 std::vector<std::future<void>> futures;
3444
3445 size_t offset = 0;
3446 while (offset < n)
3447 {
3448 parallel_detail::throw_if_parallel_canceled(options.cancel_token);
3449 size_t chunk_end = std::min(offset + chunk_size, n);
3450
3451 futures.push_back(pool.enqueue([&slots, &data, op, offset, chunk_end,
3452 token = options.cancel_token]()
3453 {
3454 auto it = std::begin(data);
3455 std::advance(it, offset);
3456 for (size_t i = offset; i < chunk_end; ++i, ++it)
3457 {
3458 parallel_detail::throw_if_parallel_canceled(token);
3459 slots[i] = op(i, *it);
3460 }
3461 }));
3462 offset = chunk_end;
3463 }
3464
3465 for (auto & f: futures)
3466 f.get();
3467
3468 std::vector<ResultT> result;
3469 result.reserve(n);
3470 for (auto & slot: slots)
3471 result.push_back(std::move(*slot));
3472 return result;
3473
3474 }
3475
3476 // =============================================================================
3477 // Convenience: Default Pool Variants
3478 // =============================================================================
3479
3487 {
3488 return default_pool();
3489 }
3490
3491 // Convenience macros for using default pool (optional)
3492#ifdef AH_PARALLEL_USE_DEFAULT_POOL
3493
3494#define PMAP(c, op) pmaps(parallel_default_pool(), c, op)
3495#define PFILTER(c, pred) pfilter(parallel_default_pool(), c, pred)
3496#define PFOLD(c, init, op) pfoldl(parallel_default_pool(), c, init, op)
3497#define PFOR_EACH(c, op) pfor_each(parallel_default_pool(), c, op)
3498#define PALL(c, pred) pall(parallel_default_pool(), c, pred)
3499#define PEXISTS(c, pred) pexists(parallel_default_pool(), c, pred)
3500#define PSUM(c) psum(parallel_default_pool(), c)
3501
3502#endif // AH_PARALLEL_USE_DEFAULT_POOL
3503} // namespace Aleph
3504
3505#endif // AH_PARALLEL_H
C++20 Ranges support and adaptors for Aleph-w containers.
Read-only cooperative cancellation token.
void throw_if_cancellation_requested() const
Throw operation_canceled if cancellation was requested.
A reusable thread pool for efficient parallel task execution.
size_t num_threads() const noexcept
Get the number of worker threads.
auto enqueue(F &&f, Args &&... args) -> std::future< std::invoke_result_t< F, Args... > >
Submit a task for execution and get a future for the result.
#define N
Definition fib.C:294
int cmp(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4129
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Freq_Node * pred
Predecessor node in level-order traversal.
const long double offset[]
Offset values indexed by symbol string length (bounded by MAX_OFFSET_INDEX)
decltype(auto) deref(T &&ptr)
Get reference from pointer or unique_ptr.
bool use_sequential_parallel_path(const size_t n, const ThreadPool &pool, const ParallelOptions &options) noexcept
void throw_if_parallel_canceled(const CancellationToken &token)
size_t chunk_count(const size_t n, const size_t chunk_size) noexcept
ThreadPool & selected_parallel_pool(const ParallelOptions &options)
auto ensure_random_access(const Container &c)
For containers with random access, just return a pointer to it For non-random access,...
size_t effective_parallel_chunk_size(const size_t n, const ThreadPool &pool, const ParallelOptions &options, const size_t min_chunk=64)
size_t chunk_size(const size_t n, const size_t num_threads, const size_t min_chunk=64)
Calculate optimal chunk size based on data size and thread count.
constexpr bool has_random_access()
Check if container supports random access.
chunk_bounds bounds_for_chunk(const size_t idx, const size_t n, const size_t chunk_size) noexcept
size_t min_holder_size_impl(const std::tuple< Holders... > &holders, std::index_sequence< Is... >)
Get minimum size from tuple of holders - always O(1) per holder.
void advance_all_iters(std::tuple< Iters... > &iters, std::index_sequence< Is... >)
Advance all iterators in tuple.
auto make_iterators_at(size_t offset, const std::tuple< Holders... > &holders, std::index_sequence< Is... >)
Create tuple of iterators at given offset.
size_t min_holder_size(const std::tuple< Holders... > &holders)
auto deref_all_iters(const std::tuple< Iters... > &iters, std::index_sequence< Is... >)
Dereference all iterators and make tuple.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
bool pall(ThreadPool &pool, const Container &c, Pred pred, size_t chunk_size=0)
Parallel all predicate (short-circuit).
bool pnone(ThreadPool &pool, const Container &c, Pred pred, size_t chunk_size=0)
Parallel none predicate.
auto pmaps(ThreadPool &pool, const Container &c, Op op, size_t chunk_size=0)
Parallel map operation.
auto pmin(ThreadPool &pool, const Container &c, size_t chunk_size=0)
Parallel minimum element.
T pzip_foldl_n(ThreadPool &pool, T init, Op op, Combiner combiner, const Containers &... cs)
Parallel fold/reduce over N zipped containers (variadic).
ThreadPool & default_pool()
Return the default shared thread pool instance.
T pzip_foldl(ThreadPool &pool, const Container1 &c1, const Container2 &c2, T init, Op op, size_t chunk_size=0)
Parallel zip + fold.
void pzip_for_each(ThreadPool &pool, const Container1 &c1, const Container2 &c2, Op op, size_t chunk_size=0)
Parallel zip + for_each.
size_t size(Node *root) noexcept
ThreadPool & parallel_default_pool()
Global default pool for parallel operations.
auto penumerate_maps(ThreadPool &pool, const Container &c, Op op, size_t chunk_size=0)
Parallel enumerate with map.
void sort_range(Range &r, Cmp cmp)
Sort a whole range in place, portably across the ranges divide.
Definition ah-ranges.H:227
void penumerate_for_each(ThreadPool &pool, Container &c, Op op, size_t chunk_size=0)
Parallel for_each with index (enumerate).
bool pzip_all_n(ThreadPool &pool, Pred pred, const Containers &... cs)
Parallel all predicate over N zipped containers (variadic).
size_t pcount_if(ThreadPool &pool, const Container &c, Pred pred, size_t chunk_size=0)
Parallel count_if operation.
auto pexclusive_scan(ThreadPool &pool, const Container &c, T init, BinaryOp op, size_t chunk_size=0)
Parallel exclusive scan over a container.
std::optional< size_t > pfind(ThreadPool &pool, const Container &c, Pred pred, size_t chunk_size=0)
Parallel find operation (returns index).
and
Check uniqueness with explicit hash + equality functors.
bool pzip_exists_n(ThreadPool &pool, Pred pred, const Containers &... cs)
Parallel exists predicate over N zipped containers (variadic).
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
void psort(ThreadPool &pool, Container &c, Compare cmp=Compare{}, const size_t min_parallel_size=1024)
Parallel sort (in-place).
void pfor_each(ThreadPool &pool, Container &c, Op op, size_t chunk_size=0)
Parallel for_each operation.
auto pfilter(ThreadPool &pool, const Container &c, Pred pred, size_t chunk_size=0)
Parallel filter operation.
auto ppartition(ThreadPool &pool, const Container &c, Pred pred, size_t chunk_size=0)
Parallel partition (stable).
auto pmerge(ThreadPool &pool, const Container1 &c1, const Container2 &c2, Compare comp=Compare{}, size_t chunk_size=0)
Parallel merge of two sorted containers.
T pproduct(ThreadPool &pool, const Container &c, T init=T{1}, size_t chunk_size=0)
Parallel product of elements.
auto pmax(ThreadPool &pool, const Container &c, size_t chunk_size=0)
Parallel maximum element.
void pzip_for_each_n(ThreadPool &pool, Op op, const Containers &... cs)
Parallel for_each over N zipped containers (variadic).
auto pzip_maps_n(ThreadPool &pool, Op op, const Containers &... cs)
Parallel map over N zipped containers (variadic).
auto pscan(ThreadPool &pool, const Container &c, BinaryOp op, size_t chunk_size=0)
Parallel inclusive scan over a container.
T pfoldl(ThreadPool &pool, const Container &c, T init, BinaryOp op, size_t chunk_size=0)
Parallel left fold (reduce).
auto pfind_value(ThreadPool &pool, const Container &c, Pred pred, size_t chunk_size=0)
Parallel find with value return.
auto pzip_maps(ThreadPool &pool, const Container1 &c1, const Container2 &c2, Op op, size_t chunk_size=0)
Parallel zip + map.
T psum(ThreadPool &pool, const Container &c, T init=T{}, size_t chunk_size=0)
Parallel sum of elements.
auto pminmax(ThreadPool &pool, const Container &c, size_t chunk_size=0)
Parallel min and max elements.
size_t pzip_count_if_n(ThreadPool &pool, Pred pred, const Containers &... cs)
Parallel count over N zipped containers (variadic).
bool pexists(ThreadPool &pool, const Container &c, Pred pred, size_t chunk_size=0)
Parallel exists predicate (short-circuit).
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
STL namespace.
static struct argp_option options[]
Definition ntreepic.C:1886
Common configuration object for parallel algorithms.
ThreadPool * pool
Executor to use (nullptr = default_pool()).
Holder for converted containers (either pointer or unique_ptr to vector).
size_t cached_size
Cached size for O(1) access.
std::decay_t< decltype(*std::begin(std::declval< Container & >()))> value_type
std::conditional_t< parallel_detail::has_random_access< Container >(), const Container *, std::unique_ptr< std::vector< value_type > > > holder_type
size_t size() const noexcept
Size is always O(1) - either from random access or from cached vector size.
Filter_Iterator< DynList< int >, DynList< int >::Iterator, Par > It
Definition test_htlist.C:61
A modern, efficient thread pool for parallel task execution.