From 3fbf2ac3d43e4999243e40ea7a07a00ec7b44c44 Mon Sep 17 00:00:00 2001 From: SChernykh Date: Sat, 10 Oct 2020 11:18:01 +0200 Subject: [PATCH 1/2] More precise hashrate calculation - Use only steady timestamp counters to guarantee correctness - CPU backend: directly measure total hashrate using raw hash counters from each thread; update data more often on ARM CPUs because they're slower - GPU backends: directly measure total hashrate too, but use interpolator with 4 second lag to fix variance from batches of hashes Total hashrate is now measured directly (realtime for CPU, 4 seconds lag for GPU), so it might differ a bit from the sum of all thread hashrates because data points are taken at different moments in time. Overhead is reduced a lot since it doesn't have to go through all threads to calculate max total hashrate on every timer tick (2 times a second). --- src/backend/common/Hashrate.cpp | 31 ++++------ src/backend/common/HashrateInterpolator.cpp | 65 +++++++++++++++++++++ src/backend/common/HashrateInterpolator.h | 57 ++++++++++++++++++ src/backend/common/Worker.cpp | 2 +- src/backend/common/Worker.h | 1 + src/backend/common/Workers.cpp | 10 +++- src/backend/common/common.cmake | 2 + src/backend/common/interfaces/IWorker.h | 1 + src/backend/cpu/CpuBackend.cpp | 6 +- src/backend/cpu/CpuWorker.cpp | 2 + src/backend/cuda/CudaBackend.cpp | 12 ++-- src/backend/cuda/CudaWorker.cpp | 9 +++ src/backend/cuda/CudaWorker.h | 4 ++ src/backend/opencl/OclBackend.cpp | 12 ++-- src/backend/opencl/OclWorker.cpp | 11 +++- src/backend/opencl/OclWorker.h | 4 ++ src/core/Miner.cpp | 2 +- 17 files changed, 192 insertions(+), 39 deletions(-) create mode 100644 src/backend/common/HashrateInterpolator.cpp create mode 100644 src/backend/common/HashrateInterpolator.h diff --git a/src/backend/common/Hashrate.cpp b/src/backend/common/Hashrate.cpp index 88709102..aa4d80c7 100644 --- a/src/backend/common/Hashrate.cpp +++ b/src/backend/common/Hashrate.cpp @@ -48,13 +48,13 @@ inline static const char *format(double h, char *buf, size_t size) xmrig::Hashrate::Hashrate(size_t threads) : - m_threads(threads) + m_threads(threads + 1) { - m_counts = new uint64_t*[threads]; - m_timestamps = new uint64_t*[threads]; - m_top = new uint32_t[threads]; + m_counts = new uint64_t*[m_threads]; + m_timestamps = new uint64_t*[m_threads]; + m_top = new uint32_t[m_threads]; - for (size_t i = 0; i < threads; i++) { + for (size_t i = 0; i < m_threads; i++) { m_counts[i] = new uint64_t[kBucketSize](); m_timestamps[i] = new uint64_t[kBucketSize](); m_top[i] = 0; @@ -77,17 +77,8 @@ xmrig::Hashrate::~Hashrate() double xmrig::Hashrate::calc(size_t 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; - } - } - - return result; + const double data = calc(0, ms); + return std::isnormal(data) ? data : 0.0; } @@ -102,7 +93,7 @@ double xmrig::Hashrate::calc(size_t threadId, size_t ms) const uint64_t earliestStamp = 0; bool haveFullSet = false; - const uint64_t timeStampLimit = xmrig::Chrono::highResolutionMSecs() - ms; + const uint64_t timeStampLimit = xmrig::Chrono::steadyMSecs() - ms; uint64_t* timestamps = m_timestamps[threadId]; uint64_t* counts = m_counts[threadId]; @@ -183,9 +174,9 @@ rapidjson::Value xmrig::Hashrate::toJSON(size_t threadId, rapidjson::Document &d auto &allocator = doc.GetAllocator(); Value out(kArrayType); - out.PushBack(normalize(calc(threadId, ShortInterval)), allocator); - out.PushBack(normalize(calc(threadId, MediumInterval)), allocator); - out.PushBack(normalize(calc(threadId, LargeInterval)), allocator); + out.PushBack(normalize(calc(threadId + 1, ShortInterval)), allocator); + out.PushBack(normalize(calc(threadId + 1, MediumInterval)), allocator); + out.PushBack(normalize(calc(threadId + 1, LargeInterval)), allocator); return out; } diff --git a/src/backend/common/HashrateInterpolator.cpp b/src/backend/common/HashrateInterpolator.cpp new file mode 100644 index 00000000..0fa339a1 --- /dev/null +++ b/src/backend/common/HashrateInterpolator.cpp @@ -0,0 +1,65 @@ +/* XMRig + * Copyright 2010 Jeff Garzik + * Copyright 2012-2014 pooler + * Copyright 2014 Lucas Jones + * Copyright 2014-2016 Wolf9466 + * Copyright 2016 Jay D Dee + * Copyright 2017-2018 XMR-Stak , + * Copyright 2018-2020 SChernykh + * Copyright 2016-2020 XMRig , + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "backend/common/HashrateInterpolator.h" + + +uint64_t xmrig::HashrateInterpolator::interpolate(uint64_t timeStamp) const +{ + timeStamp -= LagMS; + + std::lock_guard l(m_lock); + + const size_t N = m_data.size(); + + if (N < 2) { + return 0; + } + + for (size_t i = 0; i < N - 1; ++i) { + const auto& a = m_data[i]; + const auto& b = m_data[i + 1]; + + if (a.second <= timeStamp && timeStamp <= b.second) { + return a.first + static_cast(b.first - a.first) * (timeStamp - a.second) / (b.second - a.second); + } + } + + return 0; +} + +void xmrig::HashrateInterpolator::addDataPoint(uint64_t count, uint64_t timeStamp) +{ + std::lock_guard l(m_lock); + + // Clean up old data + if (!m_data.empty()) { + while (timeStamp - m_data.front().second > LagMS * 2) { + m_data.pop_front(); + } + } + + m_data.emplace_back(count, timeStamp); +} diff --git a/src/backend/common/HashrateInterpolator.h b/src/backend/common/HashrateInterpolator.h new file mode 100644 index 00000000..b4c7b8c7 --- /dev/null +++ b/src/backend/common/HashrateInterpolator.h @@ -0,0 +1,57 @@ +/* XMRig + * Copyright 2010 Jeff Garzik + * Copyright 2012-2014 pooler + * Copyright 2014 Lucas Jones + * Copyright 2014-2016 Wolf9466 + * Copyright 2016 Jay D Dee + * Copyright 2017-2018 XMR-Stak , + * Copyright 2018-2020 SChernykh + * Copyright 2016-2020 XMRig , + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef XMRIG_HASHRATE_INTERPOLATOR_H +#define XMRIG_HASHRATE_INTERPOLATOR_H + + +#include +#include +#include + + +namespace xmrig { + + +class HashrateInterpolator +{ +public: + enum { + LagMS = 4000, + }; + + uint64_t interpolate(uint64_t timeStamp) const; + void addDataPoint(uint64_t count, uint64_t timeStamp); + +private: + // Buffer of hashrate counters, used for linear interpolation of past data + mutable std::mutex m_lock; + std::deque> m_data; +}; + + +} // namespace xmrig + + +#endif /* XMRIG_HASHRATE_INTERPOLATOR_H */ diff --git a/src/backend/common/Worker.cpp b/src/backend/common/Worker.cpp index a8dd24ab..b642a25c 100644 --- a/src/backend/common/Worker.cpp +++ b/src/backend/common/Worker.cpp @@ -48,7 +48,7 @@ void xmrig::Worker::storeStats() // Fill in the data for that index m_hashCount[index] = m_count; - m_timestamp[index] = Chrono::highResolutionMSecs(); + m_timestamp[index] = Chrono::steadyMSecs(); // Switch to that index // All data will be in memory by the time it completes thanks to std::memory_order_seq_cst diff --git a/src/backend/common/Worker.h b/src/backend/common/Worker.h index a38d7387..b0825811 100644 --- a/src/backend/common/Worker.h +++ b/src/backend/common/Worker.h @@ -44,6 +44,7 @@ public: inline const VirtualMemory *memory() const override { return nullptr; } inline size_t id() const override { return m_id; } + inline uint64_t rawHashes() const override { return m_count; } void getHashrateData(uint64_t& hashCount, uint64_t& timeStamp) const override; inline void jobEarlyNotification(const Job&) override {} diff --git a/src/backend/common/Workers.cpp b/src/backend/common/Workers.cpp index 56c4bd1a..89c44a77 100644 --- a/src/backend/common/Workers.cpp +++ b/src/backend/common/Workers.cpp @@ -29,6 +29,7 @@ #include "backend/common/Workers.h" #include "backend/cpu/CpuWorker.h" #include "base/io/log/Log.h" +#include "base/tools/Chrono.h" #include "base/tools/Object.h" @@ -142,13 +143,20 @@ void xmrig::Workers::tick(uint64_t) return; } + uint64_t totalHashCount = 0; + for (Thread *handle : m_workers) { if (handle->worker()) { uint64_t hashCount, timeStamp; handle->worker()->getHashrateData(hashCount, timeStamp); - d_ptr->hashrate->add(handle->id(), hashCount, timeStamp); + d_ptr->hashrate->add(handle->id() + 1, hashCount, timeStamp); + totalHashCount += handle->worker()->rawHashes(); } } + + if (totalHashCount > 0) { + d_ptr->hashrate->add(0, totalHashCount, Chrono::steadyMSecs()); + } } diff --git a/src/backend/common/common.cmake b/src/backend/common/common.cmake index 03c37c8f..832f53ff 100644 --- a/src/backend/common/common.cmake +++ b/src/backend/common/common.cmake @@ -1,5 +1,6 @@ set(HEADERS_BACKEND_COMMON src/backend/common/Hashrate.h + src/backend/common/HashrateInterpolator.h src/backend/common/Tags.h src/backend/common/interfaces/IBackend.h src/backend/common/interfaces/IRxListener.h @@ -15,6 +16,7 @@ set(HEADERS_BACKEND_COMMON set(SOURCES_BACKEND_COMMON src/backend/common/Hashrate.cpp + src/backend/common/HashrateInterpolator.cpp src/backend/common/Threads.cpp src/backend/common/Worker.cpp src/backend/common/Workers.cpp diff --git a/src/backend/common/interfaces/IWorker.h b/src/backend/common/interfaces/IWorker.h index 9a0e6f41..17048c64 100644 --- a/src/backend/common/interfaces/IWorker.h +++ b/src/backend/common/interfaces/IWorker.h @@ -46,6 +46,7 @@ public: virtual const VirtualMemory *memory() const = 0; virtual size_t id() const = 0; virtual size_t intensity() const = 0; + virtual uint64_t rawHashes() const = 0; virtual void getHashrateData(uint64_t&, uint64_t&) const = 0; virtual void start() = 0; virtual void jobEarlyNotification(const Job&) = 0; diff --git a/src/backend/cpu/CpuBackend.cpp b/src/backend/cpu/CpuBackend.cpp index ee22bf2a..e6678bca 100644 --- a/src/backend/cpu/CpuBackend.cpp +++ b/src/backend/cpu/CpuBackend.cpp @@ -304,9 +304,9 @@ void xmrig::CpuBackend::printHashrate(bool details) Log::print("| %8zu | %8" PRId64 " | %7s | %7s | %7s |", i, data.affinity, - Hashrate::format(hashrate()->calc(i, Hashrate::ShortInterval), num, sizeof num / 3), - Hashrate::format(hashrate()->calc(i, Hashrate::MediumInterval), num + 8, sizeof num / 3), - Hashrate::format(hashrate()->calc(i, Hashrate::LargeInterval), num + 8 * 2, sizeof num / 3) + Hashrate::format(hashrate()->calc(i + 1, Hashrate::ShortInterval), num, sizeof num / 3), + Hashrate::format(hashrate()->calc(i + 1, Hashrate::MediumInterval), num + 8, sizeof num / 3), + Hashrate::format(hashrate()->calc(i + 1, Hashrate::LargeInterval), num + 8 * 2, sizeof num / 3) ); i++; diff --git a/src/backend/cpu/CpuWorker.cpp b/src/backend/cpu/CpuWorker.cpp index fa0b96b0..8565acfb 100644 --- a/src/backend/cpu/CpuWorker.cpp +++ b/src/backend/cpu/CpuWorker.cpp @@ -218,9 +218,11 @@ void xmrig::CpuWorker::start() alignas(16) uint64_t tempHash[8] = {}; // RandomX is faster, we don't need to store stats so often +# ifndef XMRIG_ARM if (m_job.currentJob().algorithm().family() == Algorithm::RANDOM_X) { storeStatsMask = 63; } +# endif # endif while (!Nonce::isOutdated(Nonce::CPU, m_job.sequence())) { diff --git a/src/backend/cuda/CudaBackend.cpp b/src/backend/cuda/CudaBackend.cpp index ed23d4d3..4d02ccba 100644 --- a/src/backend/cuda/CudaBackend.cpp +++ b/src/backend/cuda/CudaBackend.cpp @@ -409,9 +409,9 @@ void xmrig::CudaBackend::printHashrate(bool details) Log::print("| %8zu | %8" PRId64 " | %8s | %8s | %8s |" CYAN_BOLD(" #%u") YELLOW(" %s") GREEN(" %s"), i, data.thread.affinity(), - Hashrate::format(hashrate()->calc(i, Hashrate::ShortInterval) * scale, num, sizeof num / 3), - Hashrate::format(hashrate()->calc(i, Hashrate::MediumInterval) * scale, num + 16, sizeof num / 3), - Hashrate::format(hashrate()->calc(i, Hashrate::LargeInterval) * scale, num + 16 * 2, sizeof num / 3), + Hashrate::format(hashrate()->calc(i + 1, Hashrate::ShortInterval) * scale, num, sizeof num / 3), + Hashrate::format(hashrate()->calc(i + 1, Hashrate::MediumInterval) * scale, num + 16, sizeof num / 3), + Hashrate::format(hashrate()->calc(i + 1, Hashrate::LargeInterval) * scale, num + 16 * 2, sizeof num / 3), data.device.index(), data.device.topology().toString().data(), data.device.name().data() @@ -421,9 +421,9 @@ void xmrig::CudaBackend::printHashrate(bool details) } Log::print(WHITE_BOLD_S "| - | - | %8s | %8s | %8s |", - Hashrate::format(hashrate()->calc(Hashrate::ShortInterval) * scale, num, sizeof num / 3), - Hashrate::format(hashrate()->calc(Hashrate::MediumInterval) * scale, num + 16, sizeof num / 3), - Hashrate::format(hashrate()->calc(Hashrate::LargeInterval) * scale, num + 16 * 2, sizeof num / 3) + Hashrate::format(hashrate_short * scale, num, sizeof num / 3), + Hashrate::format(hashrate_medium * scale, num + 16, sizeof num / 3), + Hashrate::format(hashrate_large * scale, num + 16 * 2, sizeof num / 3) ); } diff --git a/src/backend/cuda/CudaWorker.cpp b/src/backend/cuda/CudaWorker.cpp index c8cb6f73..1f00748c 100644 --- a/src/backend/cuda/CudaWorker.cpp +++ b/src/backend/cuda/CudaWorker.cpp @@ -120,6 +120,12 @@ xmrig::CudaWorker::~CudaWorker() } +uint64_t xmrig::CudaWorker::rawHashes() const +{ + return m_hashrateData.interpolate(Chrono::steadyMSecs()); +} + + void xmrig::CudaWorker::jobEarlyNotification(const Job& job) { if (m_runner) { @@ -207,5 +213,8 @@ void xmrig::CudaWorker::storeStats() m_count += m_runner ? m_runner->processedHashes() : 0; + const uint64_t timeStamp = Chrono::steadyMSecs(); + m_hashrateData.addDataPoint(m_count, timeStamp); + Worker::storeStats(); } diff --git a/src/backend/cuda/CudaWorker.h b/src/backend/cuda/CudaWorker.h index 3f4e713a..e82e3425 100644 --- a/src/backend/cuda/CudaWorker.h +++ b/src/backend/cuda/CudaWorker.h @@ -27,6 +27,7 @@ #define XMRIG_CUDAWORKER_H +#include "backend/common/HashrateInterpolator.h" #include "backend/common/Worker.h" #include "backend/common/WorkerJob.h" #include "backend/cuda/CudaLaunchData.h" @@ -49,6 +50,7 @@ public: ~CudaWorker() override; + uint64_t rawHashes() const override; void jobEarlyNotification(const Job&) override; static std::atomic ready; @@ -67,6 +69,8 @@ private: ICudaRunner *m_runner = nullptr; WorkerJob<1> m_job; uint32_t m_deviceIndex; + + HashrateInterpolator m_hashrateData; }; diff --git a/src/backend/opencl/OclBackend.cpp b/src/backend/opencl/OclBackend.cpp index c21c63c3..764df70f 100644 --- a/src/backend/opencl/OclBackend.cpp +++ b/src/backend/opencl/OclBackend.cpp @@ -385,9 +385,9 @@ void xmrig::OclBackend::printHashrate(bool details) Log::print("| %8zu | %8" PRId64 " | %8s | %8s | %8s |" CYAN_BOLD(" #%u") YELLOW(" %s") " %s", i, data.affinity, - Hashrate::format(hashrate()->calc(i, Hashrate::ShortInterval) * scale, num, sizeof num / 3), - Hashrate::format(hashrate()->calc(i, Hashrate::MediumInterval) * scale, num + 16, sizeof num / 3), - Hashrate::format(hashrate()->calc(i, Hashrate::LargeInterval) * scale, num + 16 * 2, sizeof num / 3), + Hashrate::format(hashrate()->calc(i + 1, Hashrate::ShortInterval) * scale, num, sizeof num / 3), + Hashrate::format(hashrate()->calc(i + 1, Hashrate::MediumInterval) * scale, num + 16, sizeof num / 3), + Hashrate::format(hashrate()->calc(i + 1, Hashrate::LargeInterval) * scale, num + 16 * 2, sizeof num / 3), data.device.index(), data.device.topology().toString().data(), data.device.printableName().data() @@ -397,9 +397,9 @@ void xmrig::OclBackend::printHashrate(bool details) } Log::print(WHITE_BOLD_S "| - | - | %8s | %8s | %8s |", - Hashrate::format(hashrate()->calc(Hashrate::ShortInterval) * scale, num, sizeof num / 3), - Hashrate::format(hashrate()->calc(Hashrate::MediumInterval) * scale, num + 16, sizeof num / 3), - Hashrate::format(hashrate()->calc(Hashrate::LargeInterval) * scale, num + 16 * 2, sizeof num / 3) + Hashrate::format(hashrate_short * scale, num, sizeof num / 3), + Hashrate::format(hashrate_medium * scale, num + 16, sizeof num / 3), + Hashrate::format(hashrate_large * scale, num + 16 * 2, sizeof num / 3) ); } diff --git a/src/backend/opencl/OclWorker.cpp b/src/backend/opencl/OclWorker.cpp index 0526e9a7..c8adff81 100644 --- a/src/backend/opencl/OclWorker.cpp +++ b/src/backend/opencl/OclWorker.cpp @@ -140,6 +140,12 @@ xmrig::OclWorker::~OclWorker() } +uint64_t xmrig::OclWorker::rawHashes() const +{ + return m_hashrateData.interpolate(Chrono::steadyMSecs()); +} + + void xmrig::OclWorker::jobEarlyNotification(const Job& job) { if (m_runner) { @@ -247,8 +253,11 @@ void xmrig::OclWorker::storeStats(uint64_t t) } m_count += m_runner->processedHashes(); + const uint64_t timeStamp = Chrono::steadyMSecs(); - m_sharedData.setRunTime(Chrono::steadyMSecs() - t); + m_hashrateData.addDataPoint(m_count, timeStamp); + + m_sharedData.setRunTime(timeStamp - t); Worker::storeStats(); } diff --git a/src/backend/opencl/OclWorker.h b/src/backend/opencl/OclWorker.h index fc666f80..c62dc27c 100644 --- a/src/backend/opencl/OclWorker.h +++ b/src/backend/opencl/OclWorker.h @@ -27,6 +27,7 @@ #define XMRIG_OCLWORKER_H +#include "backend/common/HashrateInterpolator.h" #include "backend/common/Worker.h" #include "backend/common/WorkerJob.h" #include "backend/opencl/OclLaunchData.h" @@ -50,6 +51,7 @@ public: ~OclWorker() override; + uint64_t rawHashes() const override; void jobEarlyNotification(const Job&) override; static std::atomic ready; @@ -70,6 +72,8 @@ private: OclSharedData &m_sharedData; WorkerJob<1> m_job; uint32_t m_deviceIndex; + + HashrateInterpolator m_hashrateData; }; diff --git a/src/core/Miner.cpp b/src/core/Miner.cpp index d92e9915..5dab2ebb 100644 --- a/src/core/Miner.cpp +++ b/src/core/Miner.cpp @@ -203,7 +203,7 @@ public: continue; } - for (size_t i = 0; i < hr->threads(); i++) { + for (size_t i = 1; i < hr->threads(); i++) { Value thread(kArrayType); thread.PushBack(Hashrate::normalize(hr->calc(i, Hashrate::ShortInterval)), allocator); thread.PushBack(Hashrate::normalize(hr->calc(i, Hashrate::MediumInterval)), allocator); From 22a69f70da663fbe4f0365e5740bab505fc793cc Mon Sep 17 00:00:00 2001 From: SChernykh Date: Sat, 10 Oct 2020 11:22:19 +0200 Subject: [PATCH 2/2] Fix HashrateInterpolator::addDataPoint --- src/backend/common/HashrateInterpolator.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/backend/common/HashrateInterpolator.cpp b/src/backend/common/HashrateInterpolator.cpp index 0fa339a1..2edda6a6 100644 --- a/src/backend/common/HashrateInterpolator.cpp +++ b/src/backend/common/HashrateInterpolator.cpp @@ -55,10 +55,8 @@ void xmrig::HashrateInterpolator::addDataPoint(uint64_t count, uint64_t timeStam std::lock_guard l(m_lock); // Clean up old data - if (!m_data.empty()) { - while (timeStamp - m_data.front().second > LagMS * 2) { - m_data.pop_front(); - } + while (!m_data.empty() && (timeStamp - m_data.front().second > LagMS * 2)) { + m_data.pop_front(); } m_data.emplace_back(count, timeStamp);