diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a794179..701998c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,26 @@ project(parallel-crypto3) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/share/modules/cmake") +# This is useful due to some build systems (Ninja in particular) are piping +# compiler output and compiler switches it's output to plain text +option (FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." FALSE) +if (${FORCE_COLORED_OUTPUT}) + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options (-fdiagnostics-color=always) + elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + add_compile_options (-fcolor-diagnostics) + endif () +endif () + +# The file compile_commands.json is generated in build directory, so LSP could +# pick it up and guess all include paths, defines and other stuff. +# If Nix is used, LSP could not guess the locations of implicit include +# directories, so we need to include them explicitly. +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + # TODO: check what is actually required here include(CMConfig) include(CMDeploy) diff --git a/libs/parallel-zk/include/nil/crypto3/zk/commitments/batched_commitment.hpp b/libs/parallel-zk/include/nil/crypto3/zk/commitments/batched_commitment.hpp index a20df6f4..93ba19b3 100644 --- a/libs/parallel-zk/include/nil/crypto3/zk/commitments/batched_commitment.hpp +++ b/libs/parallel-zk/include/nil/crypto3/zk/commitments/batched_commitment.hpp @@ -199,10 +199,7 @@ namespace nil { } void eval_polys() { - for(auto it = _polys.begin(); it != _polys.end(); ++it) { - std::size_t k = it->first; - const auto& poly = it->second; - + for(auto const &[k, poly] : _polys) { _z.set_batch_size(k, poly.size()); auto const &point = _points.at(k); diff --git a/libs/parallel-zk/include/nil/crypto3/zk/commitments/detail/polynomial/basic_fri.hpp b/libs/parallel-zk/include/nil/crypto3/zk/commitments/detail/polynomial/basic_fri.hpp index 705a5fc6..8bfa7f51 100644 --- a/libs/parallel-zk/include/nil/crypto3/zk/commitments/detail/polynomial/basic_fri.hpp +++ b/libs/parallel-zk/include/nil/crypto3/zk/commitments/detail/polynomial/basic_fri.hpp @@ -157,7 +157,7 @@ namespace nil { std::size_t lambda, std::size_t expand_factor, bool use_grinding = false, - std::size_t grinding_parameter = 0xFFFF + std::size_t grinding_parameter = 16 ): lambda(lambda) , use_grinding(use_grinding) , grinding_parameter(grinding_parameter) @@ -174,7 +174,7 @@ namespace nil { std::size_t lambda, std::size_t expand_factor, bool use_grinding = false, - std::size_t grinding_parameter = 0xFFFF + std::size_t grinding_parameter = 16 ): lambda(lambda) , use_grinding(use_grinding) , grinding_parameter(grinding_parameter) @@ -192,7 +192,7 @@ namespace nil { std::size_t expand_factor, std::size_t lambda, bool use_grinding = false, - std::size_t grinding_parameter = 0xFFFF + std::size_t grinding_parameter = 16 ) : lambda(lambda) , use_grinding(use_grinding) , grinding_parameter(grinding_parameter) diff --git a/libs/parallel-zk/include/nil/crypto3/zk/commitments/detail/polynomial/proof_of_work.hpp b/libs/parallel-zk/include/nil/crypto3/zk/commitments/detail/polynomial/proof_of_work.hpp index be1878d0..57a038c4 100644 --- a/libs/parallel-zk/include/nil/crypto3/zk/commitments/detail/polynomial/proof_of_work.hpp +++ b/libs/parallel-zk/include/nil/crypto3/zk/commitments/detail/polynomial/proof_of_work.hpp @@ -1,5 +1,6 @@ //---------------------------------------------------------------------------// // Copyright (c) 2023 Elena Tatuzova +// Copyright (c) 2024 Vasiliy Olekhov // // MIT License // @@ -47,37 +48,66 @@ namespace nil { using transcript_type = transcript::fiat_shamir_heuristic_sequential; using output_type = OutType; - static inline OutType generate(transcript_type &transcript, OutType mask=0xFFFF) { - output_type proof_of_work = std::rand(); - output_type result; - std::vector bytes(4); + static inline std::array + to_byte_array(OutType v) { + std::array bytes; + for(int i = sizeof(v)-1; i>=0; --i) { + bytes[i] = v & 0xFF; + v >>= 8; + } + return bytes; + } + + static inline OutType generate(transcript_type &transcript, std::size_t grinding_bits = 16) { + BOOST_ASSERT_MSG(grinding_bits < 64, "Grinding parameter should be bits, not mask"); + output_type mask = grinding_bits > 0 ? ( 1ULL << grinding_bits ) - 1 : 0; + output_type pow_seed = std::rand(); + + /* Enough work for ~ two minutes on 48 cores, keccak<512> */ + std::size_t per_block = 1 << 30; + + std::atomic challenge_found = false; + std::atomic pow_value_offset; while( true ) { - transcript_type tmp_transcript = transcript; - bytes[0] = std::uint8_t((proof_of_work&0xFF000000)>>24); - bytes[1] = std::uint8_t((proof_of_work&0x00FF0000)>>16); - bytes[2] = std::uint8_t((proof_of_work&0x0000FF00)>>8); - bytes[3] = std::uint8_t(proof_of_work&0x000000FF); - - tmp_transcript(bytes); - result = tmp_transcript.template int_challenge(); - if ((result & mask) == 0) + wait_for_all(parallel_run_in_chunks( + per_block, + [&transcript, &pow_seed, &challenge_found, &pow_value_offset, &mask](std::size_t pow_start, std::size_t pow_finish) { + std::size_t i = pow_start; + while ( i < pow_finish ) { + if (challenge_found) { + break; + } + transcript_type tmp_transcript = transcript; + tmp_transcript(to_byte_array(pow_seed + i)); + OutType pow_result = tmp_transcript.template int_challenge(); + if ( ((pow_result & mask) == 0) && !challenge_found ) { + bool expected = false; + if (challenge_found.compare_exchange_strong(expected, true)) { + pow_value_offset = i; + } + break; + } + ++i; + } + }, ThreadPool::PoolLevel::LOW)); + + if (challenge_found) { break; - proof_of_work++; + } + pow_seed += per_block; } - transcript(bytes); - result = transcript.template int_challenge(); - return proof_of_work; + + transcript(to_byte_array(pow_seed + (std::size_t)pow_value_offset)); + transcript.template int_challenge(); + return pow_seed + (std::size_t)pow_value_offset; } - static inline bool verify(transcript_type &transcript, output_type proof_of_work, OutType mask=0xFFFF) { - std::vector bytes(4); - bytes[0] = std::uint8_t((proof_of_work&0xFF000000)>>24); - bytes[1] = std::uint8_t((proof_of_work&0x00FF0000)>>16); - bytes[2] = std::uint8_t((proof_of_work&0x0000FF00)>>8); - bytes[3] = std::uint8_t(proof_of_work&0x000000FF); - transcript(bytes); + static inline bool verify(transcript_type &transcript, output_type proof_of_work, std::size_t grinding_bits = 16) { + BOOST_ASSERT_MSG(grinding_bits < 64, "Grinding parameter should be bits, not mask"); + transcript(to_byte_array(proof_of_work)); output_type result = transcript.template int_challenge(); + output_type mask = grinding_bits > 0 ? ( 1ULL << grinding_bits ) - 1 : 0; return ((result & mask) == 0); } }; @@ -104,8 +134,8 @@ namespace nil { ((integral_type(1) << GrindingBits) - 1) << (FieldType::modulus_bits - GrindingBits) : 0); - /* Enough work for ~ two minutes on 48 cores */ - std::size_t per_block = 1<<23; + /* Enough work for ~ two minutes on 48 cores, poseidon */ + std::size_t per_block = 1 << 23; std::atomic challenge_found = false; std::atomic pow_value_offset; diff --git a/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/kzg.hpp b/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/kzg.hpp index b25fe40a..d281a9d3 100644 --- a/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/kzg.hpp +++ b/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/kzg.hpp @@ -352,43 +352,18 @@ namespace nil { /* The procedure of updating the transcript is subject to review and change * #295 */ - nil::marshalling::status_type status; - - for (const auto &commit: public_key.commits) { - std::vector byteblob = - nil::marshalling::pack(commit, status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(byteblob) - >(byteblob) - ); + + for (const auto &commit : public_key.commits) { + transcript(commit); } - for (const auto &S: public_key.S) { - for (const auto &s: S) { - std::vector byteblob = - nil::marshalling::pack(s, status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(byteblob) - >(byteblob) - ); + for (const auto &S : public_key.S) { + for (const auto &s : S) { + transcript(s); } } for (const auto &r: public_key.r) { for (std::size_t i = 0; i < r.size(); ++i) { - std::vector byteblob = - nil::marshalling::pack(r[i], status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(byteblob) - >(byteblob) - ); + transcript(r[i]); } } } @@ -730,24 +705,13 @@ namespace nil { * #295 */ // Push commitments to transcript - transcript(::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(_commitments[batch_ind]) - >(_commitments[batch_ind])); + + transcript(_commitments[batch_ind]); // Push evaluation points to transcript - for (std::size_t i = 0; i < this->_z.get_batch_size(batch_ind); i++) { - for (std::size_t j = 0; j < this->_z.get_poly_points_number(batch_ind, i); j++) { - nil::marshalling::status_type status; - std::vector byteblob = - nil::marshalling::pack(this->_z.get(batch_ind, i, j), status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(byteblob) - >(byteblob) - ); + for(std::size_t i = 0; i < this->_z.get_batch_size(batch_ind); i++) { + for(std::size_t j = 0; j < this->_z.get_poly_points_number(batch_ind, i); j++) { + transcript(this->_z.get(batch_ind, i, j)); } } @@ -755,16 +719,7 @@ namespace nil { for (std::size_t i = 0; i < this->_points[batch_ind].size(); i++) { auto poly = this->get_U(batch_ind, i); for (std::size_t j = 0; j < poly.size(); ++j) { - nil::marshalling::status_type status; - std::vector byteblob = - nil::marshalling::pack(poly[j], status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(byteblob) - >(byteblob) - ); + transcript(poly[j]); } } } diff --git a/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/kzg_v2.hpp b/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/kzg_v2.hpp index e7bd5299..d63d31d1 100644 --- a/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/kzg_v2.hpp +++ b/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/kzg_v2.hpp @@ -155,26 +155,12 @@ namespace nil { * #295 */ // Push commitments to transcript - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(_commitments[batch_ind]) - >(_commitments[batch_ind]) - ); + transcript(_commitments[batch_ind]); // Push evaluation points to transcript for (std::size_t i = 0; i < this->_z.get_batch_size(batch_ind); i++) { for (std::size_t j = 0; j < this->_z.get_poly_points_number(batch_ind, i); j++) { - nil::marshalling::status_type status; - std::vector byteblob = - nil::marshalling::pack(this->_z.get(batch_ind, i, j), status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(byteblob) - >(byteblob) - ); + transcript(this->_z.get(batch_ind, i, j)); } } @@ -182,16 +168,7 @@ namespace nil { for (std::size_t i = 0; i < this->_points[batch_ind].size(); i++) { auto poly = this->get_U(batch_ind, i); for (std::size_t j = 0; j < poly.size(); ++j) { - nil::marshalling::status_type status; - std::vector byteblob = - nil::marshalling::pack(poly[j], status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(byteblob) - >(byteblob) - ); + transcript(poly[j]); } } } @@ -291,14 +268,7 @@ namespace nil { typename CommitmentSchemeType::single_commitment_type pi_1 = nil::crypto3::zk::algorithms::commit_one( _params, f); - nil::marshalling::status_type status; - std::vector pi1_byteblob = nil::marshalling::pack(pi_1, status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - - transcript(::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(pi1_byteblob) - >(pi1_byteblob)); + transcript(pi_1); auto theta_2 = transcript.template challenge(); math::polynomial theta_2_vanish = { @@ -331,12 +301,7 @@ namespace nil { _params, L); /* TODO: Review the necessity of sending pi_2 to transcript */ - std::vector pi2_byteblob = nil::marshalling::pack(pi_2, status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript(::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(pi2_byteblob) - >(pi2_byteblob)); + transcript(pi_2); return {this->_z, pi_1, pi_2}; } @@ -354,20 +319,17 @@ namespace nil { } auto theta = transcript.template challenge(); - nil::marshalling::status_type status; - std::vector byteblob = nil::marshalling::pack(proof.pi_1, status); - BOOST_ASSERT(status == nil::marshalling::status_type::success); - transcript( - ::nil::crypto3::hashes::conditional_block_to_field_elements_wrapper< - typename CommitmentSchemeType::transcript_hash_type::word_type, - decltype(byteblob) - >(byteblob)); + + transcript(proof.pi_1); + auto theta_2 = transcript.template challenge(); auto theta_i = CommitmentSchemeType::scalar_value_type::one(); auto F = CommitmentSchemeType::single_commitment_type::zero(); auto rsum = CommitmentSchemeType::scalar_value_type::zero(); + nil::marshalling::status_type status; + for (const auto &it: this->_commitments) { auto k = it.first; std::size_t blob_size = this->_commitments[k].size() / this->_points.at(k).size(); diff --git a/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/lpc.hpp b/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/lpc.hpp index a78a7cf4..06e1e2ed 100644 --- a/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/lpc.hpp +++ b/libs/parallel-zk/include/nil/crypto3/zk/commitments/polynomial/lpc.hpp @@ -267,8 +267,7 @@ namespace nil { combined_Q = std::move(combined_Q_normal); } - precommitment_type combined_Q_precommitment; - combined_Q_precommitment = nil::crypto3::zk::algorithms::precommit( + precommitment_type combined_Q_precommitment = nil::crypto3::zk::algorithms::precommit( combined_Q, _fri_params.D[0], _fri_params.step_list.front() diff --git a/libs/parallel-zk/include/nil/crypto3/zk/math/integer_permutation.hpp b/libs/parallel-zk/include/nil/crypto3/zk/math/integer_permutation.hpp index 077f0bec..8ee3b088 100644 --- a/libs/parallel-zk/include/nil/crypto3/zk/math/integer_permutation.hpp +++ b/libs/parallel-zk/include/nil/crypto3/zk/math/integer_permutation.hpp @@ -27,6 +27,7 @@ #define CRYPTO3_ZK_MATH_INTEGER_PERMUTATION_HPP #include +#include #include #include #include diff --git a/libs/parallel-zk/include/nil/crypto3/zk/snark/routing/benes.hpp b/libs/parallel-zk/include/nil/crypto3/zk/snark/routing/benes.hpp index e08a71cb..2409d610 100644 --- a/libs/parallel-zk/include/nil/crypto3/zk/snark/routing/benes.hpp +++ b/libs/parallel-zk/include/nil/crypto3/zk/snark/routing/benes.hpp @@ -247,7 +247,7 @@ namespace nil { benes_routing &routing) { assert(permutation.size() == subnetwork_size); assert(permutation.is_valid()); - assert(permutation.inversed() == permutation_inv); + assert(permutation.inverse() == permutation_inv); if (column_idx_start == column_idx_end) { /* nothing to route */ diff --git a/libs/parallel-zk/test/commitment/lpc_performance.cpp b/libs/parallel-zk/test/bench_test/lpc_performance.cpp similarity index 100% rename from libs/parallel-zk/test/commitment/lpc_performance.cpp rename to libs/parallel-zk/test/bench_test/lpc_performance.cpp diff --git a/libs/parallel-zk/test/commitment/fri.cpp b/libs/parallel-zk/test/commitment/fri.cpp index c505644b..531c4c4d 100644 --- a/libs/parallel-zk/test/commitment/fri.cpp +++ b/libs/parallel-zk/test/commitment/fri.cpp @@ -116,7 +116,7 @@ BOOST_AUTO_TEST_SUITE(fri_test_suite) 2, //expand_factor lambda, true, - 0xFFFFF + 20 ); BOOST_CHECK(D[1]->m == D[0]->m / 2); diff --git a/libs/parallel-zk/test/commitment/lpc.cpp b/libs/parallel-zk/test/commitment/lpc.cpp index 47e66d62..a6e92d65 100644 --- a/libs/parallel-zk/test/commitment/lpc.cpp +++ b/libs/parallel-zk/test/commitment/lpc.cpp @@ -210,7 +210,7 @@ BOOST_AUTO_TEST_SUITE(lpc_math_polynomial_suite); 2, //expand_factor lambda, true, - 0xFFF + 12 ); using lpc_scheme_type = nil::crypto3::zk::commitments::lpc_commitment_scheme>; @@ -510,7 +510,7 @@ BOOST_AUTO_TEST_SUITE(lpc_params_test_suite) 2, //expand_factor lambda, true, - 0xFF + 8 ); using lpc_scheme_type = nil::crypto3::zk::commitments::lpc_commitment_scheme>; diff --git a/libs/parallel-zk/test/commitment/proof_of_work.cpp b/libs/parallel-zk/test/commitment/proof_of_work.cpp index ae7f51fc..3c7d02df 100644 --- a/libs/parallel-zk/test/commitment/proof_of_work.cpp +++ b/libs/parallel-zk/test/commitment/proof_of_work.cpp @@ -51,16 +51,18 @@ BOOST_AUTO_TEST_SUITE(proof_of_knowledge_test_suite) using poseidon = nil::crypto3::hashes::poseidon; using pow_type = nil::crypto3::zk::commitments::field_proof_of_work; - const integral_type expected_mask = integral_type(0xFF80000000000000) << (field_type::modulus_bits - 64); + std::size_t grinding_bits = 9; nil::crypto3::zk::transcript::fiat_shamir_heuristic_sequential transcript; auto old_transcript_1 = transcript, old_transcript_2 = transcript; - auto result = pow_type::generate(transcript, 9); - BOOST_ASSERT(pow_type::verify(old_transcript_1, result, 9)); + auto result = pow_type::generate(transcript, grinding_bits); + BOOST_ASSERT(pow_type::verify(old_transcript_1, result, grinding_bits)); // manually reimplement verify to ensure that changes in implementation didn't break it old_transcript_2(result); auto chal = old_transcript_2.template challenge(); + const integral_type expected_mask = integral_type( (1 << grinding_bits) - 1 ) << (field_type::modulus_bits - grinding_bits); + BOOST_ASSERT((integral_type(chal.data) & expected_mask) == 0); using hard_pow_type = nil::crypto3::zk::commitments::field_proof_of_work; @@ -70,28 +72,27 @@ BOOST_AUTO_TEST_SUITE(proof_of_knowledge_test_suite) BOOST_AUTO_TEST_CASE(pow_basic_test) { using keccak = nil::crypto3::hashes::keccak_1600<512>; - const std::uint32_t mask = 0xFFFFF000; - using pow_type = nil::crypto3::zk::commitments::proof_of_work; + + const std::uint64_t grinding_bits = 16; + const uint64_t expected_mask = (1ull << grinding_bits) - 1; + + using pow_type = nil::crypto3::zk::commitments::proof_of_work; nil::crypto3::zk::transcript::fiat_shamir_heuristic_sequential transcript; auto old_transcript_1 = transcript, old_transcript_2 = transcript; - auto result = pow_type::generate(transcript, mask); - BOOST_ASSERT(pow_type::verify(old_transcript_1, result, mask)); + auto result = pow_type::generate(transcript, grinding_bits); + BOOST_ASSERT(pow_type::verify(old_transcript_1, result, grinding_bits)); // manually reimplement verify to ensure that changes in implementation didn't break it - std::array bytes; - bytes[0] = std::uint8_t((result & 0xFF000000) >> 24); - bytes[1] = std::uint8_t((result & 0x00FF0000) >> 16); - bytes[2] = std::uint8_t((result & 0x0000FF00) >> 8); - bytes[3] = std::uint8_t(result & 0x000000FF); + auto bytes = pow_type::to_byte_array(result); old_transcript_2(bytes); auto chal = old_transcript_2.template int_challenge(); - BOOST_ASSERT((chal & mask) == 0); + BOOST_ASSERT( (chal & expected_mask) == 0); // check that random stuff doesn't pass verify using hard_pow_type = nil::crypto3::zk::commitments::proof_of_work; - BOOST_ASSERT(!hard_pow_type::verify(old_transcript_1, result, mask)); + BOOST_ASSERT(!hard_pow_type::verify(old_transcript_1, result, grinding_bits)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/parallelization-utils/include/nil/actor/core/parallelization_utils.hpp b/libs/parallelization-utils/include/nil/actor/core/parallelization_utils.hpp index 68aa29df..96fe37f5 100644 --- a/libs/parallelization-utils/include/nil/actor/core/parallelization_utils.hpp +++ b/libs/parallelization-utils/include/nil/actor/core/parallelization_utils.hpp @@ -69,7 +69,6 @@ namespace nil { workers_to_use = elements_count / POOL_0_MIN_CHUNK_SIZE + ((elements_count % POOL_0_MIN_CHUNK_SIZE) ? 1 : 0); workers_to_use = std::max((size_t)1, workers_to_use); } - const std::size_t elements_per_worker = elements_count / workers_to_use; std::size_t begin = 0; for (std::size_t i = 0; i < workers_to_use; i++) {