28 const auto& scalars = msm_data.scalars;
29 const auto& points = msm_data.points;
30 const auto& scalar_indices = msm_data.scalar_indices;
31 const size_t range = scalar_indices.size();
34 for (
size_t i = 0; i < range; ++i) {
36 r += f * scalars[scalar_indices[i]].to_montgomery_form();
41template <
typename Curve>
43 std::vector<uint32_t>& nonzero_scalar_indices)
noexcept
51 auto range = chunk.
range(scalars.size());
55 std::vector<uint32_t>& thread_scalar_indices = thread_indices[chunk.
thread_index];
56 thread_scalar_indices.reserve(range.size());
57 for (
size_t i : range) {
59 auto& scalar = scalars[i];
60 scalar.self_from_montgomery_form_reduced();
62 if (!scalar.is_zero()) {
63 thread_scalar_indices.push_back(
static_cast<uint32_t
>(i));
68 size_t num_entries = 0;
69 for (
const auto& indices : thread_indices) {
70 num_entries += indices.size();
72 nonzero_scalar_indices.resize(num_entries);
80 offset += thread_indices[i].size();
88template <
typename Curve>
90 std::span<const uint32_t> nonzero_indices,
91 uint32_t bits_per_slice,
99 static constexpr uint16_t FIXED_PER_SCALAR_WEIGHT = 4;
100 static_assert(NUM_BITS_IN_FIELD + FIXED_PER_SCALAR_WEIGHT <= std::numeric_limits<uint16_t>::max(),
101 "slice-count weight overflows uint16_t");
104 const size_t n = nonzero_indices.size();
108 for (
size_t k : chunk.
range(n)) {
109 const auto& scalar = scalars[nonzero_indices[k]];
112 const uint64_t msb =
uint256_t{ scalar.
data[0], scalar.data[1], scalar.data[2], scalar.data[3] }.get_msb();
113 const size_t bit_length =
static_cast<size_t>(msb) + 1;
115 static_cast<uint16_t
>((bit_length + bits_per_slice - 1) / bits_per_slice) + FIXED_PER_SCALAR_WEIGHT;
120template <
typename Curve>
127 size_t grand_total_weight = 0;
128 for (
const auto& weights : msm_scalar_weights) {
129 for (uint16_t w : weights) {
130 grand_total_weight += w;
133 if (grand_total_weight == 0) {
137 const size_t weight_per_thread =
numeric::ceil_div(grand_total_weight, num_threads);
139 size_t thread_accumulated_weight = 0;
140 size_t current_thread_idx = 0;
141 for (
size_t i = 0; i < msm_scalar_weights.size(); ++i) {
142 const auto& weights = msm_scalar_weights[i];
143 const size_t n = weights.size();
146 for (
size_t k = 0; k < n; ++k) {
147 thread_accumulated_weight += weights[k];
149 if (current_thread_idx < num_threads - 1 && thread_accumulated_weight >= weight_per_thread) {
150 work_units[current_thread_idx].push_back(
MSMWorkUnit{
152 .start_index = start,
153 .size = k + 1 - start,
156 current_thread_idx++;
157 thread_accumulated_weight = 0;
161 work_units[current_thread_idx].push_back(
MSMWorkUnit{
163 .start_index = start,
171template <
typename Curve>
173 std::span<
std::span<ScalarField>> scalars, std::vector<std::vector<uint32_t>>& msm_scalar_indices)
noexcept
175 const size_t num_msms = scalars.size();
176 msm_scalar_indices.resize(num_msms);
181 size_t total_work = 0;
182 for (
size_t i = 0; i < num_msms; ++i) {
183 transform_scalar_and_get_nonzero_scalar_indices(scalars[i], msm_scalar_indices[i]);
184 const size_t n = msm_scalar_indices[i].size();
189 const uint32_t bps = get_optimal_log_num_buckets(n);
190 compute_scalar_slice_weights(scalars[i], msm_scalar_indices[i], bps, msm_scalar_weights[i]);
196 if (num_threads > total_work) {
198 for (
size_t i = 0; i < num_msms; ++i) {
202 .size = msm_scalar_indices[i].size(),
208 return partition_by_weight(msm_scalar_weights, num_threads);
226template <
typename Curve>
229 size_t slice_size)
noexcept
231 constexpr size_t LIMB_BITS = 64;
233 size_t hi_bit = NUM_BITS_IN_FIELD - (round * slice_size);
234 size_t lo_bit = (hi_bit < slice_size) ? 0 : hi_bit - slice_size;
239 size_t start_limb = lo_bit / LIMB_BITS;
240 size_t end_limb = hi_bit / LIMB_BITS;
241 size_t lo_slice_offset = lo_bit & (LIMB_BITS - 1);
242 size_t actual_slice_size = hi_bit - lo_bit;
243 size_t lo_slice_bits =
244 (LIMB_BITS - lo_slice_offset < actual_slice_size) ? (LIMB_BITS - lo_slice_offset) : actual_slice_size;
245 size_t hi_slice_bits = actual_slice_size - lo_slice_bits;
247 uint64_t lo_slice = (scalar.data[start_limb] >> lo_slice_offset) & ((1ULL << lo_slice_bits) - 1);
248 uint64_t hi_slice = (start_limb != end_limb) ? (scalar.data[end_limb] & ((1ULL << hi_slice_bits) - 1)) : 0;
250 return static_cast<uint32_t
>(lo_slice | (hi_slice << lo_slice_bits));
256 auto compute_cost = [&](uint32_t bits) {
258 size_t buckets =
size_t{ 1 } << bits;
259 return rounds * (num_points + buckets * BUCKET_ACCUMULATION_COST);
262 uint32_t best_bits = 1;
263 size_t best_cost = compute_cost(1);
264 for (uint32_t bits = 2; bits < MAX_SLICE_BITS; ++bits) {
265 size_t cost = compute_cost(bits);
266 if (cost < best_cost) {
276 if (num_points < AFFINE_TRICK_THRESHOLD) {
288 constexpr size_t COST_OF_INVERSION = NUM_BITS_IN_FIELD + ((NUM_BITS_IN_FIELD + 3) / 4) + INVERSION_TABLE_COST;
290 double log2_num_points = log2(
static_cast<double>(num_points));
291 size_t savings_per_round = (num_points * AFFINE_TRICK_SAVINGS_PER_OP) + (num_buckets * JACOBIAN_Z_NOT_ONE_PENALTY);
292 double inversion_cost_per_round = log2_num_points *
static_cast<double>(COST_OF_INVERSION);
294 return static_cast<double>(savings_per_round) > inversion_cost_per_round;
297template <
typename Curve>
299 const size_t num_points,
308 bb::group_elements::batch_affine_add_interleaved<AffineElement, BaseField>(points, num_points, scratch_space);
311template <
typename Curve>
314 const size_t size = msm_data.scalar_indices.size();
315 const uint32_t bits_per_slice = get_optimal_log_num_buckets(size);
316 const size_t num_buckets =
size_t{ 1 } << bits_per_slice;
317 const uint32_t num_rounds =
static_cast<uint32_t
>((NUM_BITS_IN_FIELD + bits_per_slice - 1) / bits_per_slice);
318 const uint32_t remainder = NUM_BITS_IN_FIELD % bits_per_slice;
321 Element msm_result = Curve::Group::point_at_infinity;
323 for (uint32_t round = 0; round < num_rounds; ++round) {
325 for (
size_t i = 0; i < size; ++i) {
326 uint32_t idx = msm_data.scalar_indices[i];
327 uint32_t bucket = get_scalar_slice(msm_data.scalars[idx], round, bits_per_slice);
330 bucket_data.
buckets[bucket] += msm_data.points[idx];
332 bucket_data.
buckets[bucket] = msm_data.points[idx];
339 Element bucket_result = accumulate_buckets(bucket_data);
342 uint32_t num_doublings = (round == num_rounds - 1 && remainder != 0) ? remainder : bits_per_slice;
343 for (uint32_t i = 0; i < num_doublings; ++i) {
344 msm_result.self_dbl();
346 msm_result += bucket_result;
351template <
typename Curve>
354 const size_t num_points = msm_data.scalar_indices.size();
355 const uint32_t bits_per_slice = get_optimal_log_num_buckets(num_points);
356 const size_t num_buckets =
size_t{ 1 } << bits_per_slice;
358 if (!use_affine_trick(num_points, num_buckets)) {
359 return jacobian_pippenger_with_transformed_scalars(msm_data);
362 const uint32_t num_rounds =
static_cast<uint32_t
>((NUM_BITS_IN_FIELD + bits_per_slice - 1) / bits_per_slice);
363 const uint32_t remainder = NUM_BITS_IN_FIELD % bits_per_slice;
369 Element msm_result = Curve::Group::point_at_infinity;
371 for (uint32_t round = 0; round < num_rounds; ++round) {
374 for (
size_t i = 0; i < num_points; ++i) {
375 uint32_t idx = msm_data.scalar_indices[i];
376 uint32_t bucket_idx = get_scalar_slice(msm_data.scalars[idx], round, bits_per_slice);
377 msm_data.point_schedule[i] = PointScheduleEntry::create(idx, bucket_idx).data;
382 size_t num_zero_bucket_entries =
384 size_t round_size = num_points - num_zero_bucket_entries;
387 Element bucket_result = Curve::Group::point_at_infinity;
388 if (round_size > 0) {
389 std::span<uint64_t> schedule(&msm_data.point_schedule[num_zero_bucket_entries], round_size);
390 batch_accumulate_points_into_buckets(schedule, msm_data.points, affine_data, bucket_data);
391 bucket_result = accumulate_buckets(bucket_data);
396 uint32_t num_doublings = (round == num_rounds - 1 && remainder != 0) ? remainder : bits_per_slice;
397 for (uint32_t i = 0; i < num_doublings; ++i) {
398 msm_result.self_dbl();
400 msm_result += bucket_result;
406template <
typename Curve>
413 if (point_schedule.empty()) {
418 size_t scratch_it = 0;
419 const size_t num_points = point_schedule.size();
420 const size_t prefetch_max = (num_points >= PREFETCH_LOOKAHEAD) ? (num_points - PREFETCH_LOOKAHEAD) : 0;
421 const size_t last_index = num_points - 1;
424 while (point_it < num_points || scratch_it != 0) {
426 while (((scratch_it + 1) < AffineAdditionData::BATCH_SIZE) && (point_it < last_index)) {
428 if ((point_it < prefetch_max) && ((point_it & PREFETCH_INTERVAL_MASK) == 0)) {
429 for (
size_t i = PREFETCH_LOOKAHEAD / 2; i < PREFETCH_LOOKAHEAD; ++i) {
431 __builtin_prefetch(&points[entry.point_index()]);
438 process_bucket_pair(lhs.bucket_index(),
440 &points[lhs.point_index()],
441 &points[rhs.point_index()],
449 if (point_it == last_index) {
451 process_single_point(
452 last.bucket_index(), &points[last.point_index()], affine_data, bucket_data, scratch_it, point_it);
456 size_t num_points_to_add = scratch_it;
457 if (num_points_to_add >= 2) {
459 affine_data.points_to_add.data(), num_points_to_add, affine_data.inversion_scratch_space.data());
463 AffineElement* affine_output = affine_data.points_to_add.data() + (num_points_to_add / 2);
466 size_t new_scratch_it = 0;
467 size_t output_it = 0;
468 size_t num_outputs = num_points_to_add / 2;
470 while ((num_outputs > 1) && (output_it + 1 < num_outputs)) {
471 uint32_t lhs_bucket = affine_data.addition_result_bucket_destinations[output_it];
472 uint32_t rhs_bucket = affine_data.addition_result_bucket_destinations[output_it + 1];
474 process_bucket_pair(lhs_bucket,
476 &affine_output[output_it],
477 &affine_output[output_it + 1],
485 if (num_outputs > 0 && output_it == num_outputs - 1) {
486 uint32_t bucket = affine_data.addition_result_bucket_destinations[output_it];
487 process_single_point(
488 bucket, &affine_output[output_it], affine_data, bucket_data, new_scratch_it, output_it);
492 scratch_it = new_scratch_it;
496template <
typename Curve>
500 bool handle_edge_cases)
noexcept
504 const size_t num_msms = points.size();
514 auto pippenger_impl =
515 handle_edge_cases ? jacobian_pippenger_with_transformed_scalars : affine_pippenger_with_transformed_scalars;
519 BB_BENCH_NAME(
"MSM::batch_multi_scalar_mul/evaluate_work_units");
522 if (!thread_work_units[thread_idx].empty()) {
525 msm_results.reserve(msms.size());
528 std::vector<uint64_t> point_schedule_buffer;
531 point_schedule_buffer.resize(msm.size);
533 MSMData::from_work_unit(scalars, points, msm_scalar_indices, point_schedule_buffer, msm);
535 (msm.size < PIPPENGER_THRESHOLD) ? small_mul<Curve>(msm_data) : pippenger_impl(msm_data);
537 msm_results.emplace_back(msm_result, msm.batch_msm_index);
545 std::vector<Element> results(num_msms, Curve::Group::point_at_infinity);
547 BB_BENCH_NAME(
"MSM::batch_multi_scalar_mul/accumulate_results");
548 for (
const auto& single_thread_msm_results : thread_msm_results) {
549 for (
const auto& [element,
index] : single_thread_msm_results) {
550 results[
index] += element;
555 BB_BENCH_NAME(
"MSM::batch_multi_scalar_mul/batch_normalize");
556 Element::batch_normalize(results.data(), num_msms);
561 BB_BENCH_NAME(
"MSM::batch_multi_scalar_mul/scalars_to_montgomery");
562 for (
auto& scalar_span : scalars) {
564 BB_BENCH_TRACY_NAME(
"MSM::scalars_to_montgomery/chunk");
565 for (size_t i = start; i < end; ++i) {
566 scalar_span[i].self_to_montgomery_form();
572 return std::vector<AffineElement>(results.begin(), results.end());
575template <
typename Curve>
578 bool handle_edge_cases)
noexcept
580 if (scalars.size() == 0) {
581 return Curve::Group::affine_point_at_infinity;
583 const size_t num_scalars = scalars.size();
584 BB_ASSERT_GTE(points.size(), scalars.start_index + num_scalars);
596 auto results = batch_multi_scalar_mul(std::span(points_batch), std::span(scalars_batch), handle_edge_cases);
600template <
typename Curve>
603 [[maybe_unused]]
bool handle_edge_cases)
noexcept
608template <
typename Curve>
617 bool handle_edge_cases =
true) noexcept;
619template curve::Grumpkin::
Element pippenger_unsafe<curve::Grumpkin>(
620 PolynomialSpan<const curve::Grumpkin::ScalarField> scalars,
std::span<const curve::Grumpkin::AffineElement> points);
623 std::span<const curve::
BN254::AffineElement> points,
624 bool handle_edge_cases = true);
627 std::span<const curve::
BN254::AffineElement> points);
631template class
bb::scalar_multiplication::
MSM<
bb::curve::Grumpkin>;
632template class
bb::scalar_multiplication::
MSM<
bb::curve::
BN254>;
#define BB_ASSERT_GTE(left, right,...)
#define BB_ASSERT_GT(left, right,...)
#define BB_ASSERT_DEBUG(expression,...)
#define BB_ASSERT_EQ(actual, expected,...)
#define BB_BENCH_NAME(name)
#define BB_BENCH_TRACY_NAME(name)
BB_INLINE bool get(size_t index) const noexcept
BB_INLINE void set(size_t index, bool value) noexcept
typename Group::element Element
typename Group::affine_element AffineElement
typename Curve::BaseField BaseField
static bool use_affine_trick(size_t num_points, size_t num_buckets) noexcept
Decide if batch inversion saves work vs Jacobian additions.
static Element jacobian_pippenger_with_transformed_scalars(MSMData &msm_data) noexcept
Pippenger using Jacobian buckets (handles edge cases: doubling, infinity)
static uint32_t get_scalar_slice(const ScalarField &scalar, size_t round, size_t slice_size) noexcept
Extract c-bit slice from scalar for bucket index computation.
static std::vector< ThreadWorkUnits > partition_by_weight(std::span< const std::vector< uint16_t > > msm_scalar_weights, size_t num_threads) noexcept
Partition per-MSM scalar weights into num_threads work units of approximately equal cumulative weight...
static Element affine_pippenger_with_transformed_scalars(MSMData &msm_data) noexcept
Pippenger using affine buckets with batch inversion (faster, no edge case handling)
static void compute_scalar_slice_weights(std::span< const ScalarField > scalars, std::span< const uint32_t > nonzero_indices, uint32_t bits_per_slice, std::vector< uint16_t > &weights) noexcept
Compute per-scalar slice-count weights ceil(bit_length / bits_per_slice).
static void add_affine_points(AffineElement *points, const size_t num_points, typename Curve::BaseField *scratch_space) noexcept
Batch add n/2 independent point pairs using Montgomery's trick.
static std::vector< ThreadWorkUnits > get_work_units(std::span< std::span< ScalarField > > scalars, std::vector< std::vector< uint32_t > > &msm_scalar_indices) noexcept
Distribute multiple MSMs across threads with balanced bucket-accumulation work.
typename Curve::Element Element
static uint32_t get_optimal_log_num_buckets(size_t num_points) noexcept
Compute optimal bits per slice by minimizing cost over c in [1, MAX_SLICE_BITS)
static std::vector< AffineElement > batch_multi_scalar_mul(std::span< std::span< const AffineElement > > points, std::span< std::span< ScalarField > > scalars, bool handle_edge_cases=true) noexcept
Compute multiple MSMs in parallel with work balancing.
static void batch_accumulate_points_into_buckets(std::span< const uint64_t > point_schedule, std::span< const AffineElement > points, AffineAdditionData &affine_data, BucketAccumulators &bucket_data) noexcept
Process sorted point schedule into bucket accumulators using batched affine additions.
typename Curve::ScalarField ScalarField
typename Curve::AffineElement AffineElement
static void transform_scalar_and_get_nonzero_scalar_indices(std::span< ScalarField > scalars, std::vector< uint32_t > &nonzero_scalar_indices) noexcept
Convert scalars from Montgomery form and collect indices of nonzero scalars.
bb::curve::BN254::Element Element
constexpr T ceil_div(const T &numerator, const T &denominator)
Computes the ceiling of the division of two integral types.
Curve::Element small_mul(const typename MSM< Curve >::MSMData &msm_data) noexcept
Curve::Element pippenger(PolynomialSpan< const typename Curve::ScalarField > scalars, std::span< const typename Curve::AffineElement > points, bool handle_edge_cases) noexcept
Safe MSM wrapper (defaults to handle_edge_cases=true)
size_t sort_point_schedule_and_count_zero_buckets(uint64_t *point_schedule, const size_t num_entries, const uint32_t bucket_index_bits) noexcept
Sort point schedule by bucket index and count zero-bucket entries.
Curve::Element pippenger_unsafe(PolynomialSpan< const typename Curve::ScalarField > scalars, std::span< const typename Curve::AffineElement > points) noexcept
Fast MSM wrapper for linearly independent points (no edge case handling)
Entry point for Barretenberg command-line interface.
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
void parallel_for_range(size_t num_points, const std::function< void(size_t, size_t)> &func, size_t no_multhreading_if_less_or_equal)
Split a loop into several loops running in parallel.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
auto range(size_t size, size_t offset=0) const
Scratch space for batched affine point additions (one per thread)
Affine bucket accumulators for the fast affine-trick Pippenger variant.
Jacobian bucket accumulators for the safe Pippenger variant.
std::vector< Element > buckets
Container for MSM input data passed between algorithm stages.
MSMWorkUnit describes an MSM that may be part of a larger MSM.
Packed point schedule entry: (point_index << 32) | bucket_index.