client: improve implementation of CPU throttling

Old: if target CPU usage is (say) 1%,
we'd unthrottle, sleep 1 sec, throttle, and sleep 99 sec.
A change to the usage pref wouldn't be enforced during that sleep.

New: do something every second; changes are seen immediately
This commit is contained in:
David Anderson 2023-05-10 22:40:57 -07:00
parent 68df98e51f
commit 3abd3157c4
1 changed files with 36 additions and 35 deletions

View File

@ -1241,54 +1241,55 @@ void ACTIVE_TASK::set_task_state(int val, const char* where) {
}
#ifndef SIM
// CPU throttling is done by starting/stopping running jobs
// with 1-sec resolution; we can't use finer resolution because the API
// polls for start/stop messages every second.
//
// This is done in a separate thread so that it works smoothly
// even if the main thread is doing something time-consuming.
//
// The throttling factor can change at any time;
// we need to respond to these changes reasonably quickly.
// We use the following algorithm:
// Maintain a "level" X
// every second, add usage limit (0..100) to X.
// if it's over 100, don't throttle and subtract 100
// if it's less than 100, throttle
#ifdef _WIN32
DWORD WINAPI throttler(LPVOID) {
#else
void* throttler(void*) {
#endif
// Initialize diagnostics framework for this thread
//
static double x = 100;
diagnostics_thread_init();
while (1) {
client_thread_mutex.lock();
double limit = gstate.current_cpu_usage_limit();
if (gstate.tasks_suspended || limit >= 99.99) {
client_thread_mutex.unlock();
// ::Sleep((int)(1000*10)); // for Win debugging
boinc_sleep(10);
if (limit >= 100) {
boinc_sleep(1);
continue;
}
double on, off, on_frac = limit / 100;
#if 0
// sub-second CPU throttling
// DOESN'T WORK BECAUSE OF 1-SEC API POLL
#define THROTTLE_PERIOD 1.
on = THROTTLE_PERIOD * on_frac;
off = THROTTLE_PERIOD - on;
#else
// throttling w/ at least 1 sec between suspend/resume
if (on_frac > .5) {
off = 1;
on = on_frac/(1.-on_frac);
} else {
on = 1;
off = (1.-on_frac)/on_frac;
}
#endif
gstate.tasks_throttled = true;
gstate.active_tasks.suspend_all(SUSPEND_REASON_CPU_THROTTLE);
client_thread_mutex.unlock();
boinc_sleep(off);
client_thread_mutex.lock();
if (!gstate.tasks_suspended) {
gstate.active_tasks.unsuspend_all(SUSPEND_REASON_CPU_THROTTLE);
if (gstate.tasks_suspended) {
client_thread_mutex.unlock();
boinc_sleep(1);
continue;
}
x += limit;
if (x >= 100) {
if (gstate.tasks_throttled) {
gstate.active_tasks.unsuspend_all(SUSPEND_REASON_CPU_THROTTLE);
gstate.tasks_throttled = false;
}
x -= 100;
} else {
if (!gstate.tasks_throttled) {
gstate.active_tasks.suspend_all(SUSPEND_REASON_CPU_THROTTLE);
gstate.tasks_throttled = true;
}
}
gstate.tasks_throttled = false;
client_thread_mutex.unlock();
boinc_sleep(on);
boinc_sleep(1);
}
return 0;
}