Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
poly_inplace_arith.bench.cpp
Go to the documentation of this file.
1// Diagnostic benchmarks for Polynomial in-place arithmetic: operator+=,
2// operator-=, and operator*=. Each operator is benched two ways:
3//
4// _scalar — raw scalar for-loop, no vectorized_for, no parallel_for.
5// _full — Polynomial::operator(+/-/*)=, which now routes through
6// the *_chunk helper + parallel_for_heuristic + vectorized_for<5>.
7//
8// The _full / _scalar ratio is the production speedup from the SIMD path.
9// On V8/Zen3 the ratio is meaningful; on wasmtime SIMD lowering is incomplete
10// and the ratios understate the win.
11
14
15#include <benchmark/benchmark.h>
16#include <cstring>
17
18using namespace benchmark;
19using bb::fr;
20using bb::Polynomial;
22
23constexpr size_t N = 1 << 16;
24
25namespace {
26
27// Pre-populate once: self_ref + other hold random data. Each bench iteration
28// resets self to self_ref so accumulating values don't drift across iters.
29struct PolyFixture {
30 Polynomial<fr> self;
31 Polynomial<fr> self_ref;
32 Polynomial<fr> other;
33 fr scaling;
34
35 PolyFixture()
39 , scaling(fr::random_element())
40 {
41 for (size_t i = 0; i < N; ++i) {
42 self_ref.at(i) = fr::random_element();
43 other.at(i) = fr::random_element();
44 }
45 std::memcpy(self.data(), self_ref.data(), N * sizeof(fr));
46 }
47
48 void reset_self(State& state)
49 {
50 state.PauseTiming();
51 std::memcpy(self.data(), self_ref.data(), N * sizeof(fr));
52 state.ResumeTiming();
53 }
54};
55
56// Correctness check (NOT timed): scalar reference vs Polynomial::operator
57// for each of +=, -=, *= on identical inputs. Aborts before bench start
58// if any output mismatches.
59struct CorrectnessGuard {
60 CorrectnessGuard()
61 {
62 constexpr size_t M = N;
66 for (size_t i = 0; i < M; ++i) {
67 a.at(i) = fr::random_element();
68 b.at(i) = fr::random_element();
69 }
70 const fr s = fr::random_element();
71
72 auto check = [&](const char* label) {
73 for (size_t i = 0; i < M; ++i) {
74 if (!(a.at(i) == a_ref.at(i))) {
75 std::fprintf(stderr, "[POLY_INPLACE %s] mismatch at i=%zu\n", label, i);
76 std::abort();
77 }
78 }
79 };
80
81 // operator+=
82 std::memcpy(a_ref.data(), a.data(), M * sizeof(fr));
83 a += PolynomialSpan<const fr>{ 0, { b.data(), M } };
84 for (size_t i = 0; i < M; ++i) {
85 a_ref.at(i) = a_ref.at(i) + b.at(i);
86 }
87 check("+=");
88
89 // operator-=
90 std::memcpy(a_ref.data(), a.data(), M * sizeof(fr));
91 a -= PolynomialSpan<const fr>{ 0, { b.data(), M } };
92 for (size_t i = 0; i < M; ++i) {
93 a_ref.at(i) = a_ref.at(i) - b.at(i);
94 }
95 check("-=");
96
97 // operator*=
98 std::memcpy(a_ref.data(), a.data(), M * sizeof(fr));
99 a *= s;
100 for (size_t i = 0; i < M; ++i) {
101 a_ref.at(i) = a_ref.at(i) * s;
102 }
103 check("*=");
104 }
105};
106static const CorrectnessGuard correctness_guard;
107
108} // namespace
109
110// =========================== operator+= ===========================
111
112static void bench_plus_equals_scalar(State& state)
113{
114 PolyFixture f;
115 auto& self = f.self;
116 auto& other = f.other;
117 for (auto _ : state) {
118 f.reset_self(state);
119 for (size_t i = 0; i < N; ++i) {
120 self.at(i) = self.at(i) + other.at(i);
121 }
122 DoNotOptimize(self.at(0));
123 }
124}
125BENCHMARK(bench_plus_equals_scalar);
126
127static void bench_plus_equals_full(State& state)
128{
129 PolyFixture f;
130 auto& self = f.self;
131 auto& other = f.other;
132 for (auto _ : state) {
133 f.reset_self(state);
134 self += PolynomialSpan<const fr>{ 0, { other.data(), N } };
135 DoNotOptimize(self.at(0));
136 }
137}
138BENCHMARK(bench_plus_equals_full);
139
140// =========================== operator-= ===========================
141
142static void bench_minus_equals_scalar(State& state)
143{
144 PolyFixture f;
145 auto& self = f.self;
146 auto& other = f.other;
147 for (auto _ : state) {
148 f.reset_self(state);
149 for (size_t i = 0; i < N; ++i) {
150 self.at(i) = self.at(i) - other.at(i);
151 }
152 DoNotOptimize(self.at(0));
153 }
154}
155BENCHMARK(bench_minus_equals_scalar);
156
157static void bench_minus_equals_full(State& state)
158{
159 PolyFixture f;
160 auto& self = f.self;
161 auto& other = f.other;
162 for (auto _ : state) {
163 f.reset_self(state);
164 self -= PolynomialSpan<const fr>{ 0, { other.data(), N } };
165 DoNotOptimize(self.at(0));
166 }
167}
168BENCHMARK(bench_minus_equals_full);
169
170// =========================== operator*= ===========================
171
172static void bench_times_equals_scalar(State& state)
173{
174 PolyFixture f;
175 auto& self = f.self;
176 auto scaling = f.scaling;
177 for (auto _ : state) {
178 f.reset_self(state);
179 for (size_t i = 0; i < N; ++i) {
180 self.at(i) = self.at(i) * scaling;
181 }
182 DoNotOptimize(self.at(0));
183 }
184}
185BENCHMARK(bench_times_equals_scalar);
186
187static void bench_times_equals_full(State& state)
188{
189 PolyFixture f;
190 auto& self = f.self;
191 auto scaling = f.scaling;
192 for (auto _ : state) {
193 f.reset_self(state);
194 self *= scaling;
195 DoNotOptimize(self.at(0));
196 }
197}
198BENCHMARK(bench_times_equals_full);
199
constexpr size_t N
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
FF a
FF b
std::string label
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
BENCHMARK(bench_plus_equals_scalar)
BENCHMARK_MAIN()
constexpr size_t N
static field random_element(numeric::RNG *engine=nullptr) noexcept