Allow overriding of std:🧵:hardware concurrency (#3948)

* Add `get_env_int_or_default`

* Add `nano::hardware_concurrency`

* Use `nano::hardware_concurrency` when checking available cores

* Print info about logical cores at startup

* Lexical casts may throw exceptions if the string is invalid. Since this is explicitly overriden by the user, and called in several non-exception safe contexts, log to cerr the reason why the exception was thrown.

* Comment

Co-authored-by: clemahieu <clemahieu@gmail.com>
This commit is contained in:
Piotr Wójcik 2022-09-21 13:04:43 +02:00 committed by GitHub
commit 684fb3a8bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 87 additions and 18 deletions

View file

@ -2,6 +2,7 @@
#include <nano/lib/config.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <valgrind/valgrind.h>
@ -229,12 +230,6 @@ uint8_t get_pre_release_node_version ()
return boost::numeric_cast<uint8_t> (boost::lexical_cast<int> (NANO_PRE_RELEASE_VERSION_STRING));
}
std::string get_env_or_default (char const * variable_name, std::string default_value)
{
auto value = getenv (variable_name);
return value ? value : default_value;
}
uint64_t get_env_threshold_or_default (char const * variable_name, uint64_t const default_value)
{
auto * value = getenv (variable_name);
@ -321,8 +316,43 @@ std::string get_tls_toml_config_path (boost::filesystem::path const & data_path)
}
} // namespace nano
std::optional<std::string> nano::get_env (const char * variable_name)
{
auto value = std::getenv (variable_name);
if (value)
{
return value;
}
return {};
}
std::string nano::get_env_or_default (char const * variable_name, std::string default_value)
{
auto value = nano::get_env (variable_name);
return value ? *value : default_value;
}
int nano::get_env_int_or_default (const char * variable_name, const int default_value)
{
auto value = nano::get_env (variable_name);
if (value)
{
try
{
return boost::lexical_cast<int> (*value);
}
catch (...)
{
// It is unexpected that this exception will be caught, log to cerr the reason.
std::cerr << boost::str (boost::format ("Error parsing environment variable: %1% value: %2%") % variable_name % *value);
throw;
}
}
return default_value;
}
uint32_t nano::test_scan_wallet_reps_delay ()
{
auto test_env = nano::get_env_or_default ("NANO_TEST_WALLET_SCAN_REPS_DELAY", "900000"); // 15 minutes by default
return boost::lexical_cast<uint32_t> (test_env);
}
}

View file

@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
#include <chrono>
#include <optional>
#include <string>
namespace boost
@ -87,7 +88,22 @@ uint8_t get_minor_node_version ();
uint8_t get_patch_node_version ();
uint8_t get_pre_release_node_version ();
/*
* Environment variables
*/
/*
* Get environment variable as string or none if variable is not present
*/
std::optional<std::string> get_env (char const * variable_name);
/*
* Get environment variable as string or `default_value` if variable is not present
*/
std::string get_env_or_default (char const * variable_name, std::string const default_value);
/*
* Get environment variable as int or `default_value` if variable is not present
*/
int get_env_int_or_default (char const * variable_name, int const default_value);
uint64_t get_env_threshold_or_default (char const * variable_name, uint64_t const default_value);
uint16_t test_node_port ();

View file

@ -1,6 +1,7 @@
#pragma once
#include <nano/lib/errors.hpp>
#include <nano/lib/threading.hpp>
#include <thread>
@ -24,6 +25,6 @@ public:
bool enable{ false };
uint8_t memory_multiplier{ 2 };
unsigned io_threads{ std::thread::hardware_concurrency () };
unsigned io_threads{ nano::hardware_concurrency () };
};
}

View file

@ -2,7 +2,9 @@
#include <nano/lib/config.hpp>
#include <nano/lib/errors.hpp>
#include <nano/lib/threading.hpp>
#include <algorithm>
#include <memory>
#include <string>
#include <thread>
@ -53,7 +55,7 @@ class rpc_process_config final
public:
rpc_process_config (nano::network_constants & network_constants);
nano::network_constants & network_constants;
unsigned io_threads{ (4 < std::thread::hardware_concurrency ()) ? std::thread::hardware_concurrency () : 4 };
unsigned io_threads{ std::max (nano::hardware_concurrency (), 4u) };
std::string ipc_address;
uint16_t ipc_port{ network_constants.default_ipc_port };
unsigned num_ipc_connections{ (network_constants.is_live_network () || network_constants.is_test_network ()) ? 8u : network_constants.is_beta_network () ? 4u

View file

@ -308,3 +308,15 @@ std::unique_ptr<nano::container_info_component> nano::collect_container_info (th
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "count", thread_pool.num_queued_tasks (), sizeof (std::function<void ()>) }));
return composite;
}
unsigned int nano::hardware_concurrency ()
{
// Try to read overridden value from environment variable
static int value = nano::get_env_int_or_default ("NANO_HARDWARE_CONCURRENCY", 0);
if (value <= 0)
{
// Not present or invalid, use default
return std::thread::hardware_concurrency ();
}
return value;
}

View file

