From a2ada584ec76327cd6b76daf5f3f23c4958226e4 Mon Sep 17 00:00:00 2001 From: Chris Minnoy Date: Tue, 16 Feb 2021 20:09:00 +0100 Subject: [PATCH 1/6] Ticket 4180 - Use timeslots for cpu_usage_limit. --- client/app.cpp | 126 +++++++++++++++++++++++++++++++++---------------- client/app.h | 3 ++ 2 files changed, 89 insertions(+), 40 deletions(-) diff --git a/client/app.cpp b/client/app.cpp index 4111e1b959..00377e9cf3 100644 --- a/client/app.cpp +++ b/client/app.cpp @@ -150,6 +150,9 @@ ACTIVE_TASK::ACTIVE_TASK() { safe_strcpy(remote_desktop_addr, ""); async_copy = NULL; finish_file_time = 0; + + throttler_start_tick = 0; + throttler_remaining_runtime = 0; } bool ACTIVE_TASK::process_exists() { @@ -1169,6 +1172,84 @@ void ACTIVE_TASK::set_task_state(int val, const char* where) { #ifndef SIM #ifdef NEW_CPU_THROTTLE +/** + * This algo allows active BOINC tasks to be scheduled inside a 100 second timeslot. + * The % of runtime is taken from the global preference. + * If the % is 100 no action is taken on the tasks. + */ +static void throttler_timeslots() { + static unsigned int current_tick = 0; + + client_mutex.lock(); //!< @todo a mutex should be in a RAII form + + auto cpu_usage_limit = gstate.global_prefs.cpu_usage_limit; + if (cpu_usage_limit < 1.0) cpu_usage_limit = 1.0; // Run at least for one 1% + if (cpu_usage_limit > 100.0) cpu_usage_limit = 100.0; + const unsigned int run_time = std::round(cpu_usage_limit); + + if (gstate.tasks_suspended) { + gstate.tasks_throttled = false; + goto exit; + } // if + + gstate.tasks_throttled = (run_time != 100); + + if (cpu_usage_limit == 100) { + for (auto apt : gstate.active_tasks.active_tasks) { + if (apt->task_state() == PROCESS_SUSPENDED) { + apt->throttler_remaining_runtime = 0; + apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); + } // if + } // for + goto exit; + } // if + + // Determine start tick for every active task, update runtime remaining, resume/suspend tasks + { + unsigned int tick = 0; + for (auto apt : gstate.active_tasks.active_tasks) { + // Skip unknown tasks + if (apt->task_state() != PROCESS_EXECUTING and apt->task_state() != PROCESS_SUSPENDED) continue; + + // Determine start tick, kind of spread each task behind the other + //! @note in a multi core system task slots wrap around within the 100 second window + apt->throttler_start_tick = tick % 100; + tick += run_time; + + // Refresh remaining runtime if start tick matches current tick, and start task is suspended + if (current_tick == apt->throttler_start_tick) { + apt->throttler_remaining_runtime = run_time; + if (apt->task_state() == PROCESS_SUSPENDED) { + apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); + } // if + } // if + + // Bog down remaining runtime when the user selects a shorter cpu usage limit + if (apt->throttler_remaining_runtime > run_time) { + apt->throttler_remaining_runtime = run_time; + } // if + + // Control tasks + if (apt->throttler_remaining_runtime == 0) { + if (apt->task_state() == PROCESS_EXECUTING and not apt->result->non_cpu_intensive()) { + apt->preempt(REMOVE_NEVER, SUSPEND_REASON_CPU_THROTTLE); + } // if + } // if + + // Decrement remaining runtime for running process + if (apt->task_state() == PROCESS_EXECUTING) { + --apt->throttler_remaining_runtime; + } // if + } // for + } // block + +exit: + client_mutex.unlock(); + ++current_tick; + if (current_tick >= 100) current_tick = 0; + boinc_sleep(1); +} // void + #ifdef _WIN32 DWORD WINAPI throttler(LPVOID) { #else @@ -1179,48 +1260,13 @@ void* throttler(void*) { // diagnostics_thread_init(); + /** @internal maybe not such a good idea to have an infinate loop here, should check thread exit condition, but there isn't one present */ while (1) { - client_mutex.lock(); - if (gstate.tasks_suspended - || gstate.global_prefs.cpu_usage_limit > 99 - || gstate.global_prefs.cpu_usage_limit < 0.005 - ) { - client_mutex.unlock(); -// ::Sleep((int)(1000*10)); // for Win debugging - boinc_sleep(10); - continue; - } - double on, off, on_frac = gstate.global_prefs.cpu_usage_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 + // Throttler using timeslots for each task + throttler_timeslots(); + } // while - gstate.tasks_throttled = true; - gstate.active_tasks.suspend_all(SUSPEND_REASON_CPU_THROTTLE); - client_mutex.unlock(); - boinc_sleep(off); - client_mutex.lock(); - if (!gstate.tasks_suspended) { - gstate.active_tasks.unsuspend_all(SUSPEND_REASON_CPU_THROTTLE); - } - gstate.tasks_throttled = false; - client_mutex.unlock(); - boinc_sleep(on); - } return 0; -} +} // func #endif #endif diff --git a/client/app.h b/client/app.h index 168754a7dd..aacfd11f55 100644 --- a/client/app.h +++ b/client/app.h @@ -158,6 +158,9 @@ struct ACTIVE_TASK { // running past end of time slice because not checkpointed; // when we do checkpoint, reschedule double last_deadline_miss_time; + + unsigned int throttler_start_tick; //!< Used by the throttler thread to detemine when an active task should resume running (valid values are between 0 and 99) + unsigned int throttler_remaining_runtime; //!< Used by the throttler thread; counting down seconds until 0 after which the throttler thread suspends this task (max value is 100) APP_CLIENT_SHM app_client_shm; // core/app shared mem segment From 9010745f1b5e629aadc325a2dce034a35d2b8264 Mon Sep 17 00:00:00 2001 From: Chris Minnoy Date: Tue, 16 Feb 2021 20:09:00 +0100 Subject: [PATCH 2/6] Ticket 4181 - Maximum CPU time - spread tasks evenly over timeslot --- client/app.cpp | 126 +++++++++++++++++++++++++++++++++---------------- client/app.h | 3 ++ 2 files changed, 89 insertions(+), 40 deletions(-) diff --git a/client/app.cpp b/client/app.cpp index 4111e1b959..00377e9cf3 100644 --- a/client/app.cpp +++ b/client/app.cpp @@ -150,6 +150,9 @@ ACTIVE_TASK::ACTIVE_TASK() { safe_strcpy(remote_desktop_addr, ""); async_copy = NULL; finish_file_time = 0; + + throttler_start_tick = 0; + throttler_remaining_runtime = 0; } bool ACTIVE_TASK::process_exists() { @@ -1169,6 +1172,84 @@ void ACTIVE_TASK::set_task_state(int val, const char* where) { #ifndef SIM #ifdef NEW_CPU_THROTTLE +/** + * This algo allows active BOINC tasks to be scheduled inside a 100 second timeslot. + * The % of runtime is taken from the global preference. + * If the % is 100 no action is taken on the tasks. + */ +static void throttler_timeslots() { + static unsigned int current_tick = 0; + + client_mutex.lock(); //!< @todo a mutex should be in a RAII form + + auto cpu_usage_limit = gstate.global_prefs.cpu_usage_limit; + if (cpu_usage_limit < 1.0) cpu_usage_limit = 1.0; // Run at least for one 1% + if (cpu_usage_limit > 100.0) cpu_usage_limit = 100.0; + const unsigned int run_time = std::round(cpu_usage_limit); + + if (gstate.tasks_suspended) { + gstate.tasks_throttled = false; + goto exit; + } // if + + gstate.tasks_throttled = (run_time != 100); + + if (cpu_usage_limit == 100) { + for (auto apt : gstate.active_tasks.active_tasks) { + if (apt->task_state() == PROCESS_SUSPENDED) { + apt->throttler_remaining_runtime = 0; + apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); + } // if + } // for + goto exit; + } // if + + // Determine start tick for every active task, update runtime remaining, resume/suspend tasks + { + unsigned int tick = 0; + for (auto apt : gstate.active_tasks.active_tasks) { + // Skip unknown tasks + if (apt->task_state() != PROCESS_EXECUTING and apt->task_state() != PROCESS_SUSPENDED) continue; + + // Determine start tick, kind of spread each task behind the other + //! @note in a multi core system task slots wrap around within the 100 second window + apt->throttler_start_tick = tick % 100; + tick += run_time; + + // Refresh remaining runtime if start tick matches current tick, and start task is suspended + if (current_tick == apt->throttler_start_tick) { + apt->throttler_remaining_runtime = run_time; + if (apt->task_state() == PROCESS_SUSPENDED) { + apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); + } // if + } // if + + // Bog down remaining runtime when the user selects a shorter cpu usage limit + if (apt->throttler_remaining_runtime > run_time) { + apt->throttler_remaining_runtime = run_time; + } // if + + // Control tasks + if (apt->throttler_remaining_runtime == 0) { + if (apt->task_state() == PROCESS_EXECUTING and not apt->result->non_cpu_intensive()) { + apt->preempt(REMOVE_NEVER, SUSPEND_REASON_CPU_THROTTLE); + } // if + } // if + + // Decrement remaining runtime for running process + if (apt->task_state() == PROCESS_EXECUTING) { + --apt->throttler_remaining_runtime; + } // if + } // for + } // block + +exit: + client_mutex.unlock(); + ++current_tick; + if (current_tick >= 100) current_tick = 0; + boinc_sleep(1); +} // void + #ifdef _WIN32 DWORD WINAPI throttler(LPVOID) { #else @@ -1179,48 +1260,13 @@ void* throttler(void*) { // diagnostics_thread_init(); + /** @internal maybe not such a good idea to have an infinate loop here, should check thread exit condition, but there isn't one present */ while (1) { - client_mutex.lock(); - if (gstate.tasks_suspended - || gstate.global_prefs.cpu_usage_limit > 99 - || gstate.global_prefs.cpu_usage_limit < 0.005 - ) { - client_mutex.unlock(); -// ::Sleep((int)(1000*10)); // for Win debugging - boinc_sleep(10); - continue; - } - double on, off, on_frac = gstate.global_prefs.cpu_usage_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 + // Throttler using timeslots for each task + throttler_timeslots(); + } // while - gstate.tasks_throttled = true; - gstate.active_tasks.suspend_all(SUSPEND_REASON_CPU_THROTTLE); - client_mutex.unlock(); - boinc_sleep(off); - client_mutex.lock(); - if (!gstate.tasks_suspended) { - gstate.active_tasks.unsuspend_all(SUSPEND_REASON_CPU_THROTTLE); - } - gstate.tasks_throttled = false; - client_mutex.unlock(); - boinc_sleep(on); - } return 0; -} +} // func #endif #endif diff --git a/client/app.h b/client/app.h index 168754a7dd..aacfd11f55 100644 --- a/client/app.h +++ b/client/app.h @@ -158,6 +158,9 @@ struct ACTIVE_TASK { // running past end of time slice because not checkpointed; // when we do checkpoint, reschedule double last_deadline_miss_time; + + unsigned int throttler_start_tick; //!< Used by the throttler thread to detemine when an active task should resume running (valid values are between 0 and 99) + unsigned int throttler_remaining_runtime; //!< Used by the throttler thread; counting down seconds until 0 after which the throttler thread suspends this task (max value is 100) APP_CLIENT_SHM app_client_shm; // core/app shared mem segment From 37b967d43a12ce3a33cdff4fd61b9f772f3a06c5 Mon Sep 17 00:00:00 2001 From: Chris Minnoy Date: Thu, 18 Feb 2021 14:33:34 +0100 Subject: [PATCH 3/6] Spreading based on task number --- client/app.cpp | 157 ++++++++++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 75 deletions(-) diff --git a/client/app.cpp b/client/app.cpp index 00377e9cf3..d81ee4b479 100644 --- a/client/app.cpp +++ b/client/app.cpp @@ -1177,84 +1177,14 @@ void ACTIVE_TASK::set_task_state(int val, const char* where) { * The % of runtime is taken from the global preference. * If the % is 100 no action is taken on the tasks. */ -static void throttler_timeslots() { - static unsigned int current_tick = 0; - - client_mutex.lock(); //!< @todo a mutex should be in a RAII form - - auto cpu_usage_limit = gstate.global_prefs.cpu_usage_limit; - if (cpu_usage_limit < 1.0) cpu_usage_limit = 1.0; // Run at least for one 1% - if (cpu_usage_limit > 100.0) cpu_usage_limit = 100.0; - const unsigned int run_time = std::round(cpu_usage_limit); - - if (gstate.tasks_suspended) { - gstate.tasks_throttled = false; - goto exit; - } // if - - gstate.tasks_throttled = (run_time != 100); - - if (cpu_usage_limit == 100) { - for (auto apt : gstate.active_tasks.active_tasks) { - if (apt->task_state() == PROCESS_SUSPENDED) { - apt->throttler_remaining_runtime = 0; - apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); - } // if - } // for - goto exit; - } // if - - // Determine start tick for every active task, update runtime remaining, resume/suspend tasks - { - unsigned int tick = 0; - for (auto apt : gstate.active_tasks.active_tasks) { - // Skip unknown tasks - if (apt->task_state() != PROCESS_EXECUTING and apt->task_state() != PROCESS_SUSPENDED) continue; - - // Determine start tick, kind of spread each task behind the other - //! @note in a multi core system task slots wrap around within the 100 second window - apt->throttler_start_tick = tick % 100; - tick += run_time; - - // Refresh remaining runtime if start tick matches current tick, and start task is suspended - if (current_tick == apt->throttler_start_tick) { - apt->throttler_remaining_runtime = run_time; - if (apt->task_state() == PROCESS_SUSPENDED) { - apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); - } // if - } // if - - // Bog down remaining runtime when the user selects a shorter cpu usage limit - if (apt->throttler_remaining_runtime > run_time) { - apt->throttler_remaining_runtime = run_time; - } // if - - // Control tasks - if (apt->throttler_remaining_runtime == 0) { - if (apt->task_state() == PROCESS_EXECUTING and not apt->result->non_cpu_intensive()) { - apt->preempt(REMOVE_NEVER, SUSPEND_REASON_CPU_THROTTLE); - } // if - } // if - - // Decrement remaining runtime for running process - if (apt->task_state() == PROCESS_EXECUTING) { - --apt->throttler_remaining_runtime; - } // if - } // for - } // block - -exit: - client_mutex.unlock(); - ++current_tick; - if (current_tick >= 100) current_tick = 0; - boinc_sleep(1); -} // void - #ifdef _WIN32 DWORD WINAPI throttler(LPVOID) { #else void* throttler(void*) { #endif + unsigned int current_tick = 0; + unsigned int task_count; + unsigned int spread; // Initialize diagnostics framework for this thread // @@ -1262,8 +1192,85 @@ void* throttler(void*) { /** @internal maybe not such a good idea to have an infinate loop here, should check thread exit condition, but there isn't one present */ while (1) { - // Throttler using timeslots for each task - throttler_timeslots(); + client_mutex.lock(); //!< @todo a mutex should be in a RAII form + + auto cpu_usage_limit = gstate.global_prefs.cpu_usage_limit; + if (cpu_usage_limit < 1.0) cpu_usage_limit = 1.0; // Run at least for one 1% + if (cpu_usage_limit > 100.0) cpu_usage_limit = 100.0; + const unsigned int run_time = std::round(cpu_usage_limit); + + if (gstate.tasks_suspended) { + gstate.tasks_throttled = false; + goto end; + } // if + + gstate.tasks_throttled = (run_time != 100); + + // Count tasks; we need to know this to determine the spread; unsuspend if cpu_usage_limit == 100 + task_count = 0; + for (auto apt : gstate.active_tasks.active_tasks) { + // Filter out CPU tasks + if (apt->result->dont_throttle()) continue; + if (apt->task_state() != PROCESS_EXECUTING and apt->task_state() != PROCESS_SUSPENDED) continue; + // Unsuspend if no limit + if (cpu_usage_limit >= 100 and apt->task_state() == PROCESS_SUSPENDED) { + apt->throttler_remaining_runtime = 0; + apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); + } // if + // Count tasks + ++task_count; + } // for + if (cpu_usage_limit >= 100) goto end; // Nothing to do + if (task_count == 0) goto end; // Nothing to do + + // Determine spread based on task count + spread = 100 / task_count; + if (spread == 0) spread = 1; // minimum of 1 second + + // Determine start tick for every active task, update runtime remaining, resume/suspend tasks + { + unsigned int tick = 0; + for (auto apt : gstate.active_tasks.active_tasks) { + // Filter out CPU tasks + if (apt->result->dont_throttle()) continue; + if (apt->task_state() != PROCESS_EXECUTING and apt->task_state() != PROCESS_SUSPENDED) continue; + + // Determine start tick; spread start of tasks evenly over 100 second window + apt->throttler_start_tick = tick % 100; + tick += spread; + + // Refresh remaining runtime if start tick matches current tick, and run task if suspended + if (current_tick == apt->throttler_start_tick) { + apt->throttler_remaining_runtime = run_time; + if (apt->task_state() == PROCESS_SUSPENDED) { + apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); + } // if + } // if + + // Bog down remaining runtime when the user selects a shorter cpu usage limit + if (apt->throttler_remaining_runtime > run_time) { + apt->throttler_remaining_runtime = run_time; + } // if + + // Handle runtime + if (apt->task_state() == PROCESS_EXECUTING) { + // Suspend task if runtime is over + if (apt->throttler_remaining_runtime == 0) { + apt->preempt(REMOVE_NEVER, SUSPEND_REASON_CPU_THROTTLE); + } // if + // Decrement remaining runtime for running process + else { + --apt->throttler_remaining_runtime; + } // else + } // if + } // for + } // block + +end: + client_mutex.unlock(); + ++current_tick; + if (current_tick >= 100) current_tick = 0; + boinc_sleep(1); } // while return 0; From df0fffaf5869c6886a54ce833abfbef34de36684 Mon Sep 17 00:00:00 2001 From: Chris Minnoy Date: Fri, 19 Feb 2021 11:18:48 +0100 Subject: [PATCH 4/6] Finishing touch --- client/app.cpp | 139 ++++++++++++++++++++++++++----------------------- 1 file changed, 73 insertions(+), 66 deletions(-) diff --git a/client/app.cpp b/client/app.cpp index d81ee4b479..63d9f7eb78 100644 --- a/client/app.cpp +++ b/client/app.cpp @@ -1184,7 +1184,9 @@ void* throttler(void*) { #endif unsigned int current_tick = 0; unsigned int task_count; - unsigned int spread; + unsigned int stride; + unsigned int run_time; + float cpu_usage_limit; // Initialize diagnostics framework for this thread // @@ -1192,85 +1194,90 @@ void* throttler(void*) { /** @internal maybe not such a good idea to have an infinate loop here, should check thread exit condition, but there isn't one present */ while (1) { - client_mutex.lock(); //!< @todo a mutex should be in a RAII form + client_mutex.lock(); //!< @todo a mutex should be in a RAII form - auto cpu_usage_limit = gstate.global_prefs.cpu_usage_limit; - if (cpu_usage_limit < 1.0) cpu_usage_limit = 1.0; // Run at least for one 1% - if (cpu_usage_limit > 100.0) cpu_usage_limit = 100.0; - const unsigned int run_time = std::round(cpu_usage_limit); - - if (gstate.tasks_suspended) { - gstate.tasks_throttled = false; - goto end; + // Don't throttle if all tasks are suspended + if (gstate.tasks_suspended) { + gstate.tasks_throttled = false; + goto end; } // if + // Get cpu usage limit + cpu_usage_limit = gstate.global_prefs.cpu_usage_limit; + if (cpu_usage_limit < 1.0) cpu_usage_limit = 1.0; // Run at least for one 1% + if (cpu_usage_limit > 100.0) cpu_usage_limit = 100.0; + run_time = std::round(cpu_usage_limit); + + // Update global state gstate.tasks_throttled = (run_time != 100); - // Count tasks; we need to know this to determine the spread; unsuspend if cpu_usage_limit == 100 - task_count = 0; - for (auto apt : gstate.active_tasks.active_tasks) { + // Count tasks; we need to know this to determine the stride; unsuspend if cpu_usage_limit == 100 + task_count = 0; + for (auto apt : gstate.active_tasks.active_tasks) { + const auto task_state = apt->task_state(); // Filter out CPU tasks - if (apt->result->dont_throttle()) continue; - if (apt->task_state() != PROCESS_EXECUTING and apt->task_state() != PROCESS_SUSPENDED) continue; - // Unsuspend if no limit - if (cpu_usage_limit >= 100 and apt->task_state() == PROCESS_SUSPENDED) { - apt->throttler_remaining_runtime = 0; - apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); - } // if - // Count tasks + if (apt->result->dont_throttle()) continue; + if (task_state != PROCESS_EXECUTING and task_state != PROCESS_SUSPENDED) continue; + // Unsuspend if no limit + if (cpu_usage_limit >= 100 and task_state == PROCESS_SUSPENDED) { + apt->throttler_remaining_runtime = 0; + apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); + } // if + // Count tasks ++task_count; - } // for - if (cpu_usage_limit >= 100) goto end; // Nothing to do - if (task_count == 0) goto end; // Nothing to do + } // for + if (cpu_usage_limit >= 100) goto end; // Nothing to do + if (task_count == 0) goto end; // Nothing to do - // Determine spread based on task count - spread = 100 / task_count; - if (spread == 0) spread = 1; // minimum of 1 second + // Determine stride based on task count + stride = 100 / task_count; + if (stride == 0) stride = 1; // minimum of 1 second - // Determine start tick for every active task, update runtime remaining, resume/suspend tasks - { - unsigned int tick = 0; + // Determine start tick for every active task, update runtime remaining, resume/suspend tasks + { + unsigned int tick = 0; for (auto apt : gstate.active_tasks.active_tasks) { - // Filter out CPU tasks - if (apt->result->dont_throttle()) continue; - if (apt->task_state() != PROCESS_EXECUTING and apt->task_state() != PROCESS_SUSPENDED) continue; - - // Determine start tick; spread start of tasks evenly over 100 second window - apt->throttler_start_tick = tick % 100; - tick += spread; - - // Refresh remaining runtime if start tick matches current tick, and run task if suspended - if (current_tick == apt->throttler_start_tick) { - apt->throttler_remaining_runtime = run_time; - if (apt->task_state() == PROCESS_SUSPENDED) { - apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); - } // if - } // if - - // Bog down remaining runtime when the user selects a shorter cpu usage limit - if (apt->throttler_remaining_runtime > run_time) { - apt->throttler_remaining_runtime = run_time; - } // if - - // Handle runtime - if (apt->task_state() == PROCESS_EXECUTING) { - // Suspend task if runtime is over - if (apt->throttler_remaining_runtime == 0) { + const auto task_state = apt->task_state(); + // Filter out CPU tasks + if (apt->result->dont_throttle()) continue; + if (task_state != PROCESS_EXECUTING and task_state != PROCESS_SUSPENDED) continue; + + // Determine start tick; spread start of tasks evenly over 100 second window + apt->throttler_start_tick = tick % 100; + tick += stride; + + // Refresh remaining runtime if start tick matches current tick, and run task if suspended + if (current_tick == apt->throttler_start_tick) { + apt->throttler_remaining_runtime = run_time; + if (task_state == PROCESS_SUSPENDED) { + apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); + } // if + } // if + + // Bog down remaining runtime when the user selects a shorter cpu usage limit + if (apt->throttler_remaining_runtime > run_time) { + apt->throttler_remaining_runtime = run_time; + } // if + + // Handle runtime + if (task_state == PROCESS_EXECUTING) { + // Suspend task if runtime is over + if (apt->throttler_remaining_runtime == 0) { apt->preempt(REMOVE_NEVER, SUSPEND_REASON_CPU_THROTTLE); - } // if - // Decrement remaining runtime for running process - else { - --apt->throttler_remaining_runtime; - } // else - } // if + } // if + // Decrement remaining runtime for running process + else { + --apt->throttler_remaining_runtime; + } // else + } // if } // for - } // block + } // block end: - client_mutex.unlock(); - ++current_tick; - if (current_tick >= 100) current_tick = 0; - boinc_sleep(1); + client_mutex.unlock(); + ++current_tick; + if (current_tick >= 100) current_tick = 0; + boinc_sleep(1); } // while return 0; From a954b9941bee6fbce17d64097cb364c326646287 Mon Sep 17 00:00:00 2001 From: Chris Minnoy Date: Fri, 19 Feb 2021 13:38:26 +0100 Subject: [PATCH 5/6] Windows compiler incompatibility --- client/app.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/app.cpp b/client/app.cpp index 63d9f7eb78..d19ea8ac02 100644 --- a/client/app.cpp +++ b/client/app.cpp @@ -1217,9 +1217,9 @@ void* throttler(void*) { const auto task_state = apt->task_state(); // Filter out CPU tasks if (apt->result->dont_throttle()) continue; - if (task_state != PROCESS_EXECUTING and task_state != PROCESS_SUSPENDED) continue; + if (task_state != PROCESS_EXECUTING && task_state != PROCESS_SUSPENDED) continue; // Unsuspend if no limit - if (cpu_usage_limit >= 100 and task_state == PROCESS_SUSPENDED) { + if (cpu_usage_limit >= 100 && task_state == PROCESS_SUSPENDED) { apt->throttler_remaining_runtime = 0; apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); } // if @@ -1240,7 +1240,7 @@ void* throttler(void*) { const auto task_state = apt->task_state(); // Filter out CPU tasks if (apt->result->dont_throttle()) continue; - if (task_state != PROCESS_EXECUTING and task_state != PROCESS_SUSPENDED) continue; + if (task_state != PROCESS_EXECUTING && task_state != PROCESS_SUSPENDED) continue; // Determine start tick; spread start of tasks evenly over 100 second window apt->throttler_start_tick = tick % 100; From 89d4db47423f9dc5682400000c5153f80aab0366 Mon Sep 17 00:00:00 2001 From: Chris Minnoy Date: Tue, 30 Mar 2021 13:56:33 +0200 Subject: [PATCH 6/6] Motivation for this new throttler algorithm: - The old algorithm enabled enable all tasks at the same time, starving CPU cache and memory bandwidth. - The old algorithm did not optimaly make use of XFR because again all threads where enabled and disable in sync - The old algorithm put more strain on PSU and VRMs as there are high power spikes every time The new algorithm should result on more stable and lower temperatures on most CPUs while increasing overall throuput. --- client/app.cpp | 61 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/client/app.cpp b/client/app.cpp index d19ea8ac02..f64bf03238 100644 --- a/client/app.cpp +++ b/client/app.cpp @@ -1182,16 +1182,38 @@ DWORD WINAPI throttler(LPVOID) { #else void* throttler(void*) { #endif + static int (*gcd)(int, int) = [](int a, int b) -> int { + if (a == 0) return b; + if (b == 0) return a; + if (a == b) return a; + if (a > b) return gcd(a - b, b); + return gcd(a, b - a); + }; + + //* NEW ALGORITHM unsigned int current_tick = 0; unsigned int task_count; - unsigned int stride; + bool throttled; unsigned int run_time; + unsigned int sleep_time; + unsigned int divider; + float stride; + float window_size; float cpu_usage_limit; // Initialize diagnostics framework for this thread // diagnostics_thread_init(); + /** + * Motivation for this new throttler algorithm: + * - The old algorithm enabled enable all tasks at the same time, starving CPU cache and memory bandwidth. + * - The old algorithm did not optimaly make use of XFR because again all threads where enabled and disable in sync + * - The old algorithm put more strain on PSU and VRMs as there are high power spikes every time + * + * The new algorithm should result on more stable and lower temperatures on most CPUs while increasing overall throuput. + */ + /** @internal maybe not such a good idea to have an infinate loop here, should check thread exit condition, but there isn't one present */ while (1) { client_mutex.lock(); //!< @todo a mutex should be in a RAII form @@ -1204,12 +1226,20 @@ void* throttler(void*) { // Get cpu usage limit cpu_usage_limit = gstate.global_prefs.cpu_usage_limit; - if (cpu_usage_limit < 1.0) cpu_usage_limit = 1.0; // Run at least for one 1% - if (cpu_usage_limit > 100.0) cpu_usage_limit = 100.0; + if (cpu_usage_limit < 1.0f) cpu_usage_limit = 1.0f; // Run at least for one 1% + if (cpu_usage_limit > 100.0f) cpu_usage_limit = 100.0f; + + // Determine shortest run_time and sleep_time in whole seconds run_time = std::round(cpu_usage_limit); + throttled = (run_time != 100); + sleep_time = 100 - run_time; + divider = gcd(run_time, sleep_time); + run_time /= divider; + sleep_time /= divider; + window_size = run_time + sleep_time; // Update global state - gstate.tasks_throttled = (run_time != 100); + gstate.tasks_throttled = throttled; // Count tasks; we need to know this to determine the stride; unsuspend if cpu_usage_limit == 100 task_count = 0; @@ -1219,31 +1249,35 @@ void* throttler(void*) { if (apt->result->dont_throttle()) continue; if (task_state != PROCESS_EXECUTING && task_state != PROCESS_SUSPENDED) continue; // Unsuspend if no limit - if (cpu_usage_limit >= 100 && task_state == PROCESS_SUSPENDED) { + if (cpu_usage_limit >= 100.0f && task_state == PROCESS_SUSPENDED) { apt->throttler_remaining_runtime = 0; apt->unsuspend(SUSPEND_REASON_CPU_THROTTLE); } // if + // Skip non-scheduled tasks + if (apt->scheduler_state != CPU_SCHED_SCHEDULED) continue; // Count tasks ++task_count; } // for - if (cpu_usage_limit >= 100) goto end; // Nothing to do + if (cpu_usage_limit >= 100.0f) goto end; // Nothing to do if (task_count == 0) goto end; // Nothing to do - // Determine stride based on task count - stride = 100 / task_count; - if (stride == 0) stride = 1; // minimum of 1 second + // Determine stride based on scheduled task count + stride = window_size / task_count; // Determine start tick for every active task, update runtime remaining, resume/suspend tasks { - unsigned int tick = 0; + float tick = 0.0f; for (auto apt : gstate.active_tasks.active_tasks) { - const auto task_state = apt->task_state(); + const auto task_state = apt->task_state(); + // Filter out CPU tasks if (apt->result->dont_throttle()) continue; if (task_state != PROCESS_EXECUTING && task_state != PROCESS_SUSPENDED) continue; + // Skip non-scheduled tasks + if (apt->scheduler_state != CPU_SCHED_SCHEDULED) continue; // Determine start tick; spread start of tasks evenly over 100 second window - apt->throttler_start_tick = tick % 100; + apt->throttler_start_tick = std::round(std::fmod(tick, window_size)); tick += stride; // Refresh remaining runtime if start tick matches current tick, and run task if suspended @@ -1276,10 +1310,9 @@ void* throttler(void*) { end: client_mutex.unlock(); ++current_tick; - if (current_tick >= 100) current_tick = 0; + if (current_tick >= window_size) current_tick = 0; boinc_sleep(1); } // while - return 0; } // func #endif