24 std::span<typename Curve::AffineElement>
doubled;
37template <
typename Curve>
41 std::span<const uint32_t> dedup_infos = {})
noexcept
49 const size_t K = scalar_arrays.size();
51 out_results.assign(K, Curve::Group::point_at_infinity);
53 auto info_for = [&](
size_t m)
noexcept ->
size_t {
return m < dedup_infos.size() ? dedup_infos[m] : 0; };
59 const size_t n = std::min(scalar_arrays[0].size(), point_arrays[0].size());
63 PolynomialSpan<const ScalarField> sp(0, std::span<const ScalarField>(scalar_arrays[0].
data(), n));
64 out_results[0] = pippenger_round_parallel<Curve>(sp, point_arrays[0], info_for(0));
68 std::vector<size_t> n_input(K);
69 for (
size_t m = 0; m < K; ++m) {
70 n_input[m] = std::min(scalar_arrays[m].size(), point_arrays[m].size());
79 auto find_or_create_group = [&](
const AffineElement* base_ptr,
size_t n) ->
size_t {
80 for (
size_t g = 0;
g < glv_groups.size(); ++
g) {
81 if (glv_groups[g].base_ptr == base_ptr) {
87 g.base_ptr = base_ptr;
90 return glv_groups.size() - 1;
93 std::vector<size_t> msm_to_group(K, std::numeric_limits<size_t>::max());
94 for (
size_t m = 0; m < K; ++m) {
95 if (n_input[m] == 0) {
98 const size_t g = find_or_create_group(point_arrays[m].
data(), n_input[m]);
99 glv_groups[
g].member_msms.push_back(m);
103 std::vector<bool> group_uses_glv(glv_groups.size(),
false);
104 for (
size_t g = 0;
g < glv_groups.size(); ++
g) {
109 group_uses_glv[
g] = glv_groups[
g].group_max_n <= round_parallel_detail::GLV_SMALL_N_THRESHOLD;
123 BB_BENCH_NAME(
"MSM_fast::pippenger_round_parallel_batched/glv_double_points");
125 const AffineElement* min_base =
nullptr;
126 for (
size_t g = 0;
g < glv_groups.size(); ++
g) {
127 glv_groups[
g].doubled = {};
128 if (!group_uses_glv[g]) {
132 min_base = glv_groups[
g].base_ptr;
136 if (min_base !=
nullptr) {
137 const auto min_addr =
reinterpret_cast<uintptr_t
>(min_base);
138 size_t max_extent_units = 0;
139 for (
size_t g = 0;
g < glv_groups.size(); ++
g) {
140 if (!group_uses_glv[g]) {
143 const auto base_addr =
reinterpret_cast<uintptr_t
>(glv_groups[
g].base_ptr);
144 const uintptr_t offset_bytes =
base_addr - min_addr;
147 "GLV group base_ptr not aligned to AffineElement boundary "
148 "(point spans must be subranges of a contiguous AffineElement array)");
149 const size_t offset_units = offset_bytes /
sizeof(AffineElement);
150 const size_t end_units = offset_units + glv_groups[
g].group_max_n;
151 max_extent_units =
std::max(max_extent_units, end_units);
155 2 * max_extent_units);
156 AffineElement*
const master_buf = master_doubled_owner.get();
157 const BaseField beta = BaseField::cube_root_of_unity();
159 for (
size_t i : chunk.range(max_extent_units)) {
160 master_buf[2 * i] = min_base[i];
161 master_buf[(2 * i) + 1].x = min_base[i].x * beta;
162 master_buf[(2 * i) + 1].y = -min_base[i].y;
166 for (
size_t g = 0;
g < glv_groups.size(); ++
g) {
167 if (!group_uses_glv[g]) {
170 const auto base_addr =
reinterpret_cast<uintptr_t
>(glv_groups[
g].base_ptr);
171 const size_t offset_units = (
base_addr - min_addr) /
sizeof(AffineElement);
172 glv_groups[
g].doubled =
193 const size_t mt_threshold =
194 (pool_width <= 1 || MSM_MIN_PTS_PER_THREAD > std::numeric_limits<size_t>::max() / pool_width)
195 ? std::numeric_limits<size_t>::max()
198 std::vector<size_t> small_members;
199 std::vector<size_t> large_members;
200 small_members.reserve(K);
201 large_members.reserve(K);
202 for (
size_t m = 0; m < K; ++m) {
203 if (n_input[m] == 0) {
206 if (pool_width > 1 && n_input[m] < mt_threshold) {
207 small_members.push_back(m);
209 large_members.push_back(m);
212 if (small_members.size() < 2) {
215 large_members.insert(large_members.end(), small_members.begin(), small_members.end());
216 std::sort(large_members.begin(), large_members.end());
217 small_members.clear();
220 auto external_glv_for = [&](
size_t m,
size_t n)
noexcept -> std::span<const AffineElement> {
221 const size_t g = msm_to_group[m];
222 if (g != std::numeric_limits<size_t>::max() && group_uses_glv[
g] && !glv_groups[
g].doubled.empty()) {
225 return { glv_groups[
g].doubled.data(), 2 * n };
242 const bool large_members_concurrent =
false;
244 static constexpr size_t CONCURRENT_MIN_MEMBERS = 100;
245 static constexpr size_t CONCURRENT_MIN_POOL_WIDTH = 4;
246 size_t total_large_n = 0;
247 size_t max_large_n = 0;
248 for (
size_t m : large_members) {
249 total_large_n += n_input[m];
250 max_large_n =
std::max(max_large_n, n_input[m]);
252 const bool large_members_concurrent = pool_width >= CONCURRENT_MIN_POOL_WIDTH &&
253 large_members.size() > CONCURRENT_MIN_MEMBERS &&
254 max_large_n <= total_large_n / pool_width;
262 size_t shared_arena_bytes = 0;
265 if (!large_members_concurrent) {
266 for (
size_t m : large_members) {
267 const bool ext_glv = !external_glv_for(m, n_input[m]).empty();
271 const size_t bytes = compute_arena_bytes_for_msm<Curve>(n_input[m], ext_glv, info_for(m));
272 shared_arena_bytes =
std::max(shared_arena_bytes, bytes);
274 if (shared_arena_bytes > 0) {
284 if (!small_members.empty()) {
285 BB_BENCH_NAME(
"MSM_fast::pippenger_round_parallel_batched/small_members");
286 size_t small_arena_bytes = 0;
287 for (
size_t m : small_members) {
288 const bool ext_glv = !external_glv_for(m, n_input[m]).empty();
290 compute_arena_bytes_for_msm<Curve>(n_input[m], ext_glv, info_for(m), 1);
291 small_arena_bytes =
std::max(small_arena_bytes, bytes);
296 const size_t workers_by_budget =
297 small_arena_bytes > 0 ?
std::max<size_t>(1, round_parallel_detail::BATCH_MEM_BUDGET / small_arena_bytes)
298 :
std::numeric_limits<size_t>::max();
299 const size_t num_workers = std::min({ pool_width, small_members.size(), workers_by_budget });
301 if (small_arena_bytes > 0) {
303 num_workers * small_arena_bytes);
305 std::atomic<size_t> next_member{ 0 };
308 if (small_arena_bytes > 0) {
309 worker_arena = { small_arena_owner.get() + (tid * small_arena_bytes), small_arena_bytes };
313 if (s >= small_members.size()) {
316 const size_t m = small_members[s];
317 const size_t n = n_input[m];
318 PolynomialSpan<const ScalarField> sp(0, std::span<const ScalarField>(scalar_arrays[m].
data(), n));
319 out_results[m] = pippenger_round_parallel<Curve>(
320 sp, point_arrays[m], info_for(m), external_glv_for(m, n), worker_arena, 1);
325 if (large_members_concurrent) {
333 BB_BENCH_NAME(
"MSM_fast::pippenger_round_parallel_batched/large_members_concurrent");
335 large_members.begin(), large_members.end(), [&](
size_t a,
size_t b) { return n_input[a] > n_input[b]; });
336 const size_t num_workers = std::min(pool_width, large_members.size());
337 std::atomic<size_t> next_large{ 0 };
341 if (s >= large_members.size()) {
344 const size_t m = large_members[s];
345 const size_t n = n_input[m];
346 PolynomialSpan<const ScalarField> sp(0, std::span<const ScalarField>(scalar_arrays[m].
data(), n));
347 out_results[m] = pippenger_round_parallel<Curve>(
348 sp, point_arrays[m], info_for(m), external_glv_for(m, n), {}, 1);
357 for (
size_t m : large_members) {
358 const size_t n = n_input[m];
359 PolynomialSpan<const ScalarField> sp(0, std::span<const ScalarField>(scalar_arrays[m].
data(), n));
361 pippenger_round_parallel<Curve>(sp, point_arrays[m], info_for(m), external_glv_for(m, n), shared_arena);
367template <
typename Curve>
371 bool handle_edge_cases,
372 std::span<const uint32_t> dedup_infos)
noexcept
375 const size_t k = scalars.size();
384 point_subspans.reserve(k);
385 scalar_subspans.reserve(k);
386 for (
size_t i = 0; i < k; ++i) {
387 const size_t start_i = scalars[i].start_index;
388 BB_ASSERT_LTE(start_i, points.size(),
"scalars[m].start_index exceeds shared points span");
389 point_subspans.push_back(points.subspan(start_i, points.size() - start_i));
390 scalar_subspans.push_back(scalars[i].span);
393 auto info_for = [&](
size_t m)
noexcept ->
size_t {
return m < dedup_infos.size() ? dedup_infos[m] : 0; };
395 if (handle_edge_cases) {
396 std::vector<AffineElement> results(k);
397 for (
size_t i = 0; i < k; ++i) {
398 const size_t n = std::min(point_subspans[i].size(), scalar_subspans[i].size());
400 std::span<const ScalarField>(scalar_subspans[i].
data(), n));
402 AffineElement(pippenger_fast<Curve>(scalar_span, point_subspans[i], handle_edge_cases, info_for(i)));
408 pippenger_round_parallel_batched<Curve>(scalar_subspans, point_subspans, per_msm_jac, dedup_infos);
410 std::vector<AffineElement> results(k);
411 for (
size_t i = 0; i < k; ++i) {
#define BB_ASSERT_EQ(actual, expected,...)
#define BB_ASSERT_LTE(left, right,...)
#define BB_BENCH_NAME(name)
typename Group::affine_element AffineElement
typename Curve::AffineElement AffineElement
constexpr size_t SMALL_MSM_BATCH_THRESHOLD
constexpr size_t MSM_MIN_PTS_PER_THREAD
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
constexpr void g(state_array &state, size_t a, size_t b, size_t c, size_t d, uint32_t x, uint32_t y)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
std::vector< size_t > member_msms
const Curve::AffineElement * base_ptr
std::span< typename Curve::AffineElement > doubled