From 350f82615fe8aaece23f0f19adb618397af6e6f9 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sun, 6 Dec 2020 16:35:18 -0600 Subject: [PATCH 01/11] Add benchmark for dense polynomial evaluate, rename fft bench --- poly-benches/Cargo.toml | 11 ++-- poly-benches/benches/dense_polynomial.rs | 54 +++++++++++++++++++ .../benches/{fft.rs => groth16_fft.rs} | 0 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 poly-benches/benches/dense_polynomial.rs rename poly-benches/benches/{fft.rs => groth16_fft.rs} (100%) diff --git a/poly-benches/Cargo.toml b/poly-benches/Cargo.toml index 1ba153e5e..fc804280f 100644 --- a/poly-benches/Cargo.toml +++ b/poly-benches/Cargo.toml @@ -11,11 +11,16 @@ publish = false [dev-dependencies] ark-poly = { path = "../poly" } ark-ff = { path = "../ff" } -ark-test-curves = { path = "../test-curves", default-features = false, features = [ "bls12_381_scalar_field" ] } +ark-test-curves = { path = "../test-curves", default-features = false, features = [ "bls12_381_scalar_field", "mnt4_753_curve" ] } criterion = "0.3.1" rand = "0.7" [[bench]] -name = "fft" -path = "benches/fft.rs" +name = "groth16_fft" +path = "benches/groth16_fft.rs" harness = false + +[[bench]] +name = "dense_polynomial" +path = "benches/dense_polynomial.rs" +harness = false \ No newline at end of file diff --git a/poly-benches/benches/dense_polynomial.rs b/poly-benches/benches/dense_polynomial.rs new file mode 100644 index 000000000..feff6446f --- /dev/null +++ b/poly-benches/benches/dense_polynomial.rs @@ -0,0 +1,54 @@ +use rand; + +extern crate criterion; + +use ark_ff::{Field}; +use ark_poly::{polynomial::univariate::DensePolynomial, UVPolynomial, Polynomial}; +use ark_test_curves::bls12_381::{Fr as bls12_381_fr}; +use criterion::BenchmarkId; +use criterion::Criterion; +use criterion::{criterion_group, criterion_main}; + +const POLY_LOG_MIN_SIZE : usize = 15; +const POLY_EVALUATE_MAX_DEGREE : usize = 1 << 17; + +// returns vec![2^{POLY_LOG_MIN_SIZE}, ... 2^n], where n = ceil(log_2(max_degree)) +fn size_range(max_degree: usize) -> Vec { + let mut to_ret = vec![1 << POLY_LOG_MIN_SIZE]; + while *to_ret.last().unwrap() < max_degree + { + to_ret.push(to_ret.last().unwrap() * 2); + } + to_ret +} + +fn bench_poly_evaluate( + c: &mut Criterion, + name: &'static str, +) { + let mut group = c.benchmark_group(format!("{:?} - evaluate_polynomial", name)); + for degree in size_range(POLY_EVALUATE_MAX_DEGREE).iter() { + group.bench_with_input(BenchmarkId::from_parameter(degree), degree, |b, °ree| { + // Per benchmark setup + let mut rng = &mut rand::thread_rng(); + let poly = DensePolynomial::::rand(degree, &mut rng); + b.iter(|| { + // Per benchmark iteration + let pt = F::rand(&mut rng); + poly.evaluate(&pt); + }); + }); + } + group.finish(); +} + +fn bench_bls12_381( + c: &mut Criterion +) +{ + let name = "bls12_381"; + bench_poly_evaluate::(c, name); +} + +criterion_group!(benches, bench_bls12_381); +criterion_main!(benches); diff --git a/poly-benches/benches/fft.rs b/poly-benches/benches/groth16_fft.rs similarity index 100% rename from poly-benches/benches/fft.rs rename to poly-benches/benches/groth16_fft.rs From 9fa6843e924c7154a180a0a91f3e0b205def517a Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sun, 6 Dec 2020 16:37:15 -0600 Subject: [PATCH 02/11] Use horners method for polynomial evaluation --- poly/src/polynomial/univariate/dense.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/poly/src/polynomial/univariate/dense.rs b/poly/src/polynomial/univariate/dense.rs index d807b6b94..9ce9014e7 100644 --- a/poly/src/polynomial/univariate/dense.rs +++ b/poly/src/polynomial/univariate/dense.rs @@ -12,9 +12,6 @@ use ark_std::{ use ark_ff::{FftField, Field, Zero}; use rand::Rng; -#[cfg(feature = "parallel")] -use rayon::prelude::*; - /// Stores a polynomial in coefficient form. #[derive(Clone, PartialEq, Eq, Hash, Default, CanonicalSerialize, CanonicalDeserialize)] pub struct DensePolynomial { @@ -39,18 +36,16 @@ impl Polynomial for DensePolynomial { fn evaluate(&self, point: &F) -> F { if self.is_zero() { return F::zero(); + } else if point.is_zero() { + return self.coeffs[0]; } - let mut powers_of_point = vec![F::one()]; - let mut cur = *point; - for _ in 0..self.degree() { - powers_of_point.push(cur); - cur *= point; + // Horners method + let mut result = F::zero(); + for i in (0..self.degree()).rev() { + result *= point; + result += self.coeffs[i]; } - assert_eq!(powers_of_point.len(), self.coeffs.len()); - ark_std::cfg_into_iter!(powers_of_point) - .zip(&self.coeffs) - .map(|(power, coeff)| power * coeff) - .sum() + result } } From e6d4b710d6af2d15a90cd1f2318abc553a2c4cd9 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sun, 6 Dec 2020 16:59:09 -0600 Subject: [PATCH 03/11] fix bug --- poly/src/polynomial/univariate/dense.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/poly/src/polynomial/univariate/dense.rs b/poly/src/polynomial/univariate/dense.rs index 9ce9014e7..2b7a6bff6 100644 --- a/poly/src/polynomial/univariate/dense.rs +++ b/poly/src/polynomial/univariate/dense.rs @@ -41,7 +41,8 @@ impl Polynomial for DensePolynomial { } // Horners method let mut result = F::zero(); - for i in (0..self.degree()).rev() { + let num_coeffs = self.degree() + 1; + for i in (0..num_coeffs).rev() { result *= point; result += self.coeffs[i]; } From 60db7d5fcbaf363701dd71961bba7be85d589441 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sun, 6 Dec 2020 17:01:49 -0600 Subject: [PATCH 04/11] Fix lint --- poly-benches/benches/dense_polynomial.rs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/poly-benches/benches/dense_polynomial.rs b/poly-benches/benches/dense_polynomial.rs index feff6446f..c819864a0 100644 --- a/poly-benches/benches/dense_polynomial.rs +++ b/poly-benches/benches/dense_polynomial.rs @@ -2,30 +2,26 @@ use rand; extern crate criterion; -use ark_ff::{Field}; -use ark_poly::{polynomial::univariate::DensePolynomial, UVPolynomial, Polynomial}; -use ark_test_curves::bls12_381::{Fr as bls12_381_fr}; +use ark_ff::Field; +use ark_poly::{polynomial::univariate::DensePolynomial, Polynomial, UVPolynomial}; +use ark_test_curves::bls12_381::Fr as bls12_381_fr; use criterion::BenchmarkId; use criterion::Criterion; use criterion::{criterion_group, criterion_main}; -const POLY_LOG_MIN_SIZE : usize = 15; -const POLY_EVALUATE_MAX_DEGREE : usize = 1 << 17; +const POLY_LOG_MIN_SIZE: usize = 15; +const POLY_EVALUATE_MAX_DEGREE: usize = 1 << 17; // returns vec![2^{POLY_LOG_MIN_SIZE}, ... 2^n], where n = ceil(log_2(max_degree)) fn size_range(max_degree: usize) -> Vec { let mut to_ret = vec![1 << POLY_LOG_MIN_SIZE]; - while *to_ret.last().unwrap() < max_degree - { + while *to_ret.last().unwrap() < max_degree { to_ret.push(to_ret.last().unwrap() * 2); } to_ret } -fn bench_poly_evaluate( - c: &mut Criterion, - name: &'static str, -) { +fn bench_poly_evaluate(c: &mut Criterion, name: &'static str) { let mut group = c.benchmark_group(format!("{:?} - evaluate_polynomial", name)); for degree in size_range(POLY_EVALUATE_MAX_DEGREE).iter() { group.bench_with_input(BenchmarkId::from_parameter(degree), degree, |b, °ree| { @@ -42,10 +38,7 @@ fn bench_poly_evaluate( group.finish(); } -fn bench_bls12_381( - c: &mut Criterion -) -{ +fn bench_bls12_381(c: &mut Criterion) { let name = "bls12_381"; bench_poly_evaluate::(c, name); } From 5dea19cbd410dcd845d775b3c1b20ed109ac2453 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sun, 6 Dec 2020 17:03:15 -0600 Subject: [PATCH 05/11] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e58ddcf..e2e3db777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - #100 (ark-ff) Implement `batch_inverse_and_mul` - #101 (ark-ff) Add `element(i: usize)` on the `Domain` trait. - #107 (ark-serialize) Add an impl of `CanonicalSerialize/Deserialize` for `BTreeSet`. +- #114 (ark-poly) Significantly speedup and reduce memory usage of `DensePolynomial.evaluate`. ### Bug fixes - #36 (ark-ec) In Short-Weierstrass curves, include an infinity bit in `ToConstraintField`. From 04588557f6cc7f5473ef7bd2644a47f676b8a9c0 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sun, 6 Dec 2020 18:32:10 -0600 Subject: [PATCH 06/11] Add parallel horners method support --- poly-benches/Cargo.toml | 7 +++- poly/src/polynomial/univariate/dense.rs | 51 ++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/poly-benches/Cargo.toml b/poly-benches/Cargo.toml index fc804280f..1aa1197a1 100644 --- a/poly-benches/Cargo.toml +++ b/poly-benches/Cargo.toml @@ -8,12 +8,17 @@ license = "MIT/Apache-2.0" edition = "2018" publish = false -[dev-dependencies] +[dependencies] ark-poly = { path = "../poly" } ark-ff = { path = "../ff" } ark-test-curves = { path = "../test-curves", default-features = false, features = [ "bls12_381_scalar_field", "mnt4_753_curve" ] } criterion = "0.3.1" rand = "0.7" +rayon = { version = "1", optional = true } + +[features] +default = [] +parallel = ["ark-ff/parallel", "rayon", "ark-poly/parallel" ] [[bench]] name = "groth16_fft" diff --git a/poly/src/polynomial/univariate/dense.rs b/poly/src/polynomial/univariate/dense.rs index 2b7a6bff6..ef0558cee 100644 --- a/poly/src/polynomial/univariate/dense.rs +++ b/poly/src/polynomial/univariate/dense.rs @@ -4,6 +4,7 @@ use crate::{EvaluationDomain, Evaluations, GeneralEvaluationDomain}; use crate::{Polynomial, UVPolynomial}; use ark_serialize::*; use ark_std::{ + cmp::{max, min}, fmt, ops::{Add, AddAssign, Deref, DerefMut, Div, Mul, Neg, Sub, SubAssign}, vec::Vec, @@ -12,6 +13,9 @@ use ark_std::{ use ark_ff::{FftField, Field, Zero}; use rand::Rng; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + /// Stores a polynomial in coefficient form. #[derive(Clone, PartialEq, Eq, Hash, Default, CanonicalSerialize, CanonicalDeserialize)] pub struct DensePolynomial { @@ -39,15 +43,58 @@ impl Polynomial for DensePolynomial { } else if point.is_zero() { return self.coeffs[0]; } - // Horners method + self.internal_evaluate(point) + } +} + +#[cfg(feature = "parallel")] +// Set some minimum number of field elements to be worked on per thread +// to avoid per-thread costs dominating parallel execution time. +const MIN_ELEMENTS_PER_THREAD: usize = 16; + +impl DensePolynomial { + #[cfg(not(feature = "parallel"))] + fn internal_evaluate(&self, point: &F) -> F + { + // Horners method - serial method let mut result = F::zero(); - let num_coeffs = self.degree() + 1; + let num_coeffs = self.coeffs.len(); for i in (0..num_coeffs).rev() { result *= point; result += self.coeffs[i]; } result } + + #[cfg(feature = "parallel")] + fn internal_evaluate(&self, point: &F) -> F + { + // Horners method - parallel method + // compute the number of threads we will be using. + let num_cpus_available = rayon::current_num_threads(); + let num_coeffs = self.coeffs.len(); + let num_elem_per_thread = max(num_coeffs / num_cpus_available, MIN_ELEMENTS_PER_THREAD); + // compute num_cpus_used as ceil(num_coeffs / num_elem_per_thread) + let num_cpus_used = (num_coeffs + num_elem_per_thread - 1) / num_elem_per_thread; + + // run Horners method on each thread + let mut per_thread_res = vec![F::zero(); num_cpus_used]; + let result = per_thread_res.par_iter_mut() + .enumerate() + .map(|(i, _)| + { + let mut thread_result = F::zero(); + let iter_start_index = i * num_elem_per_thread; + let iter_end_index = min((i+1) * num_elem_per_thread, num_coeffs); + for i in (iter_start_index..iter_end_index).rev() { + thread_result *= point; + thread_result += self.coeffs[i]; + } + thread_result *= point.pow(&[iter_start_index as u64]); + thread_result + }).sum(); + result + } } impl UVPolynomial for DensePolynomial { From 8b74f1c695b3dfd62f6ab10890becc2fe5ac6ae9 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sun, 6 Dec 2020 18:42:04 -0600 Subject: [PATCH 07/11] Fix lint, add some more comments --- poly/src/polynomial/univariate/dense.rs | 39 +++++++++++++------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/poly/src/polynomial/univariate/dense.rs b/poly/src/polynomial/univariate/dense.rs index ef0558cee..d24aa5981 100644 --- a/poly/src/polynomial/univariate/dense.rs +++ b/poly/src/polynomial/univariate/dense.rs @@ -54,8 +54,7 @@ const MIN_ELEMENTS_PER_THREAD: usize = 16; impl DensePolynomial { #[cfg(not(feature = "parallel"))] - fn internal_evaluate(&self, point: &F) -> F - { + fn internal_evaluate(&self, point: &F) -> F { // Horners method - serial method let mut result = F::zero(); let num_coeffs = self.coeffs.len(); @@ -67,8 +66,7 @@ impl DensePolynomial { } #[cfg(feature = "parallel")] - fn internal_evaluate(&self, point: &F) -> F - { + fn internal_evaluate(&self, point: &F) -> F { // Horners method - parallel method // compute the number of threads we will be using. let num_cpus_available = rayon::current_num_threads(); @@ -77,22 +75,27 @@ impl DensePolynomial { // compute num_cpus_used as ceil(num_coeffs / num_elem_per_thread) let num_cpus_used = (num_coeffs + num_elem_per_thread - 1) / num_elem_per_thread; - // run Horners method on each thread + // run Horners method on each thread as follows: + // 1) Split up the coefficients across each thread evenly. + // 2) Do polynomial evaluation via horner's method for the thread's coefficeints + // 3) Scale the result point^{thread coefficient start index} + // Then obtain the final polynomial evaluation by summing each threads result. let mut per_thread_res = vec![F::zero(); num_cpus_used]; - let result = per_thread_res.par_iter_mut() + let result = per_thread_res + .par_iter_mut() .enumerate() - .map(|(i, _)| - { - let mut thread_result = F::zero(); - let iter_start_index = i * num_elem_per_thread; - let iter_end_index = min((i+1) * num_elem_per_thread, num_coeffs); - for i in (iter_start_index..iter_end_index).rev() { - thread_result *= point; - thread_result += self.coeffs[i]; - } - thread_result *= point.pow(&[iter_start_index as u64]); - thread_result - }).sum(); + .map(|(i, _)| { + let mut thread_result = F::zero(); + let iter_start_index = i * num_elem_per_thread; + let iter_end_index = min((i + 1) * num_elem_per_thread, num_coeffs); + for i in (iter_start_index..iter_end_index).rev() { + thread_result *= point; + thread_result += self.coeffs[i]; + } + thread_result *= point.pow(&[iter_start_index as u64]); + thread_result + }) + .sum(); result } } From 4ce7d11615a855f67b04b9f702a4424bf9c1af5d Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sun, 6 Dec 2020 19:01:50 -0600 Subject: [PATCH 08/11] Move import to feature gate --- poly/src/polynomial/univariate/dense.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/poly/src/polynomial/univariate/dense.rs b/poly/src/polynomial/univariate/dense.rs index d24aa5981..ad1c1a26f 100644 --- a/poly/src/polynomial/univariate/dense.rs +++ b/poly/src/polynomial/univariate/dense.rs @@ -4,7 +4,6 @@ use crate::{EvaluationDomain, Evaluations, GeneralEvaluationDomain}; use crate::{Polynomial, UVPolynomial}; use ark_serialize::*; use ark_std::{ - cmp::{max, min}, fmt, ops::{Add, AddAssign, Deref, DerefMut, Div, Mul, Neg, Sub, SubAssign}, vec::Vec, @@ -13,6 +12,8 @@ use ark_std::{ use ark_ff::{FftField, Field, Zero}; use rand::Rng; +#[cfg(feature = "parallel")] +use ark_std::cmp::{max, min}; #[cfg(feature = "parallel")] use rayon::prelude::*; From e5888ad904109b0139f0ae1d7c81cbdc843f71bf Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Mon, 7 Dec 2020 14:07:41 -0600 Subject: [PATCH 09/11] Refactor evaluate to use par_chunks --- poly/src/polynomial/univariate/dense.rs | 29 +++++++++---------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/poly/src/polynomial/univariate/dense.rs b/poly/src/polynomial/univariate/dense.rs index ad1c1a26f..d6eb1b4de 100644 --- a/poly/src/polynomial/univariate/dense.rs +++ b/poly/src/polynomial/univariate/dense.rs @@ -13,7 +13,7 @@ use ark_ff::{FftField, Field, Zero}; use rand::Rng; #[cfg(feature = "parallel")] -use ark_std::cmp::{max, min}; +use ark_std::cmp::max; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -73,30 +73,21 @@ impl DensePolynomial { let num_cpus_available = rayon::current_num_threads(); let num_coeffs = self.coeffs.len(); let num_elem_per_thread = max(num_coeffs / num_cpus_available, MIN_ELEMENTS_PER_THREAD); - // compute num_cpus_used as ceil(num_coeffs / num_elem_per_thread) - let num_cpus_used = (num_coeffs + num_elem_per_thread - 1) / num_elem_per_thread; // run Horners method on each thread as follows: // 1) Split up the coefficients across each thread evenly. // 2) Do polynomial evaluation via horner's method for the thread's coefficeints // 3) Scale the result point^{thread coefficient start index} // Then obtain the final polynomial evaluation by summing each threads result. - let mut per_thread_res = vec![F::zero(); num_cpus_used]; - let result = per_thread_res - .par_iter_mut() - .enumerate() - .map(|(i, _)| { - let mut thread_result = F::zero(); - let iter_start_index = i * num_elem_per_thread; - let iter_end_index = min((i + 1) * num_elem_per_thread, num_coeffs); - for i in (iter_start_index..iter_end_index).rev() { - thread_result *= point; - thread_result += self.coeffs[i]; - } - thread_result *= point.pow(&[iter_start_index as u64]); - thread_result - }) - .sum(); + let result = self.coeffs.par_chunks(num_elem_per_thread).enumerate().map(|(i, chunk)| { + let mut thread_result = F::zero(); + for j in (0..chunk.len()).rev() { + thread_result *= point; + thread_result += chunk[j]; + } + thread_result *= point.pow(&[(i * num_elem_per_thread) as u64]); + thread_result + }).sum(); result } } From 3e6c0e60ca88795a3fc766693d0fd1c6d29b347b Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Mon, 7 Dec 2020 14:53:04 -0600 Subject: [PATCH 10/11] fix style --- poly/src/polynomial/univariate/dense.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/poly/src/polynomial/univariate/dense.rs b/poly/src/polynomial/univariate/dense.rs index d6eb1b4de..9d7d1ad5d 100644 --- a/poly/src/polynomial/univariate/dense.rs +++ b/poly/src/polynomial/univariate/dense.rs @@ -79,15 +79,20 @@ impl DensePolynomial { // 2) Do polynomial evaluation via horner's method for the thread's coefficeints // 3) Scale the result point^{thread coefficient start index} // Then obtain the final polynomial evaluation by summing each threads result. - let result = self.coeffs.par_chunks(num_elem_per_thread).enumerate().map(|(i, chunk)| { - let mut thread_result = F::zero(); - for j in (0..chunk.len()).rev() { - thread_result *= point; - thread_result += chunk[j]; - } - thread_result *= point.pow(&[(i * num_elem_per_thread) as u64]); - thread_result - }).sum(); + let result = self + .coeffs + .par_chunks(num_elem_per_thread) + .enumerate() + .map(|(i, chunk)| { + let mut thread_result = F::zero(); + for j in (0..chunk.len()).rev() { + thread_result *= point; + thread_result += chunk[j]; + } + thread_result *= point.pow(&[(i * num_elem_per_thread) as u64]); + thread_result + }) + .sum(); result } } From 4be36e42951eb70affe5126cadb6343fa43f4636 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Mon, 7 Dec 2020 15:03:59 -0600 Subject: [PATCH 11/11] Refactor common logic --- poly/src/polynomial/univariate/dense.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/poly/src/polynomial/univariate/dense.rs b/poly/src/polynomial/univariate/dense.rs index 9d7d1ad5d..0e4e880c9 100644 --- a/poly/src/polynomial/univariate/dense.rs +++ b/poly/src/polynomial/univariate/dense.rs @@ -54,18 +54,23 @@ impl Polynomial for DensePolynomial { const MIN_ELEMENTS_PER_THREAD: usize = 16; impl DensePolynomial { - #[cfg(not(feature = "parallel"))] - fn internal_evaluate(&self, point: &F) -> F { - // Horners method - serial method + #[inline] + // Horner's method for polynomial evaluation + fn horner_evaluate(poly_coeffs: &[F], point: &F) -> F { let mut result = F::zero(); - let num_coeffs = self.coeffs.len(); + let num_coeffs = poly_coeffs.len(); for i in (0..num_coeffs).rev() { result *= point; - result += self.coeffs[i]; + result += poly_coeffs[i]; } result } + #[cfg(not(feature = "parallel"))] + fn internal_evaluate(&self, point: &F) -> F { + Self::horner_evaluate(&self.coeffs, point) + } + #[cfg(feature = "parallel")] fn internal_evaluate(&self, point: &F) -> F { // Horners method - parallel method @@ -84,11 +89,7 @@ impl DensePolynomial { .par_chunks(num_elem_per_thread) .enumerate() .map(|(i, chunk)| { - let mut thread_result = F::zero(); - for j in (0..chunk.len()).rev() { - thread_result *= point; - thread_result += chunk[j]; - } + let mut thread_result = Self::horner_evaluate(&chunk, point); thread_result *= point.pow(&[(i * num_elem_per_thread) as u64]); thread_result })