perf(mining): run the mine-when-idle check at 1 Hz, probe the GPU sparingly
checkIdleMining ran every frame: an X11 idle query and, with GPU-aware idle detection, an nvidia-smi launch on the UI thread per frame. - The check runs once a second, since its decisions work on a seconds scale (the idle delay is tens of seconds or more). A miner it started or scaled up still yields on the first frame that sees the user back: that path skips the gate, because frames can be seconds apart while the app idles. - The GPU is probed only when input is idle and the result can change a decision, at most every 5 s, with hysteresis (busy at 10%+, clear below 5%) so a GPU hovering near 10% can't keep restarting the miner. - Thread scaling returns early when no miner is running or a toggle is in flight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -966,7 +966,7 @@ void App::update()
|
|||||||
checkAutoLock();
|
checkAutoLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mine-when-idle check (runs every frame, internally rate-limited by idle detection)
|
// Mine-when-idle check (cheap on most frames: it gates itself to 1 Hz)
|
||||||
checkIdleMining();
|
checkIdleMining();
|
||||||
|
|
||||||
// P8: Dedup rebuildAddressList — only rebuild once per frame
|
// P8: Dedup rebuildAddressList — only rebuild once per frame
|
||||||
|
|||||||
@@ -1604,6 +1604,9 @@ private:
|
|||||||
|
|
||||||
// Mine-when-idle: auto-start/stop mining based on system idle state
|
// Mine-when-idle: auto-start/stop mining based on system idle state
|
||||||
bool idle_mining_active_ = false; // true when mining was auto-started by idle detection
|
bool idle_mining_active_ = false; // true when mining was auto-started by idle detection
|
||||||
|
std::chrono::steady_clock::time_point idle_mining_next_check_{}; // checkIdleMining runs at 1 Hz
|
||||||
|
std::chrono::steady_clock::time_point idle_gpu_next_sample_{}; // GPU probe at most every 5 s
|
||||||
|
bool idle_gpu_busy_ = false; // last GPU-aware verdict (with hysteresis)
|
||||||
bool idle_scaled_to_idle_ = false; // true when threads have been scaled up for idle
|
bool idle_scaled_to_idle_ = false; // true when threads have been scaled up for idle
|
||||||
|
|
||||||
// Private methods - rendering
|
// Private methods - rendering
|
||||||
|
|||||||
+38
-13
@@ -752,6 +752,17 @@ void App::checkIdleMining() {
|
|||||||
// Skip idle mining adjustments while thread benchmark is running
|
// Skip idle mining adjustments while thread benchmark is running
|
||||||
if (ui::IsMiningBenchmarkActive()) return;
|
if (ui::IsMiningBenchmarkActive()) return;
|
||||||
|
|
||||||
|
// The decisions below work on a seconds scale (the idle delay is tens of seconds or more), so check
|
||||||
|
// at 1 Hz. This used to run every frame: an X11 idle query and, with GPU-aware idle, an nvidia-smi
|
||||||
|
// launch on the UI thread per frame. Steady clock rather than ImGui::GetTime(), whose clock stops
|
||||||
|
// while minimized (update() still runs then).
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
// ...except while a miner this feature started (or scaled up) is running: it must yield on the first
|
||||||
|
// frame that sees the user back, and frames can be seconds apart when the app idles, so a gated frame
|
||||||
|
// may be the only one carrying that input. The idle query is cheap; the GPU probe has its own cache.
|
||||||
|
if (!(idle_mining_active_ || idle_scaled_to_idle_) && now < idle_mining_next_check_) return;
|
||||||
|
idle_mining_next_check_ = now + std::chrono::seconds(1);
|
||||||
|
|
||||||
int idleSec = util::Platform::getSystemIdleSeconds();
|
int idleSec = util::Platform::getSystemIdleSeconds();
|
||||||
int delay = settings_->getMineIdleDelay();
|
int delay = settings_->getMineIdleDelay();
|
||||||
bool isPool = settings_->getPoolMode() || !supportsSoloMining();
|
bool isPool = settings_->getPoolMode() || !supportsSoloMining();
|
||||||
@@ -759,26 +770,40 @@ void App::checkIdleMining() {
|
|||||||
bool threadScaling = settings_->getIdleThreadScaling();
|
bool threadScaling = settings_->getIdleThreadScaling();
|
||||||
int maxThreads = std::max(1, (int)std::thread::hardware_concurrency());
|
int maxThreads = std::max(1, (int)std::thread::hardware_concurrency());
|
||||||
|
|
||||||
// GPU-aware idle detection: if enabled, treat GPU utilization >= 10%
|
|
||||||
// as "user active" (e.g. watching a video). Disabled = unrestricted
|
|
||||||
// mode that only looks at keyboard/mouse input.
|
|
||||||
bool gpuBusy = false;
|
|
||||||
if (settings_->getIdleGpuAware()) {
|
|
||||||
int gpuUtil = util::Platform::getGpuUtilization();
|
|
||||||
gpuBusy = (gpuUtil >= 10);
|
|
||||||
}
|
|
||||||
bool systemIdle = (idleSec >= delay) && !gpuBusy;
|
|
||||||
|
|
||||||
// Check if mining is already running (manually started by user)
|
// Check if mining is already running (manually started by user)
|
||||||
bool miningActive = isPool
|
bool miningActive = isPool
|
||||||
? (xmrig_manager_ && xmrig_manager_->isRunning())
|
? (xmrig_manager_ && xmrig_manager_->isRunning())
|
||||||
: state_.mining.generate;
|
: state_.mining.generate;
|
||||||
|
|
||||||
|
// Thread scaling only adjusts a miner the user started: with none running there is nothing to
|
||||||
|
// decide, or to probe for.
|
||||||
|
if (threadScaling && (!miningActive || mining_toggle_in_progress_.load())) return;
|
||||||
|
|
||||||
|
// GPU-aware idle detection: if enabled, treat GPU utilization >= 10% as "user active" (e.g. watching
|
||||||
|
// a video). Disabled = unrestricted mode that only looks at keyboard/mouse input. The probe can only
|
||||||
|
// matter once input is idle and a decision can change (start/stop mode leaves a miner the user
|
||||||
|
// started alone), so it runs only then, at most every 5 s, with hysteresis (busy at >= 10%, clear
|
||||||
|
// below 5%) so a GPU hovering near 10% can't keep restarting the miner. Unknown (-1) reads as not busy.
|
||||||
|
const bool inputIdle = idleSec >= delay;
|
||||||
|
const bool decisionPossible = threadScaling || !miningActive || idle_mining_active_;
|
||||||
|
bool gpuBusy = false;
|
||||||
|
if (inputIdle && decisionPossible && settings_->getIdleGpuAware()) {
|
||||||
|
if (now >= idle_gpu_next_sample_) {
|
||||||
|
const int gpuUtil = util::Platform::getGpuUtilization();
|
||||||
|
if (gpuUtil >= 10) idle_gpu_busy_ = true;
|
||||||
|
else if (gpuUtil < 5) idle_gpu_busy_ = false;
|
||||||
|
idle_gpu_next_sample_ = now + std::chrono::seconds(5);
|
||||||
|
}
|
||||||
|
gpuBusy = idle_gpu_busy_;
|
||||||
|
} else if (!inputIdle) {
|
||||||
|
idle_gpu_busy_ = false; // input activity decides now; sample afresh once idle again
|
||||||
|
idle_gpu_next_sample_ = {};
|
||||||
|
}
|
||||||
|
bool systemIdle = inputIdle && !gpuBusy;
|
||||||
|
|
||||||
if (threadScaling) {
|
if (threadScaling) {
|
||||||
// --- Thread scaling mode ---
|
// --- Thread scaling mode ---
|
||||||
// Mining must already be running (started by user). We just adjust threads.
|
// Mining is already running (started by user; checked above). We just adjust threads.
|
||||||
if (!miningActive || mining_toggle_in_progress_.load()) return;
|
|
||||||
|
|
||||||
int activeThreads = settings_->getIdleThreadsActive();
|
int activeThreads = settings_->getIdleThreadsActive();
|
||||||
int idleThreads = settings_->getIdleThreadsIdle();
|
int idleThreads = settings_->getIdleThreadsIdle();
|
||||||
// Resolve auto values: active defaults to half, idle defaults to all
|
// Resolve auto values: active defaults to half, idle defaults to all
|
||||||
|
|||||||
Reference in New Issue
Block a user