Ability to limit CPU rate for POW (#1734)

* Rate limited POW

Formatting

Fix wallet build

Formatting

Fix test

Change interval to nanoseconds

Formatting

* Formatting

* Formatting again

* Add CLI option to --debug_profile_generate to specify sleep interval.

* Move thread sleep to outer while loop
This commit is contained in:
Wesley Shillingford 2019-03-26 14:58:24 +00:00 committed by GitHub
commit 878cce97e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 105 additions and 32 deletions

View file

@ -32,7 +32,7 @@ TEST (node, block_store_path_failure)
auto path (nano::unique_path ());
nano::logging logging;
logging.init (path);
nano::work_pool work (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool work (std::numeric_limits<unsigned>::max ());
auto node (std::make_shared<nano::node> (init, *service, 24000, path, alarm, logging, work));
ASSERT_TRUE (node->wallets.items.empty ());
node->stop ();
@ -47,7 +47,7 @@ TEST (node, password_fanout)
nano::node_config config;
config.peering_port = 24000;
config.logging.init (path);
nano::work_pool work (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool work (std::numeric_limits<unsigned>::max ());
config.password_fanout = 10;
auto node (std::make_shared<nano::node> (init, *service, path, alarm, config, work));
auto wallet (node->wallets.create (100));
@ -698,10 +698,12 @@ TEST (node_config, v16_v17_upgrade)
// These config options should not be present
ASSERT_FALSE (tree.get_optional_child ("tcp_client_timeout"));
ASSERT_FALSE (tree.get_optional_child ("tcp_server_timeout"));
ASSERT_FALSE (tree.get_optional_child ("pow_sleep_interval"));
config.deserialize_json (upgraded, tree);
// The config options should be added after the upgrade
ASSERT_TRUE (!!tree.get_optional_child ("tcp_client_timeout"));
ASSERT_TRUE (!!tree.get_optional_child ("tcp_server_timeout"));
ASSERT_TRUE (!!tree.get_optional_child ("pow_sleep_interval"));
ASSERT_TRUE (upgraded);
auto version (tree.get<std::string> ("version"));
@ -723,19 +725,23 @@ TEST (node_config, v17_values)
// Check config is correct
tree.put ("tcp_client_timeout", 1);
tree.put ("tcp_server_timeout", 0);
tree.put ("pow_sleep_interval", 0);
config.deserialize_json (upgraded, tree);
ASSERT_FALSE (upgraded);
ASSERT_EQ (config.tcp_client_timeout.count (), 1);
ASSERT_EQ (config.tcp_server_timeout.count (), 0);
ASSERT_EQ (config.pow_sleep_interval.count (), 0);
// Check config is correct with other values
tree.put ("tcp_client_timeout", std::numeric_limits<unsigned long>::max () - 100);
tree.put ("tcp_server_timeout", std::numeric_limits<unsigned>::max ());
tree.put ("pow_sleep_interval", std::numeric_limits<unsigned long>::max () - 100);
upgraded = false;
config.deserialize_json (upgraded, tree);
ASSERT_FALSE (upgraded);
ASSERT_EQ (config.tcp_client_timeout.count (), std::numeric_limits<unsigned long>::max () - 100);
ASSERT_EQ (config.tcp_server_timeout.count (), std::numeric_limits<unsigned>::max ());
ASSERT_EQ (config.pow_sleep_interval.count (), std::numeric_limits<unsigned long>::max () - 100);
}
// Regression test to ensure that deserializing includes changes node via get_required_child

View file

@ -1,13 +1,14 @@
#include <gtest/gtest.h>
#include <nano/lib/jsonconfig.hpp>
#include <nano/lib/timer.hpp>
#include <nano/node/node.hpp>
#include <nano/node/wallet.hpp>
TEST (work, one)
{
nano::network_params params;
nano::work_pool pool (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool pool (std::numeric_limits<unsigned>::max ());
nano::change_block block (1, 1, nano::keypair ().prv, 3, 4);
block.block_work_set (pool.generate (block.root ()));
uint64_t difficulty;
@ -18,7 +19,7 @@ TEST (work, one)
TEST (work, validate)
{
nano::network_params params;
nano::work_pool pool (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool pool (std::numeric_limits<unsigned>::max ());
nano::send_block send_block (1, 1, 2, nano::keypair ().prv, 4, 6);
uint64_t difficulty;
ASSERT_TRUE (nano::work_validate (send_block, &difficulty));
@ -30,7 +31,7 @@ TEST (work, validate)
TEST (work, cancel)
{
nano::work_pool pool (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool pool (std::numeric_limits<unsigned>::max ());
auto iterations (0);
auto done (false);
while (!done)
@ -47,7 +48,7 @@ TEST (work, cancel)
TEST (work, cancel_many)
{
nano::work_pool pool (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool pool (std::numeric_limits<unsigned>::max ());
nano::uint256_union key1 (1);
nano::uint256_union key2 (2);
nano::uint256_union key3 (1);
@ -75,10 +76,10 @@ TEST (work, opencl)
auto opencl (nano::opencl_work::create (true, { 0, 0, 16 * 1024 }, logging));
if (opencl != nullptr)
{
nano::work_pool pool (std::numeric_limits<unsigned>::max (), opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) {
nano::work_pool pool (std::numeric_limits<unsigned>::max (), std::chrono::nanoseconds (0), opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) {
return opencl->generate_work (root_a, difficulty_a);
}
: std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> (nullptr));
: std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> (nullptr));
ASSERT_NE (nullptr, pool.opencl);
nano::uint256_union root;
uint64_t difficulty (0xff00000000000000);
@ -121,7 +122,7 @@ TEST (work, opencl_config)
TEST (work, difficulty)
{
nano::work_pool pool (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool pool (std::numeric_limits<unsigned>::max ());
nano::uint256_union root (1);
uint64_t difficulty1 (0xff00000000000000);
uint64_t difficulty2 (0xfff0000000000000);
@ -143,3 +144,45 @@ TEST (work, difficulty)
} while (nonce2 > difficulty3);
ASSERT_GT (nonce2, difficulty2);
}
TEST (work, eco_pow)
{
auto work_func = [](std::promise<std::chrono::nanoseconds> & promise, std::chrono::nanoseconds interval) {
nano::work_pool pool (1, interval);
constexpr auto num_iterations = 5;
nano::timer<std::chrono::nanoseconds> timer;
timer.start ();
for (int i = 0; i < num_iterations; ++i)
{
nano::uint256_union root (1);
uint64_t difficulty1 (0xff00000000000000);
uint64_t difficulty2 (0xfff0000000000000);
uint64_t work (0);
uint64_t nonce (0);
do
{
work = pool.generate (root, difficulty1);
nano::work_validate (root, work, &nonce);
} while (nonce > difficulty2);
ASSERT_GT (nonce, difficulty1);
}
promise.set_value_at_thread_exit (timer.stop ());
};
std::promise<std::chrono::nanoseconds> promise1;
std::future<std::chrono::nanoseconds> future1 = promise1.get_future ();
std::promise<std::chrono::nanoseconds> promise2;
std::future<std::chrono::nanoseconds> future2 = promise2.get_future ();
std::thread thread1 (work_func, std::ref (promise1), std::chrono::nanoseconds (0));
std::thread thread2 (work_func, std::ref (promise2), std::chrono::nanoseconds (100000));
// Confirm that the eco pow rate limiter is working.
// It's possible under some unlucky circumstances that this fails.
ASSERT_LT (future1.get (), future2.get ());
thread1.join ();
thread2.join ();
}

View file

@ -32,9 +32,10 @@ uint64_t nano::work_value (nano::block_hash const & root_a, uint64_t work_a)
return result;
}
nano::work_pool::work_pool (unsigned max_threads_a, std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> opencl_a) :
nano::work_pool::work_pool (unsigned max_threads_a, std::chrono::nanoseconds pow_rate_limiter_a, std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> opencl_a) :
ticket (0),
done (false),
pow_rate_limiter (pow_rate_limiter_a),
opencl (opencl_a)
{
static_assert (ATOMIC_INT_LOCK_FREE == 2, "Atomic int needed");
@ -71,6 +72,7 @@ void nano::work_pool::loop (uint64_t thread)
blake2b_state hash;
blake2b_init (&hash, sizeof (output));
std::unique_lock<std::mutex> lock (mutex);
auto pow_sleep = pow_rate_limiter;
while (!done || !pending.empty ())
{
auto empty (pending.empty ());
@ -101,6 +103,12 @@ void nano::work_pool::loop (uint64_t thread)
blake2b_init (&hash, sizeof (output));
iteration -= 1;
}
// Add a rate limiter (if specified) to the pow calculation to save some CPUs which don't want to operate at full throttle
if (pow_sleep != std::chrono::nanoseconds (0))
{
std::this_thread::sleep_for (pow_sleep);
}
}
lock.lock ();
if (ticket == ticket_l)
@ -162,24 +170,24 @@ void nano::work_pool::stop ()
producer_condition.notify_all ();
}
void nano::work_pool::generate (nano::uint256_union const & root_a, std::function<void(boost::optional<uint64_t> const &)> callback_a)
void nano::work_pool::generate (nano::uint256_union const & hash_a, std::function<void(boost::optional<uint64_t> const &)> callback_a)
{
generate (root_a, callback_a, network_params.publish_threshold);
generate (hash_a, callback_a, network_params.publish_threshold);
}
void nano::work_pool::generate (nano::uint256_union const & root_a, std::function<void(boost::optional<uint64_t> const &)> callback_a, uint64_t difficulty_a)
void nano::work_pool::generate (nano::uint256_union const & hash_a, std::function<void(boost::optional<uint64_t> const &)> callback_a, uint64_t difficulty_a)
{
assert (!root_a.is_zero ());
assert (!hash_a.is_zero ());
boost::optional<uint64_t> result;
if (opencl)
{
result = opencl (root_a, difficulty_a);
result = opencl (hash_a, difficulty_a);
}
if (!result)
{
{
std::lock_guard<std::mutex> lock (mutex);
pending.push_back ({ root_a, callback_a, difficulty_a });
pending.push_back ({ hash_a, callback_a, difficulty_a });
}
producer_condition.notify_all ();
}

View file

@ -28,7 +28,7 @@ public:
class work_pool final
{
public:
work_pool (unsigned, std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> = nullptr);
work_pool (unsigned, std::chrono::nanoseconds = std::chrono::nanoseconds (0), std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> = nullptr);
~work_pool ();
void loop (uint64_t);
void stop ();
@ -44,6 +44,7 @@ public:
std::list<nano::work_item> pending;
std::mutex mutex;
std::condition_variable producer_condition;
std::chrono::nanoseconds pow_rate_limiter;
std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> opencl;
nano::observer_set<bool> work_observers;
};

View file

@ -21,10 +21,10 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano::
config.node.logging.init (data_path);
boost::asio::io_context io_ctx;
auto opencl (nano::opencl_work::create (config.opencl_enable, config.opencl, config.node.logging));
nano::work_pool opencl_work (config.node.work_threads, opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) {
nano::work_pool opencl_work (config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) {
return opencl->generate_work (root_a, difficulty_a);
}
: std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> (nullptr));
: std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> (nullptr));
nano::alarm alarm (io_ctx);
nano::node_init init;
try

View file

@ -99,7 +99,8 @@ int main (int argc, char * const * argv)
("platform", boost::program_options::value<std::string> (), "Defines the <platform> for OpenCL commands")
("device", boost::program_options::value<std::string> (), "Defines <device> for OpenCL command")
("threads", boost::program_options::value<std::string> (), "Defines <threads> count for OpenCL command")
("difficulty", boost::program_options::value<std::string> (), "Defines <difficulty> for OpenCL command, HEX");
("difficulty", boost::program_options::value<std::string> (), "Defines <difficulty> for OpenCL command, HEX")
("pow_sleep_interval", boost::program_options::value<std::string> (), "Defines the amount to sleep inbetween each pow calculation attempt");
// clang-format on
boost::program_options::variables_map vm;
@ -164,7 +165,7 @@ int main (int argc, char * const * argv)
if (!key.decode_hex (key_it->second.as<std::string> ()))
{
nano::keypair genesis (key.to_string ());
nano::work_pool work (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool work (std::numeric_limits<unsigned>::max ());
std::cout << "Genesis: " << genesis.prv.data.to_string () << "\n"
<< "Public: " << genesis.pub.to_string () << "\n"
<< "Account: " << genesis.pub.to_account () << "\n";
@ -283,7 +284,14 @@ int main (int argc, char * const * argv)
}
else if (vm.count ("debug_profile_generate"))
{
nano::work_pool work (std::numeric_limits<unsigned>::max (), nullptr);
auto pow_rate_limiter = std::chrono::nanoseconds (0);
auto pow_sleep_interval_it = vm.find ("pow_sleep_interval");
if (pow_sleep_interval_it != vm.cend ())
{
pow_rate_limiter = std::chrono::nanoseconds (boost::lexical_cast<uint64_t> (pow_sleep_interval_it->second.as<std::string> ()));
}
nano::work_pool work (std::numeric_limits<unsigned>::max (), pow_rate_limiter);
nano::change_block block (0, 0, nano::keypair ().prv, 0, 0);
std::cerr << "Starting generation profiling\n";
while (true)
@ -364,10 +372,10 @@ int main (int argc, char * const * argv)
{
nano::logging logging;
auto opencl (nano::opencl_work::create (true, { platform, device, threads }, logging));
nano::work_pool work_pool (std::numeric_limits<unsigned>::max (), opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) {
nano::work_pool work_pool (std::numeric_limits<unsigned>::max (), std::chrono::nanoseconds (0), opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) {
return opencl->generate_work (root_a, difficulty_a);
}
: std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> (nullptr));
: std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> (nullptr));
nano::change_block block (0, 0, nano::keypair ().prv, 0, 0);
std::cerr << boost::str (boost::format ("Starting OpenCL generation profiling. Platform: %1%. Device: %2%. Threads: %3%. Difficulty: %4$#x\n") % platform % device % threads % difficulty);
for (uint64_t i (0); true; ++i)
@ -402,7 +410,7 @@ int main (int argc, char * const * argv)
}
else if (vm.count ("debug_profile_verify"))
{
nano::work_pool work (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool work (std::numeric_limits<unsigned>::max ());
nano::change_block block (0, 0, nano::keypair ().prv, 0, 0);
std::cerr << "Starting verification profiling\n";
while (true)
@ -478,7 +486,7 @@ int main (int argc, char * const * argv)
std::cerr << boost::str (boost::format ("Starting pregenerating %1% blocks\n") % max_blocks);
nano::system system (24000, 1);
nano::node_init init;
nano::work_pool work (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool work (std::numeric_limits<unsigned>::max ());
nano::logging logging;
auto path (nano::unique_path ());
logging.init (path);
@ -590,7 +598,7 @@ int main (int argc, char * const * argv)
std::cerr << boost::str (boost::format ("Starting pregenerating %1% votes\n") % max_votes);
nano::system system (24000, 1);
nano::node_init init;
nano::work_pool work (std::numeric_limits<unsigned>::max (), nullptr);
nano::work_pool work (std::numeric_limits<unsigned>::max ());
nano::logging logging;
auto path (nano::unique_path ());
logging.init (path);

View file

@ -237,10 +237,10 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost
std::shared_ptr<nano_qt::wallet> gui;
nano::set_application_icon (application);
auto opencl (nano::opencl_work::create (config.opencl_enable, config.opencl, config.node.logging));
nano::work_pool work (config.node.work_threads, opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) {
nano::work_pool work (config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) {
return opencl->generate_work (root_a, difficulty_a);
}
: std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> (nullptr));
: std::function<boost::optional<uint64_t> (nano::uint256_union const &, uint64_t)> (nullptr));
nano::alarm alarm (io_ctx);
nano::node_init init;
nano::node_flags flags;

View file

@ -3511,7 +3511,7 @@ nano::inactive_node::inactive_node (boost::filesystem::path const & path_a, uint
path (path_a),
io_context (std::make_shared<boost::asio::io_context> ()),
alarm (*io_context),
work (1, nullptr),
work (1),
peering_port (peering_port_a)
{
boost::system::error_code error_chmod;

View file

@ -8,6 +8,7 @@ namespace
{
const char * preconfigured_peers_key = "preconfigured_peers";
const char * signature_checker_threads_key = "signature_checker_threads";
const char * pow_sleep_interval_key = "pow_sleep_interval";
const char * default_beta_peer_network = "peering-beta.nano.org";
const char * default_live_peer_network = "peering.nano.org";
}
@ -112,6 +113,7 @@ nano::error nano::node_config::serialize_json (nano::jsonconfig & json) const
json.put ("unchecked_cutoff_time", unchecked_cutoff_time.count ());
json.put ("tcp_client_timeout", tcp_client_timeout.count ());
json.put ("tcp_server_timeout", tcp_server_timeout.count ());
json.put ("pow_sleep_interval", pow_sleep_interval.count ());
nano::jsonconfig websocket_l;
websocket_config.serialize_json (websocket_l);
json.put_child ("websocket", websocket_l);
@ -244,6 +246,7 @@ bool nano::node_config::upgrade_json (unsigned version_a, nano::jsonconfig & jso
json.put_child ("websocket", websocket_l);
json.put ("tcp_client_timeout", tcp_client_timeout.count ());
json.put ("tcp_server_timeout", tcp_server_timeout.count ());
json.put (pow_sleep_interval_key, pow_sleep_interval.count ());
upgraded = true;
}
case 17:
@ -379,8 +382,11 @@ nano::error nano::node_config::deserialize_json (bool & upgraded_a, nano::jsonco
json.get<bool> ("allow_local_peers", allow_local_peers);
json.get<unsigned> (signature_checker_threads_key, signature_checker_threads);
// Validate ranges
auto pow_sleep_interval_l (pow_sleep_interval.count ());
json.get (pow_sleep_interval_key, pow_sleep_interval_l);
pow_sleep_interval = std::chrono::nanoseconds (pow_sleep_interval_l);
// Validate ranges
if (online_weight_quorum > 100)
{
json.get_error ().set ("online_weight_quorum must be less than 100");

View file

@ -58,6 +58,7 @@ public:
std::chrono::seconds unchecked_cutoff_time{ std::chrono::seconds (4 * 60 * 60) }; // 4 hours
std::chrono::seconds tcp_client_timeout{ std::chrono::seconds (5) };
std::chrono::seconds tcp_server_timeout{ std::chrono::seconds (30) };
std::chrono::nanoseconds pow_sleep_interval{ 0 };
static std::chrono::seconds constexpr keepalive_period = std::chrono::seconds (60);
static std::chrono::seconds constexpr keepalive_cutoff = keepalive_period * 5;
static std::chrono::minutes constexpr wallet_backup_interval = std::chrono::minutes (5);

View file

@ -21,7 +21,7 @@ std::string nano::error_system_messages::message (int ev) const
nano::system::system (uint16_t port_a, uint16_t count_a) :
alarm (io_ctx),
work (1, nullptr)
work (1)
{
auto scale_str = std::getenv ("DEADLINE_SCALE_FACTOR");
if (scale_str)