From 9826039373ca8a80b9644f702ba61f428e04fbd6 Mon Sep 17 00:00:00 2001 From: cohcho Date: Fri, 9 Oct 2020 14:31:52 +0000 Subject: [PATCH] Hashrate: display numerical error Different workers store stats asynchronously and with different frequency. It has direct impact on accuracy of hashrate calculations. Error calculation overhead balanced with faster iteration due to tail index - Hashrate is being calculated as previously - Error is being displayed when it's above zero --- src/backend/common/Hashrate.cpp | 133 ++++++++++++++++++-------------- src/backend/common/Hashrate.h | 27 ++++++- src/core/Miner.cpp | 19 ++--- 3 files changed, 107 insertions(+), 72 deletions(-) diff --git a/src/backend/common/Hashrate.cpp b/src/backend/common/Hashrate.cpp index 88709102..30b39288 100644 --- a/src/backend/common/Hashrate.cpp +++ b/src/backend/common/Hashrate.cpp @@ -34,12 +34,18 @@ #include "base/io/json/Json.h" #include "base/tools/Chrono.h" #include "base/tools/Handle.h" +#include "base/io/log/Log.h" -inline static const char *format(double h, char *buf, size_t size) +inline static const char *format(double h, char *buf, size_t size, double errorDown, double errorUp) { if (std::isnormal(h)) { - snprintf(buf, size, (h < 100.0) ? "%04.2f" : "%03.1f", h); + if (std::max(errorDown, errorUp) >= (h < 100.0 ? 0.01 : 0.1)) { + snprintf(buf, size, (h < 100.0) ? "%5.2f" BLACK_BOLD("(-%.2f/+%.2f}") : "%5.1f" BLACK_BOLD("(-%.1f/+%.1f)"), h, errorDown, errorUp); + } + else { + snprintf(buf, size, (h < 100.0) ? "%5.2f" : "%5.1f", h); + } return buf; } @@ -52,12 +58,16 @@ xmrig::Hashrate::Hashrate(size_t threads) : { m_counts = new uint64_t*[threads]; m_timestamps = new uint64_t*[threads]; - m_top = new uint32_t[threads]; + m_head = new uint32_t[threads]; + m_tail = new uint32_t*[threads]; + const uint64_t now = xmrig::Chrono::highResolutionMSecs(); for (size_t i = 0; i < threads; i++) { m_counts[i] = new uint64_t[kBucketSize](); m_timestamps[i] = new uint64_t[kBucketSize](); - m_top[i] = 0; + m_head[i] = 0; + m_tail[i] = new uint32_t[3](); + m_timestamps[i][0] = now; } } @@ -67,92 +77,97 @@ xmrig::Hashrate::~Hashrate() for (size_t i = 0; i < m_threads; i++) { delete [] m_counts[i]; delete [] m_timestamps[i]; + delete [] m_tail[i]; } delete [] m_counts; delete [] m_timestamps; - delete [] m_top; + delete [] m_head; + delete [] m_tail; } -double xmrig::Hashrate::calc(size_t ms) const + +xmrig::Hashrate::Value xmrig::Hashrate::calc(size_t threadId, Intervals ms) const { - double result = 0.0; - double data; - - for (size_t i = 0; i < m_threads; ++i) { - data = calc(i, ms); - if (std::isnormal(data)) { - result += data; - } + TimeRange time; + uint64_t count; + if (!findRecords(threadId, ms, time, count)) { + return { nan(""), nan(""), nan("") }; } + return { count * 1000.0 / (time.second - time.first), 0.0, 0.0 }; +} - return result; +xmrig::Hashrate::Value xmrig::Hashrate::calc(Intervals ms) const +{ + const uint64_t now = xmrig::Chrono::highResolutionMSecs(); + uint64_t time_earliest[2] = { now, 0 }; + uint64_t time_latest[2] = { 0, now }; + uint64_t total_count = 0; + double result = 0.0; + for (size_t threadId = 0; threadId < m_threads; ++threadId) { + TimeRange time; + uint64_t count; + if (!findRecords(threadId, ms, time, count)) { + continue; + } + for (size_t j = 0; j < 2; ++j) { + if ((time.first < time_earliest[j]) ^ j) { + time_earliest[j] = time.first; + } + if ((time.second > time_latest[j]) ^ j) { + time_latest[j] = time.second; + } + } + total_count += count; + result += count * 1000.0 / (time.second - time.first); + } + const double lower = total_count * 1000.0 / (time_latest[0] - time_earliest[0]); + const double upper = total_count * 1000.0 / (time_latest[1] - time_earliest[1]); + return { result, result - lower, upper - result }; } -double xmrig::Hashrate::calc(size_t threadId, size_t ms) const +bool xmrig::Hashrate::findRecords(size_t threadId, Intervals ms, TimeRange &time, uint64_t &count) const { assert(threadId < m_threads); if (threadId >= m_threads) { - return nan(""); + return false; } - uint64_t earliestHashCount = 0; - uint64_t earliestStamp = 0; - bool haveFullSet = false; - const uint64_t timeStampLimit = xmrig::Chrono::highResolutionMSecs() - ms; - uint64_t* timestamps = m_timestamps[threadId]; - uint64_t* counts = m_counts[threadId]; - - const size_t idx_start = (m_top[threadId] - 1) & kBucketMask; - size_t idx = idx_start; - - uint64_t lastestStamp = timestamps[idx]; - uint64_t lastestHashCnt = counts[idx]; - - do { - if (timestamps[idx] < timeStampLimit) { - haveFullSet = (timestamps[idx] != 0); - if (idx != idx_start) { - idx = (idx + 1) & kBucketMask; - earliestStamp = timestamps[idx]; - earliestHashCount = counts[idx]; - } - break; + const uint32_t head = m_head[threadId]; + // time[tale] < timeStampLimit <= time[later_tail] <= time[head] + uint32_t &tail = m_tail[threadId][ms == ShortInterval ? 0 : (ms == MediumInterval ? 1 : 2)]; + if (m_timestamps[threadId][tail] >= timeStampLimit) { + return false; + } + while (tail != head) { + const uint32_t later_tail = (tail + 1) & kBucketMask; + if (m_timestamps[threadId][later_tail] >= timeStampLimit) { + time = { m_timestamps[threadId][later_tail], m_timestamps[threadId][head] }; + count = m_counts[threadId][head] - m_counts[threadId][later_tail]; + return true; } - idx = (idx - 1) & kBucketMask; - } while (idx != idx_start); - - if (!haveFullSet || earliestStamp == 0 || lastestStamp == 0) { - return nan(""); + tail = later_tail; } - - if (lastestStamp - earliestStamp == 0) { - return nan(""); - } - - const auto hashes = static_cast(lastestHashCnt - earliestHashCount); - const auto time = static_cast(lastestStamp - earliestStamp) / 1000.0; - - return hashes / time; + return false; } void xmrig::Hashrate::add(size_t threadId, uint64_t count, uint64_t timestamp) { - const size_t top = m_top[threadId]; - m_counts[threadId][top] = count; - m_timestamps[threadId][top] = timestamp; + const uint32_t head = (m_head[threadId] + 1) & kBucketMask; + m_counts[threadId][head] = count; + m_timestamps[threadId][head] = timestamp; - m_top[threadId] = (top + 1) & kBucketMask; + m_head[threadId] = head; } -const char *xmrig::Hashrate::format(double h, char *buf, size_t size) +const char *xmrig::Hashrate::format(Value h, char *buf, size_t size) { - return ::format(h, buf, size); + return ::format(h.estimate, buf, size, h.errorDown, h.errorUp); } diff --git a/src/backend/common/Hashrate.h b/src/backend/common/Hashrate.h index 59e1afe1..89ed39b3 100644 --- a/src/backend/common/Hashrate.h +++ b/src/backend/common/Hashrate.h @@ -28,6 +28,7 @@ #include #include +#include #include "3rdparty/rapidjson/fwd.h" @@ -42,6 +43,21 @@ class Hashrate public: XMRIG_DISABLE_COPY_MOVE_DEFAULT(Hashrate) + struct Value { + Value() = default; + Value(const double estimate, const double errorDown, const double errorUp): estimate(estimate), errorDown(errorDown), errorUp(errorUp) {} + + inline operator double() const { return estimate; } + inline Value operator *(const double v) const { return { estimate * v , errorDown * v, errorUp * v}; } + inline Value& operator +=(const Value &o) { estimate += o.estimate; errorDown += o.errorDown; errorUp += o.errorUp; return *this; } + + double estimate; + double errorDown; + double errorUp; + }; + + typedef std::pair TimeRange; + enum Intervals { ShortInterval = 10000, MediumInterval = 60000, @@ -50,13 +66,13 @@ public: Hashrate(size_t threads); ~Hashrate(); - double calc(size_t ms) const; - double calc(size_t threadId, size_t ms) const; + Value calc(Intervals ms) const; + Value calc(size_t threadId, Intervals ms) const; void add(size_t threadId, uint64_t count, uint64_t timestamp); inline size_t threads() const { return m_threads; } - static const char *format(double h, char *buf, size_t size); + static const char *format(Value h, char *buf, size_t size); static rapidjson::Value normalize(double d); # ifdef XMRIG_FEATURE_API @@ -65,11 +81,14 @@ public: # endif private: + bool findRecords(size_t threadId, Intervals ms, TimeRange &time, uint64_t &count) const; + constexpr static size_t kBucketSize = 2 << 11; constexpr static size_t kBucketMask = kBucketSize - 1; size_t m_threads; - uint32_t* m_top; + uint32_t* m_head; + uint32_t** m_tail; uint64_t** m_counts; uint64_t** m_timestamps; }; diff --git a/src/core/Miner.cpp b/src/core/Miner.cpp index d92e9915..844c51b0 100644 --- a/src/core/Miner.cpp +++ b/src/core/Miner.cpp @@ -244,8 +244,9 @@ public: void printHashrate(bool details) { - char num[16 * 4] = { 0 }; - double speed[3] = { 0.0 }; + constexpr size_t num_width = 32; + char num[num_width * 4] = {}; + Hashrate::Value speed[3] = {}; for (auto backend : backends) { const auto hashrate = backend->hashrate(); @@ -304,12 +305,12 @@ public: } # endif - LOG_INFO("%s " WHITE_BOLD("speed") " 10s/60s/15m " CYAN_BOLD("%s") CYAN(" %s %s ") CYAN_BOLD("%s") " max " CYAN_BOLD("%s %s"), + LOG_INFO("%s " WHITE_BOLD("speed") " 10s/60s/15m " CYAN_BOLD("%s") " " CYAN("%s") " " CYAN("%s") " " CYAN_BOLD("%s") " max " CYAN_BOLD("%s %s"), Tags::miner(), - Hashrate::format(speed[0] * scale, num, sizeof(num) / 4), - Hashrate::format(speed[1] * scale, num + 16, sizeof(num) / 4), - Hashrate::format(speed[2] * scale, num + 16 * 2, sizeof(num) / 4), h, - Hashrate::format(maxHashrate[algorithm] * scale, num + 16 * 3, sizeof(num) / 4), h + Hashrate::format(speed[0] * scale, num, num_width), + Hashrate::format(speed[1] * scale, num + num_width, num_width), + Hashrate::format(speed[2] * scale, num + num_width, num_width), h, + Hashrate::format(maxHashrate[algorithm] * scale, num + num_width * 3, num_width), h ); } @@ -327,7 +328,7 @@ public: bool battery_power = false; Controller *controller; Job job; - mutable std::map maxHashrate; + mutable std::map maxHashrate; std::vector backends; String userJobId; Timer *timer = nullptr; @@ -570,7 +571,7 @@ void xmrig::Miner::onConfigChanged(Config *config, Config *previousConfig) void xmrig::Miner::onTimer(const Timer *) { - double maxHashrate = 0.0; + Hashrate::Value maxHashrate{}; const auto healthPrintTime = d_ptr->controller->config()->healthPrintTime(); for (IBackend *backend : d_ptr->backends) {