Shamrock 2025.10.0
Astrophysical Code
Loading...
Searching...
No Matches
segmented_sort_in_place.cpp
Go to the documentation of this file.
1// -------------------------------------------------------//
2//
3// SHAMROCK code for hydrodynamics
4// Copyright (c) 2021-2026 Timothée David--Cléris <tim.shamrock@proton.me>
5// SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1
6// Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information
7//
8// -------------------------------------------------------//
9
15
18#include "shambase/assert.hpp"
23
24namespace shamalgs::primitives::details {
25
26 template<class T, class Comp>
27 inline void segmented_sort_in_place_local_insertion_sort(
28 sham::DeviceBuffer<T> &buf, const sham::DeviceBuffer<u32> &offsets, Comp &&comp) {
29
30 auto &q = buf.get_dev_scheduler().get_queue();
31
32 size_t interact_count = buf.get_size();
33 size_t offsets_count = offsets.get_size();
34 size_t N = offsets_count - 1;
35
37 q,
38 sham::MultiRef{offsets},
39 sham::MultiRef{buf},
40 N,
41 [interact_count,
42 comp](u32 gid, const u32 *__restrict__ offsets, T *__restrict__ in_out_sorted) {
43 u32 start_index = offsets[gid];
44 u32 end_index = offsets[gid + 1];
45
46 // can be equal if there is no interaction for this sender
47 SHAM_ASSERT(start_index <= end_index);
48
49 // skip empty ranges to avoid unnecessary work
50 if (start_index == end_index) {
51 return;
52 }
53
54 // if there is no interactions at the end of the offset list
55 // offsets[gid] can be equal to interact_count
56 // but we check that start_index != end_index, so here the correct assertions
57 // is indeed start_index < interact_count
58 SHAM_ASSERT(start_index < interact_count);
59 SHAM_ASSERT(end_index <= interact_count); // see the for loop for this one
60
61 shambase::ptr_insert_sort(in_out_sorted, start_index, end_index, comp);
62 });
63 }
64
65 template<class T, class Comp>
66 inline void segmented_sort_in_place_multi_std_sort(
67 sham::DeviceBuffer<T> &buf, const sham::DeviceBuffer<u32> &offsets, Comp &&comp) {
68
69 auto &q = buf.get_dev_scheduler().get_queue();
70
71 size_t interact_count = buf.get_size();
72 size_t offsets_count = offsets.get_size();
73 size_t N = offsets_count - 1;
74
75 std::vector<T> buf_stdvec = buf.copy_to_stdvec();
76 std::vector<u32> offsets_stdvec = offsets.copy_to_stdvec();
77
78#pragma omp parallel for
79 for (u32 i = 0; i < N; ++i) {
80 u32 start_index = offsets_stdvec[i];
81 u32 end_index = offsets_stdvec[i + 1];
82
83 // can be equal if there is no interaction for this sender
84 SHAM_ASSERT(start_index <= end_index);
85
86 // skip empty ranges to avoid unnecessary work
87 if (start_index == end_index) {
88 continue;
89 }
90
91 // if there is no interactions at the end of the offset list
92 // offsets[gid] can be equal to interact_count
93 // but we check that start_index != end_index, so here the correct assertions
94 // is indeed start_index < interact_count
95 SHAM_ASSERT(start_index < interact_count);
96 SHAM_ASSERT(end_index <= interact_count); // see the for loop for this one
97
98 std::sort(buf_stdvec.begin() + start_index, buf_stdvec.begin() + end_index, comp);
99 }
100
101 buf.copy_from_stdvec(buf_stdvec);
102 }
103
104} // namespace shamalgs::primitives::details
105
106namespace shamalgs::primitives {
107
109 namespace impl {
110
113 static constexpr std::string_view variant_type_name = "local_insertion_sort";
114 };
115
118 static constexpr std::string_view variant_type_name = "multi_std_sort";
119 };
120
122 [](const sham::DeviceScheduler_ptr &, auto &self) {
123 self.set(MultiStdSort{});
124 }};
125
128 return segmented_sort_in_place_impl.get_default_config_list();
129 }
130
133 return segmented_sort_in_place_impl.get_current_config();
134 }
135
137 bool is_impl_set_segmented_sort_in_place() { return segmented_sort_in_place_impl.is_set(); }
138
140 void set_impl_segmented_sort_in_place(const std::string &impl) {
141 shamlog_info_ln(
142 "algs", "setting segmented sort in place implementation to impl :", impl);
143 segmented_sort_in_place_impl.set(impl);
144 }
145
147 void autoselect_impl_segmented_sort_in_place(const sham::DeviceScheduler_ptr &dev_sched) {
148 segmented_sort_in_place_impl.autoselect(dev_sched);
149 shamlog_info_ln(
150 "algs",
151 "defaulting segmented sort in place implementation to impl :",
153 }
154
155 } // namespace impl
156
157 template<class T, class Comp>
158 void internal_segmented_sort_in_place(
159 sham::DeviceBuffer<T> &buf, const sham::DeviceBuffer<u32> &offsets, Comp &&comp) {
160
161 if (buf.get_size() == 0) {
162 return;
163 }
164
165 if (offsets.get_size() == 0) {
166 throw shambase::make_except_with_loc<std::invalid_argument>("offsets buffer is empty");
167 }
168
169 if (!impl::segmented_sort_in_place_impl.is_set()) {
171 }
172
173 std::visit(
176 details::segmented_sort_in_place_local_insertion_sort(buf, offsets, comp);
177 },
178 [&](impl::MultiStdSort) {
179 details::segmented_sort_in_place_multi_std_sort(buf, offsets, comp);
180 },
181 },
182 impl::segmented_sort_in_place_impl.get());
183 }
184
185 template<>
186 void segmented_sort_in_place<u32_2>(
187 sham::DeviceBuffer<u32_2> &buf, const sham::DeviceBuffer<u32> &offsets) {
188
189 internal_segmented_sort_in_place(buf, offsets, [](u32_2 a, u32_2 b) {
190 return (a.x() == b.x()) ? (a.y() < b.y()) : (a.x() < b.x());
191 });
192 }
193
194 template<>
195 void segmented_sort_in_place<u32>(
196 sham::DeviceBuffer<u32> &buf, const sham::DeviceBuffer<u32> &offsets) {
197 internal_segmented_sort_in_place(buf, offsets, [](u32 a, u32 b) {
198 return a < b;
199 });
200 }
201
202} // namespace shamalgs::primitives
Generic std::variant-based implementation selector.
std::uint32_t u32
32 bit unsigned integer
Shamrock assertion utility.
#define SHAM_ASSERT(x)
Shorthand for SHAM_ASSERT_NAMED without a message.
Definition assert.hpp:67
A buffer allocated in USM (Unified Shared Memory).
void copy_from_stdvec(const std::vector< T > &vec)
Copy the content of a std::vector into the buffer.
std::shared_ptr< DeviceScheduler > & get_dev_scheduler_ptr()
Gets the Device scheduler pointer corresponding to the held allocation.
std::vector< T > copy_to_stdvec() const
Copy the content of the buffer to a std::vector.
size_t get_size() const
Gets the number of elements in the buffer.
DeviceScheduler & get_dev_scheduler() const
Gets the Device scheduler corresponding to the held allocation.
DeviceQueue & get_queue(u32 id=0)
Get a reference to a DeviceQueue.
Drop-in replacement for the hand-rolled "global variable + enum + name mapping.
void kernel_call(sham::DeviceQueue &q, RefIn in, RefOut in_out, u32 n, Functor &&func, SourceLocation &&callsite=SourceLocation{})
Submit a kernel to a SYCL queue.
namespace to control implementation behavior
void set_impl_segmented_sort_in_place(const std::string &impl)
Set the implementation for segmented sort in place, from a config json string.
void autoselect_impl_segmented_sort_in_place(const sham::DeviceScheduler_ptr &dev_sched)
Select the default implementation for segmented sort in place.
std::string get_current_impl_segmented_sort_in_place()
Get the current implementation for segmented sort in place, as a config json string.
bool is_impl_set_segmented_sort_in_place()
Check if an implementation has been selected for segmented sort in place.
std::vector< std::string > get_default_impl_list_segmented_sort_in_place()
Get list of available segmented sort in place implementations, as config json strings.
namespace for primitive algorithm (e.g. sort, scan, reductions, ...)
void ptr_insert_sort(T *data, u32 start, u32 end, Comp &&comp)
Simple insertion sort on pointer range.
overloaded(Ts...) -> overloaded< Ts... >
Deduction guide so overloaded{lambda1, lambda2, ...} deduces Ts... from the lambdas.
ExcptTypes make_except_with_loc(std::string message, SourceLocation loc=SourceLocation{})
Create an exception with a message and a location.
Sort each segment locally with an insertion sort, one kernel work-item per segment.
Copy back to host and sort each segment with std::sort, parallelized over OpenMP.