Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
pippenger_fallbacks.hpp
Go to the documentation of this file.
1#pragma once
2
3// Implementation fragment included from scalar_multiplication_fast.cpp inside
4// bb::scalar_multiplication.
5
6// Trivial-N fallback. For small n the Pippenger scaffolding (digit extraction, bucket
7// scratch allocation, parallel_for dispatch, GLV split, etc.) costs many times more
8// than running a Straus-style simultaneous double-and-add in Jacobian. Delegates to
9// `Element::straus_msm`, which on endomorphism curves builds a per-point WNAF lookup
10// table and amortises ~128 doublings across all N inputs (vs N×128 for naive
11// per-point operator*). Robust to all edge cases (zero scalars, points at infinity)
12// so this also covers `handle_edge_cases=true` for trivially small N. The single
13// Jacobian→affine inversion at the caller boundary (when `MSM_fast<>::msm` constructs an
14// `AffineElement` from the returned `Element`) is the only inversion paid.
15template <typename Curve>
16typename Curve::Element trivial_msm(PolynomialSpan<const typename Curve::ScalarField> scalars_span,
18{
19 using Element = typename Curve::Element;
20 using AffineElement = typename Curve::AffineElement;
21 using ScalarField = typename Curve::ScalarField;
22
23 const size_t n = scalars_span.size();
24 if (n == 0) {
25 return Curve::Group::point_at_infinity;
26 }
27 BB_ASSERT_GTE(all_points.size(), scalars_span.start_index + n);
28 std::span<const AffineElement> points_view(&all_points[scalars_span.start_index], n);
29 std::span<const ScalarField> scalars_view(scalars_span.span.data(), n);
30 return Element::straus_msm(points_view, scalars_view);
31}
32
41template <typename Curve>
42typename Curve::Element trivial_msm_threaded(PolynomialSpan<const typename Curve::ScalarField> scalars_span,
44 size_t max_threads) noexcept
45{
46 using Element = typename Curve::Element;
47 using AffineElement = typename Curve::AffineElement;
48 using ScalarField = typename Curve::ScalarField;
49 const size_t n = scalars_span.size();
50 if (n == 0) {
51 return Curve::Group::point_at_infinity;
52 }
53 BB_ASSERT_GTE(all_points.size(), scalars_span.start_index + n);
54
55 // Strip zero-scalar entries before dispatching to straus_msm. straus_msm has
56 // non-trivial per-scalar fixed cost (per-window bias decode + bucket scatter), and
57 // when this function fires from the n_active-based fallback in
58 // pippenger_round_parallel the input span often contains many zeros (the
59 // dispatch fired precisely because n_active << n). Compacting once up front saves
60 // straus_msm one pass over the dead entries on every worker slice.
61 std::vector<ScalarField> compact_scalars;
62 std::vector<AffineElement> compact_points;
63 compact_scalars.reserve(n);
64 compact_points.reserve(n);
65 const ScalarField* src_scalars = scalars_span.span.data();
66 const AffineElement* src_points = all_points.data() + scalars_span.start_index;
67 for (size_t i = 0; i < n; ++i) {
68 if (!src_scalars[i].is_zero()) {
69 compact_scalars.push_back(src_scalars[i]);
70 compact_points.push_back(src_points[i]);
71 }
72 }
73 const size_t n_active = compact_scalars.size();
74 if (n_active == 0) {
75 return Curve::Group::point_at_infinity;
76 }
77
78 // One task per OS worker, not lmul-oversubscribed — straus_msm slices have
79 // non-trivial fixed cost so dynamic-claim averaging isn't worth the extra
80 // dispatch tax at the trivial-MSM_fast sizes this function handles. A caller's
81 // max_threads cap (or `bb::get_num_cpus() <= 1`, the chonk-batch-verifier
82 // serial gate) routes through the `<= 1` early-return below, keeping capped
83 // calls off the thread pool entirely.
84 const size_t pool_threads = max_threads == 0 ? bb::get_num_cpus() : std::min(max_threads, bb::get_num_cpus());
85 const size_t num_threads = std::min(n_active, pool_threads);
86 if (num_threads <= 1) {
87 std::span<const AffineElement> pts(compact_points.data(), n_active);
88 std::span<const ScalarField> scs(compact_scalars.data(), n_active);
89 return Element::straus_msm(pts, scs);
90 }
91
92 // Each worker runs `Element::straus_msm` over its slice. Note that straus_msm
93 // accepts Montgomery-form scalars (it converts internally), so callers must pass
94 // Montgomery-form scalars on entry to this function.
95 std::vector<Element> partials(num_threads, Curve::Group::point_at_infinity);
96 bb::parallel_for(num_threads, [&](size_t tid) {
97 const size_t lo = (tid * n_active) / num_threads;
98 const size_t hi = ((tid + 1) * n_active) / num_threads;
99 const size_t slice_n = hi - lo;
100 if (slice_n == 0) {
101 return;
102 }
103 std::span<const AffineElement> pts(compact_points.data() + lo, slice_n);
104 std::span<const ScalarField> scs(compact_scalars.data() + lo, slice_n);
105 partials[tid] = Element::straus_msm(pts, scs);
106 });
107 Element total_result = partials[0];
108 for (size_t t = 1; t < num_threads; ++t) {
109 total_result += partials[t];
110 }
111 return total_result;
112}
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
typename Group::element Element
Definition bn254.hpp:21
typename Group::affine_element AffineElement
Definition bn254.hpp:22
bb::fr ScalarField
Definition bn254.hpp:18
size_t get_num_cpus()
Definition thread.cpp:34
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:112
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::Element trivial_msm_threaded(PolynomialSpan< const typename Curve::ScalarField > scalars_span, std::span< const typename Curve::AffineElement > all_points, size_t max_threads) noexcept
Multi-threaded straus_msm driver for very-small MSMs.
Curve::Element trivial_msm(PolynomialSpan< const typename Curve::ScalarField > scalars_span, std::span< const typename Curve::AffineElement > all_points) noexcept
Curve::Element Element