Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
thread.cpp
Go to the documentation of this file.
1#include "thread.hpp"
2#include "log.hpp"
3#include "throw_or_abort.hpp"
5#include <cstdlib>
6#include <string>
7#include <utility>
8
9#ifndef NO_MULTITHREADING
10#include <thread>
11
12namespace {
13uint32_t& get_num_cores_ref()
14{
15 static thread_local const char* val = std::getenv("HARDWARE_CONCURRENCY");
16 static thread_local uint32_t cores =
17 val != nullptr ? static_cast<uint32_t>(std::stoul(val)) : std::min(32U, env_hardware_concurrency());
18 return cores;
19}
20} // namespace
21#endif
22
23namespace bb {
24void set_parallel_for_concurrency([[maybe_unused]] size_t num_cores)
25{
26#ifdef NO_MULTITHREADING
27 throw_or_abort("Cannot set hardware concurrency when multithreading is disabled.");
28#else
29 // This is already thread-local, so setting it affects only the current thread
30 get_num_cores_ref() = static_cast<uint32_t>(num_cores);
31#endif
32}
33
35{
36#ifdef NO_MULTITHREADING
37 return 1;
38#else
39 return static_cast<size_t>(get_num_cores_ref());
40#endif
41}
42} // namespace bb
43
79namespace bb {
80// 64 core aws r5.
81// pippenger run: pippenger_bench/1048576
82// coset_fft run: coset_fft_bench_parallel/4194304
83// proof run: 2m gate ultraplonk. average of 5.
84
85// pippenger: 179ms
86// coset_fft: 54776us
87// proof: 11.33s
88void parallel_for_omp(size_t num_iterations, const std::function<void(size_t)>& func);
89
90// pippenger: 163ms
91// coset_fft: 59993us
92// proof: 11.11s
93void parallel_for_moody(size_t num_iterations, const std::function<void(size_t)>& func);
94
95// pippenger: 154ms
96// coset_fft: 92997us
97// proof: 10.84s
98void parallel_for_spawning(size_t num_iterations, const std::function<void(size_t)>& func);
99
100// pippenger: 178ms
101// coset_fft: 70207us
102// proof: 11.55s
103void parallel_for_queued(size_t num_iterations, const std::function<void(size_t)>& func);
104
105// pippenger: 152ms
106// coset_fft: 56658us
107// proof: 11.28s
108void parallel_for_atomic_pool(size_t num_iterations, const std::function<void(size_t)>& func);
109
110void parallel_for_mutex_pool(size_t num_iterations, const std::function<void(size_t)>& func);
111
112void parallel_for(size_t num_iterations, const std::function<void(size_t)>& func)
113{
114#ifdef NO_MULTITHREADING
115 for (size_t i = 0; i < num_iterations; ++i) {
116 func(i);
117 }
118#else
119#ifdef OMP_MULTITHREADING
120 parallel_for_omp(num_iterations, func);
121#else
122 // parallel_for_spawning(num_iterations, func);
123 // parallel_for_moody(num_iterations, func);
124 // parallel_for_atomic_pool(num_iterations, func);
125 parallel_for_mutex_pool(num_iterations, func);
126 // parallel_for_queued(num_iterations, func);
127#endif
128#endif
129}
130
142void parallel_for_range(size_t num_points,
143 const std::function<void(size_t, size_t)>& func,
144 size_t no_multhreading_if_less_or_equal)
145{
146 if (num_points <= no_multhreading_if_less_or_equal) {
147 func(0, num_points);
148 return;
149 }
150 // Get number of cpus we can split into
151 const size_t num_cpus = get_num_cpus();
152
153 // Compute the size of a single chunk
154 const size_t chunk_size = (num_points / num_cpus) + (num_points % num_cpus == 0 ? 0 : 1);
155 // Parallelize over chunks
156 parallel_for(num_cpus, [num_points, chunk_size, &func](size_t chunk_index) {
157 // If num_points is small, sometimes we need fewer CPUs
158 if (chunk_size * chunk_index > num_points) {
159 return;
160 }
161 // Compute the current chunk size (can differ in case it's the last chunk)
162 size_t current_chunk_size = std::min(num_points - (chunk_size * chunk_index), chunk_size);
163 if (current_chunk_size == 0) {
164 return;
165 }
166 size_t start = chunk_index * chunk_size;
167 size_t end = chunk_index * chunk_size + current_chunk_size;
168 func(start, end);
169 });
170};
171
172void parallel_for_heuristic(size_t num_points,
173 const std::function<void(size_t, size_t, size_t)>& func,
174 size_t heuristic_cost)
175{
176 using namespace thread_heuristics;
177 // Get number of cpus we can split into
178 const size_t num_cpus = get_num_cpus();
179
180 // Compute the size of a single chunk
181 const size_t chunk_size = (num_points / num_cpus) + (num_points % num_cpus == 0 ? 0 : 1);
182
183 // Compute the cost of all operations done by other threads
184 const size_t offset_cost = (num_points - chunk_size) * heuristic_cost;
185
186 // If starting parallel for is longer than computing, just compute
187 if (offset_cost < PARALLEL_FOR_COST) {
188 func(0, num_points, 0);
189 return;
190 }
191 // Parallelize over chunks
192 parallel_for(num_cpus, [num_points, chunk_size, &func](size_t chunk_index) {
193 // If num_points is small, sometimes we need fewer CPUs
194 if ((chunk_size * chunk_index) > num_points) {
195 return;
196 }
197 // Compute the current chunk size (can differ in case it's the last chunk)
198 const size_t current_chunk_size = std::min(num_points - (chunk_size * chunk_index), chunk_size);
199 if (current_chunk_size == 0) {
200 return;
201 }
202 const size_t start = chunk_index * chunk_size;
203 const size_t end = start + current_chunk_size;
204 func(start, end, chunk_index);
205 });
206};
207
208MultithreadData calculate_thread_data(size_t num_iterations, size_t min_iterations_per_thread)
209{
210 size_t num_threads = calculate_num_threads(num_iterations, min_iterations_per_thread);
211 const size_t thread_size = num_iterations / num_threads;
212
213 // Cumpute the index bounds for each thread
214 std::vector<size_t> start(num_threads);
215 std::vector<size_t> end(num_threads);
216 for (size_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
217 start[thread_idx] = thread_idx * thread_size;
218 end[thread_idx] = (thread_idx == num_threads - 1) ? num_iterations : (thread_idx + 1) * thread_size;
219 }
220
221 return MultithreadData{ num_threads, std::move(start), std::move(end) };
222}
223
233size_t calculate_num_threads(size_t num_iterations, size_t min_iterations_per_thread)
234{
235 size_t max_num_threads = get_num_cpus(); // number of available threads
236 size_t desired_num_threads = num_iterations / min_iterations_per_thread;
237 size_t num_threads = std::min(desired_num_threads, max_num_threads); // fewer than max if justified
238 num_threads = num_threads > 0 ? num_threads : 1; // ensure num_threads is at least 1
239 return num_threads;
240}
241
242} // namespace bb
WASM_EXPORT uint32_t env_hardware_concurrency()
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
void parallel_for_mutex_pool(size_t num_iterations, const std::function< void(size_t)> &func)
MultithreadData calculate_thread_data(size_t num_iterations, size_t min_iterations_per_thread)
Calculates number of threads and index bounds for each thread.
Definition thread.cpp:208
void parallel_for_queued(size_t num_iterations, const std::function< void(size_t)> &func)
size_t get_num_cpus()
Definition thread.cpp:34
void parallel_for_moody(size_t num_iterations, const std::function< void(size_t)> &func)
size_t calculate_num_threads(size_t num_iterations, size_t min_iterations_per_thread)
calculates number of threads to create based on minimum iterations per thread
Definition thread.cpp:233
void parallel_for_atomic_pool(size_t num_iterations, const std::function< void(size_t)> &func)
void parallel_for_heuristic(size_t num_points, const std::function< void(size_t, size_t, size_t)> &func, size_t heuristic_cost)
Split a loop into several loops running in parallel based on operations in 1 iteration.
Definition thread.cpp:172
void parallel_for_spawning(size_t num_iterations, const std::function< void(size_t)> &func)
void set_parallel_for_concurrency(size_t num_cores)
Definition thread.cpp:24
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:112
void parallel_for_omp(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.
Definition thread.cpp:142
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
void throw_or_abort(std::string const &err)