@ -201,4 +201,9 @@ private:
};
std::unique_ptr<nano::container_info_component> collect_container_info (thread_pool & thread_pool, std::string const & name);
/*
* Number of available logical processor cores. Might be overridden by setting `NANO_HARDWARE_CONCURRENCY` environment variable
*/
unsigned int hardware_concurrency ();
}

View file

@ -32,7 +32,7 @@ nano::work_pool::work_pool (nano::network_constants & network_constants, unsigne
static_assert (ATOMIC_INT_LOCK_FREE == 2, "Atomic int needed");
boost::thread::attributes attrs;
nano::thread_attributes::set (attrs);
auto count (network_constants.is_dev_network () ? std::min (max_threads_a, 1u) : std::min (max_threads_a, std::max (1u, boost::thread::hardware_concurrency ())));
auto count (network_constants.is_dev_network () ? std::min (max_threads_a, 1u) : std::min (max_threads_a, std::max (1u, nano::hardware_concurrency ())));
if (opencl)
{
// One thread to handle OpenCL

View file

@ -117,6 +117,9 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano::
std::cout << initialization_text << std::endl;
logger.always_log (initialization_text);
// Print info about number of logical cores detected, those are used to decide how many IO, worker and signature checker threads to spawn
logger.always_log (boost::format ("Hardware concurrency: %1% ( configured: %2% )") % std::thread::hardware_concurrency () % nano::hardware_concurrency ());
nano::set_file_descriptor_limit (OPEN_FILE_DESCRIPTORS_LIMIT);
auto const file_descriptor_limit = nano::get_file_descriptor_limit ();
if (file_descriptor_limit < OPEN_FILE_DESCRIPTORS_LIMIT)

View file

@ -56,16 +56,16 @@ public:
nano::amount online_weight_minimum{ 60000 * nano::Gxrb_ratio };
unsigned election_hint_weight_percent{ 50 };
unsigned password_fanout{ 1024 };
unsigned io_threads{ std::max<unsigned> (4, std::thread::hardware_concurrency ()) };
unsigned network_threads{ std::max<unsigned> (4, std::thread::hardware_concurrency ()) };
unsigned work_threads{ std::max<unsigned> (4, std::thread::hardware_concurrency ()) };
unsigned io_threads{ std::max (4u, nano::hardware_concurrency ()) };
unsigned network_threads{ std::max (4u, nano::hardware_concurrency ()) };
unsigned work_threads{ std::max (4u, nano::hardware_concurrency ()) };
/* Use half available threads on the system for signature checking. The calling thread does checks as well, so these are extra worker threads */
unsigned signature_checker_threads{ std::thread::hardware_concurrency () / 2 };
unsigned signature_checker_threads{ std::max (2u, nano::hardware_concurrency () / 2) };
bool enable_voting{ false };
unsigned bootstrap_connections{ 4 };
unsigned bootstrap_connections_max{ 64 };
unsigned bootstrap_initiator_threads{ 1 };
unsigned bootstrap_serving_threads{ std::max<unsigned> (2, std::thread::hardware_concurrency () / 2) };
unsigned bootstrap_serving_threads{ std::max (2u, nano::hardware_concurrency () / 2) };
uint32_t bootstrap_frontier_request_count{ 1024 * 1024 };
nano::websocket::config websocket_config;
nano::diagnostics_config diagnostics_config;

View file

@ -744,7 +744,7 @@ bool nano::rocksdb::store::copy_db (boost::filesystem::path const & destination_
backup_options.share_table_files = true;
// Increase number of threads used for copying
backup_options.max_background_operations = std::thread::hardware_concurrency ();
backup_options.max_background_operations = nano::hardware_concurrency ();
auto status = ::rocksdb::BackupEngine::Open (::rocksdb::Env::Default (), backup_options, &backup_engine_raw);
backup_engine.reset (backup_engine_raw);
if (!status.ok ())

View file

@ -9,7 +9,7 @@ template <typename T>
void parallel_traversal (std::function<void (T const &, T const &, bool const)> const & action)
{
// Between 10 and 40 threads, scales well even in low power systems as long as actions are I/O bound
unsigned const thread_count = std::max (10u, std::min (40u, 10 * std::thread::hardware_concurrency ()));
unsigned const thread_count = std::max (10u, std::min (40u, 10 * nano::hardware_concurrency ()));
T const value_max{ std::numeric_limits<T>::max () };
T const split = value_max / thread_count;
std::vector<std::thread> threads;

View file

@ -63,7 +63,7 @@ namespace test
boost::asio::io_context io_ctx;
std::vector<std::shared_ptr<nano::node>> nodes;
nano::logging logging;
nano::work_pool work{ nano::dev::network_params.network, std::max (std::thread::hardware_concurrency (), 1u) };
nano::work_pool work{ nano::dev::network_params.network, std::max (nano::hardware_concurrency (), 1u) };
std::chrono::time_point<std::chrono::steady_clock, std::chrono::duration<double>> deadline{ std::chrono::steady_clock::time_point::max () };
double deadline_scaling_factor{ 1.0 };
unsigned node_sequence{ 0 };