Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
batched_affine_addition.cpp
Go to the documentation of this file.
5#include <algorithm>
6#include <execution>
7#include <set>
8#include <utility>
9
10namespace bb {
11
12template <typename Curve>
14 const std::span<G1>& points, const std::vector<size_t>& sequence_counts)
15{
16 BB_BENCH_NAME("BatchedAffineAddition::add_in_place");
17 // Instantiate scratch space for point addition denominators and their calculation
18 std::vector<Fq> scratch_space_vector(points.size());
19 std::span<Fq> scratch_space(scratch_space_vector);
20
21 // Divide the work into groups of addition sequences to be reduced by each thread
22 auto [addition_sequences_, sequence_tags] = construct_thread_data(points, sequence_counts, scratch_space);
23 auto& addition_sequences = addition_sequences_;
24
25 const size_t num_threads = addition_sequences.size();
26 parallel_for(num_threads, [&](size_t thread_idx) { batched_affine_add_in_place(addition_sequences[thread_idx]); });
27
28 // Construct a vector of the reduced points, accounting for sequences that may have been split across threads
29 std::vector<G1> reduced_points;
30 reduced_points.reserve(sequence_counts.size());
31 size_t prev_tag = std::numeric_limits<size_t>::max();
32 for (auto [sequences, tags] : zip_view(addition_sequences, sequence_tags)) {
33 // Extract the first num-sequence-counts many points from each add sequence
34 for (size_t i = 0; i < sequences.sequence_counts.size(); ++i) {
35 if (tags[i] == prev_tag) {
36 reduced_points.back() = reduced_points.back() + sequences.points[i];
37 } else {
38 reduced_points.emplace_back(sequences.points[i]);
39 }
40 prev_tag = tags[i];
41 }
42 }
43
44 return reduced_points;
45}
46
47template <typename Curve>
49 const std::span<G1>& points, const std::vector<size_t>& sequence_counts, const std::span<Fq>& scratch_space)
50{
51 // Compute the endpoints of the sequences within the points array from the sequence counts
52 std::vector<size_t> sequence_endpoints;
53 sequence_endpoints.reserve(sequence_counts.size());
54 size_t total_count = 0;
55 for (const auto& count : sequence_counts) {
56 total_count += count;
57 sequence_endpoints.emplace_back(total_count);
58 }
59
60 if (points.size() != total_count) {
61 throw_or_abort("Number of input points does not match sequence counts!");
62 }
63
64 // Determine the optimal number of threads for parallelization
65 const size_t MIN_POINTS_PER_THREAD = 1 << 14; // heuristic; anecdotally optimal for practical cases
66 const size_t total_num_points = points.size();
67 const size_t optimal_threads = total_num_points / MIN_POINTS_PER_THREAD;
68 const size_t num_threads = std::max(size_t{ 1 }, std::min(get_num_cpus(), optimal_threads));
69 // Distribute the work as evenly as possible across threads
70 const size_t base_thread_size = total_num_points / num_threads;
71 const size_t leftover_size = total_num_points % num_threads;
72 std::vector<size_t> thread_sizes(num_threads, base_thread_size);
73 for (size_t i = 0; i < leftover_size; ++i) {
74 thread_sizes[i]++;
75 }
76
77 // Construct the point spans for each thread according to the distribution determined above
78 std::vector<std::span<G1>> thread_points;
79 thread_points.reserve(num_threads);
80 std::vector<std::span<Fq>> thread_scratch_space;
81 thread_scratch_space.reserve(num_threads);
82 std::vector<size_t> thread_endpoints;
83 thread_endpoints.reserve(num_threads);
84 size_t point_index = 0;
85 for (auto size : thread_sizes) {
86 thread_points.push_back(points.subspan(point_index, size));
87 thread_scratch_space.push_back(scratch_space.subspan(point_index, size));
88 point_index += size;
89 thread_endpoints.emplace_back(point_index);
90 }
91
92 // Construct the union of the thread and sequence endpoints by combining, sorting, then removing duplicates. This is
93 // used to break the points into sequences for each thread while tracking tags so that sequences split across one of
94 // more threads can be properly reconstructed.
95 std::vector<size_t> all_endpoints;
96 all_endpoints.reserve(thread_endpoints.size() + sequence_endpoints.size());
97 all_endpoints.insert(all_endpoints.end(), thread_endpoints.begin(), thread_endpoints.end());
98 all_endpoints.insert(all_endpoints.end(), sequence_endpoints.begin(), sequence_endpoints.end());
99 std::sort(all_endpoints.begin(), all_endpoints.end());
100 auto last = std::unique(all_endpoints.begin(), all_endpoints.end());
101 all_endpoints.erase(last, all_endpoints.end());
102
103 // Construct sequence counts and tags for each thread using the set of all thread and sequence endpoints
104 size_t prev_endpoint = 0;
105 size_t thread_idx = 0;
106 size_t sequence_idx = 0;
107 std::vector<std::vector<size_t>> thread_sequence_counts(num_threads);
108 std::vector<std::vector<size_t>> thread_sequence_tags(num_threads);
109 for (auto& endpoint : all_endpoints) {
110 size_t chunk_size = endpoint - prev_endpoint;
111 thread_sequence_counts[thread_idx].emplace_back(chunk_size);
112 thread_sequence_tags[thread_idx].emplace_back(sequence_idx);
113 if (endpoint == thread_endpoints[thread_idx]) {
114 thread_idx++;
115 }
116 if (endpoint == sequence_endpoints[sequence_idx]) {
117 sequence_idx++;
118 }
119 prev_endpoint = endpoint;
120 }
121
122 if (thread_sequence_counts.size() != thread_points.size()) {
123 throw_or_abort("Mismatch in sequence count construction!");
124 }
125
126 // Construct the addition sequences for each thread
127 std::vector<AdditionSequences> addition_sequences;
128 addition_sequences.reserve(num_threads);
129 for (size_t i = 0; i < num_threads; ++i) {
130 addition_sequences.push_back(
131 AdditionSequences{ std::move(thread_sequence_counts[i]), thread_points[i], thread_scratch_space[i] });
132 }
133
134 return { std::move(addition_sequences), std::move(thread_sequence_tags) };
135}
136
137template <typename Curve>
139 Curve>::batch_compute_point_addition_slope_inverses(const AdditionSequences& add_sequences)
140{
141 auto points = add_sequences.points;
142 const auto& sequence_counts = add_sequences.sequence_counts;
143
144 // Count the total number of point pairs to be added across all addition sequences
145 size_t total_num_pairs{ 0 };
146 for (auto& count : sequence_counts) {
147 total_num_pairs += count >> 1;
148 }
149
150 // Define scratch space for batched inverse computations and eventual storage of denominators
151 BB_ASSERT_GTE(add_sequences.scratch_space.size(), 2 * total_num_pairs);
152 std::span<Fq> denominators = add_sequences.scratch_space.subspan(0, total_num_pairs);
153 std::span<Fq> differences = add_sequences.scratch_space.subspan(total_num_pairs, total_num_pairs);
154
155 // Compute and store successive products of differences (x_2 - x_1)
156 Fq accumulator = 1;
157 size_t point_idx = 0;
158 size_t pair_idx = 0;
159 for (auto& count : sequence_counts) {
160 const auto num_pairs = count >> 1;
161 for (size_t j = 0; j < num_pairs; ++j) {
162 BB_ASSERT_LT(pair_idx, total_num_pairs);
163 const auto& x1 = points[point_idx++].x;
164 const auto& x2 = points[point_idx++].x;
165
166 // It is assumed that the input points are random and thus w/h/p do not share an x-coordinate
167 BB_ASSERT(x1 != x2);
168
169 auto diff = x2 - x1;
170 differences[pair_idx] = diff;
171
172 // Store and update the running product of differences at each stage
173 denominators[pair_idx++] = accumulator;
174 accumulator *= diff;
175 }
176 // If number of points in the sequence is odd, we skip the last one since it has no pair
177 point_idx += (count & 0x01ULL);
178 }
179
180 // Invert the full product of differences
181 Fq inverse = accumulator.invert();
182
183 // Compute the individual point-pair addition denominators 1/(x2 - x1)
184 for (size_t i = 0; i < total_num_pairs; ++i) {
185 size_t idx = total_num_pairs - 1 - i;
186 denominators[idx] *= inverse;
187 inverse *= differences[idx];
188 }
189
190 return denominators;
191}
192
193template <typename Curve>
195{
196 const size_t num_points = add_sequences.points.size();
197 if (num_points == 0 || num_points == 1) { // nothing to do
198 return;
199 }
200
201 // Batch compute terms of the form 1/(x2 -x1) for each pair to be added in this round
202 std::span<Fq> denominators = batch_compute_point_addition_slope_inverses(add_sequences);
203
204 auto points = add_sequences.points;
205 auto& sequence_counts = add_sequences.sequence_counts;
206
207 // Compute pairwise in-place additions for all sequences with more than 1 point
208 size_t point_idx = 0; // index for points to be summed
209 size_t result_point_idx = 0; // index for result points
210 size_t pair_idx = 0; // index into array of denominators for each pair
211 bool more_additions = false;
212 for (auto& count : sequence_counts) {
213 const auto num_pairs = count >> 1;
214 const bool overflow = static_cast<bool>(count & 0x01ULL);
215 // Compute the sum of all pairs in the sequence and store the result in the same points array
216 for (size_t j = 0; j < num_pairs; ++j) {
217 const auto& point_1 = points[point_idx++]; // first summand
218 const auto& point_2 = points[point_idx++]; // second summand
219 const auto& denominator = denominators[pair_idx++]; // denominator needed in add formula
220 auto& result = points[result_point_idx++]; // target for addition result
221
222 result = affine_add_with_denominator(point_1, point_2, denominator);
223 }
224 // If the sequence had an odd number of points, simply carry the unpaired point over to the next round
225 if (overflow) {
226 points[result_point_idx++] = points[point_idx++];
227 }
228
229 // Update the sequence counts in place for the next round
230 const uint32_t updated_sequence_count = static_cast<uint32_t>(num_pairs) + static_cast<uint32_t>(overflow);
231 count = updated_sequence_count;
232
233 // More additions are required if any sequence has not yet been reduced to a single point
234 more_additions = more_additions || updated_sequence_count > 1;
235 }
236
237 // Recursively perform pairwise additions until all sequences have been reduced to a single point
238 if (more_additions) {
239 const size_t updated_point_count = result_point_idx;
240 std::span<G1> updated_points(&points[0], updated_point_count);
241 return batched_affine_add_in_place(
242 AdditionSequences{ std::move(sequence_counts), updated_points, add_sequences.scratch_space });
243 }
244}
245
248} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_GTE(left, right,...)
Definition assert.hpp:128
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
Class for handling fast batched affine addition of large sets of EC points.
static std::vector< G1 > add_in_place(const std::span< G1 > &points, const std::vector< size_t > &sequence_counts)
Given a set of points and sequence counts, peform addition to reduce each sequence to a single point.
static void batched_affine_add_in_place(AdditionSequences add_sequences)
Internal method for in-place summation of a single set of addition sequences.
static ThreadData construct_thread_data(const std::span< G1 > &points, const std::vector< size_t > &sequence_counts, const std::span< Fq > &scratch_space)
Construct the set of AdditionSequences to be handled by each thread.
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
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
void throw_or_abort(std::string const &err)
VectorField result