mirror of
https://github.com/FeralInteractive/gamemode.git
synced 2025-06-06 23:57:22 +02:00
Add an option for using a different governor for integrated GPUs
This commit adds two new configuration options: igpu_desiredgov and igpu_power_threshold which allow for a different CPU governor when the Intel integrated GPU is under load. This currently only applies to Intel integrated GPUs and not AMD APUs because it uses the Intel RAPL infrastructure for getting power information. If on a platform that without an Intel integrated GPU or where the kernel does not support RAPL, the new options will be ignored and it will fall back to the old behavior. One of the core principals of gamemoded to date has been that, when playing a game, we want to use the "performance" CPU governor to increase CPU performance and prevent CPU-limiting. However, when the integrated GPU is under load, this can be counter-productive because the CPU and GPU share a thermal and power budget. By throwing the CPU governor to "performance" game mode currently makes the CPU frequency management far too aggressive and it burns more power than needed. With a discrete GPU, this is fine because the worst that happens is a bit more fan noise. With an integrated GPU, however, the additional power being burned by the CPU is power not available to the GPU and this can cause the GPU to clock down and lead to significantly worse performance. By using the "powersave" governor instead of the "performance" governor while the integrated GPU is under load, we can save power on the CPU side which lets the GPU clock up higher. On my Razer Blade Stealth 13 with an i7-1065G7, this improves the performance of "Shadow of the Tomb Raider" by around 25-30% according to its internal benchmark mode.
This commit is contained in:
parent
c1646ecdd9
commit
688373a260
@ -39,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "ini.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/inotify.h>
|
||||
@ -50,6 +51,8 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
/* Default value for the reaper frequency */
|
||||
#define DEFAULT_REAPER_FREQ 5
|
||||
|
||||
#define DEFAULT_IGPU_POWER_THRESHOLD 0.3f
|
||||
|
||||
/* Helper macro for defining the config variable getter */
|
||||
#define DEFINE_CONFIG_GET(name) \
|
||||
long config_get_##name(GameModeConfig *self) \
|
||||
@ -82,6 +85,9 @@ struct GameModeConfig {
|
||||
char defaultgov[CONFIG_VALUE_MAX];
|
||||
char desiredgov[CONFIG_VALUE_MAX];
|
||||
|
||||
char igpu_desiredgov[CONFIG_VALUE_MAX];
|
||||
float igpu_power_threshold;
|
||||
|
||||
char softrealtime[CONFIG_VALUE_MAX];
|
||||
long renice;
|
||||
|
||||
@ -179,6 +185,27 @@ __attribute__((unused)) static bool get_long_value_hex(const char *value_name, c
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a long value from a string
|
||||
*/
|
||||
static bool get_float_value(const char *value_name, const char *value, float *output)
|
||||
{
|
||||
char *end = NULL;
|
||||
float config_value = strtof(value, &end);
|
||||
|
||||
if (errno == ERANGE) {
|
||||
LOG_ERROR("Config: %s overflowed, given [%s]\n", value_name, value);
|
||||
return false;
|
||||
} else if (!(*value != '\0' && end && *end == '\0')) {
|
||||
LOG_ERROR("Config: %s was invalid, given [%s]\n", value_name, value);
|
||||
return false;
|
||||
} else {
|
||||
*output = config_value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple strstr scheck
|
||||
* Could be expanded for wildcard or regex
|
||||
@ -230,6 +257,10 @@ static int inih_handler(void *user, const char *section, const char *name, const
|
||||
valid = get_string_value(value, self->values.defaultgov);
|
||||
} else if (strcmp(name, "desiredgov") == 0) {
|
||||
valid = get_string_value(value, self->values.desiredgov);
|
||||
} else if (strcmp(name, "igpu_desiredgov") == 0) {
|
||||
valid = get_string_value(value, self->values.igpu_desiredgov);
|
||||
} else if (strcmp(name, "igpu_power_threshold") == 0) {
|
||||
valid = get_float_value(name, value, &self->values.igpu_power_threshold);
|
||||
} else if (strcmp(name, "softrealtime") == 0) {
|
||||
valid = get_string_value(value, self->values.softrealtime);
|
||||
} else if (strcmp(name, "renice") == 0) {
|
||||
@ -329,6 +360,7 @@ static void load_config_files(GameModeConfig *self)
|
||||
memset(&self->values, 0, sizeof(self->values));
|
||||
|
||||
/* Set some non-zero defaults */
|
||||
self->values.igpu_power_threshold = DEFAULT_IGPU_POWER_THRESHOLD;
|
||||
self->values.inhibit_screensaver = 1; /* Defaults to on */
|
||||
self->values.reaper_frequency = DEFAULT_REAPER_FREQ;
|
||||
self->values.gpu_device = -1; /* 0 is a valid device ID so use -1 to indicate no value */
|
||||
@ -641,6 +673,35 @@ void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALU
|
||||
memcpy_locked_config(self, governor, self->values.desiredgov, sizeof(self->values.desiredgov));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the chosen iGPU desired governor
|
||||
*/
|
||||
void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
|
||||
{
|
||||
memcpy_locked_config(self,
|
||||
governor,
|
||||
self->values.igpu_desiredgov,
|
||||
sizeof(self->values.igpu_desiredgov));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the chosen iGPU power threshold
|
||||
*/
|
||||
float config_get_igpu_power_threshold(GameModeConfig *self)
|
||||
{
|
||||
float value = 0;
|
||||
memcpy_locked_config(self, &value, &self->values.igpu_power_threshold, sizeof(float));
|
||||
/* Validate the threshold value */
|
||||
if (isnan(value) || value < 0) {
|
||||
LOG_ONCE(ERROR,
|
||||
"Configured iGPU power threshold value '%f' is invalid, ignoring iGPU default "
|
||||
"governor.\n",
|
||||
value);
|
||||
value = FP_INFINITE;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the chosen soft realtime behavior
|
||||
*/
|
||||
|
@ -102,6 +102,8 @@ bool config_get_inhibit_screensaver(GameModeConfig *self);
|
||||
long config_get_script_timeout(GameModeConfig *self);
|
||||
void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
|
||||
void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
|
||||
void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
|
||||
float config_get_igpu_power_threshold(GameModeConfig *self);
|
||||
void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]);
|
||||
long config_get_renice_value(GameModeConfig *self);
|
||||
long config_get_ioprio_value(GameModeConfig *self);
|
||||
|
@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "common-governors.h"
|
||||
#include "common-helpers.h"
|
||||
#include "common-logging.h"
|
||||
#include "common-power.h"
|
||||
|
||||
#include "gamemode.h"
|
||||
#include "gamemode-config.h"
|
||||
@ -66,6 +67,7 @@ struct GameModeClient {
|
||||
enum GameModeGovernor {
|
||||
GAME_MODE_GOVERNOR_DEFAULT,
|
||||
GAME_MODE_GOVERNOR_DESIRED,
|
||||
GAME_MODE_GOVERNOR_IGPU_DESIRED,
|
||||
};
|
||||
|
||||
struct GameModeContext {
|
||||
@ -82,6 +84,10 @@ struct GameModeContext {
|
||||
struct GameModeGPUInfo *stored_gpu; /**<Stored GPU info for the current GPU */
|
||||
struct GameModeGPUInfo *target_gpu; /**<Target GPU info for the current GPU */
|
||||
|
||||
bool igpu_optimization_enabled;
|
||||
uint32_t last_cpu_energy_uj;
|
||||
uint32_t last_igpu_energy_uj;
|
||||
|
||||
/* Reaper control */
|
||||
struct {
|
||||
pthread_t thread;
|
||||
@ -219,6 +225,11 @@ static int game_mode_set_governor(GameModeContext *self, enum GameModeGovernor g
|
||||
gov_str = gov_config_str[0] != '\0' ? gov_config_str : "performance";
|
||||
break;
|
||||
|
||||
case GAME_MODE_GOVERNOR_IGPU_DESIRED:
|
||||
config_get_igpu_desired_governor(self->config, gov_config_str);
|
||||
gov_str = gov_config_str[0] != '\0' ? gov_config_str : "powersave";
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(!"Invalid governor requested");
|
||||
}
|
||||
@ -240,6 +251,93 @@ static int game_mode_set_governor(GameModeContext *self, enum GameModeGovernor g
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void game_mode_enable_igpu_optimization(GameModeContext *self)
|
||||
{
|
||||
float threshold = config_get_igpu_power_threshold(self->config);
|
||||
|
||||
/* There's no way the GPU is using 10000x the power. This lets us
|
||||
* short-circuit if the config file specifies an invalid threshold
|
||||
* and we want to disable the iGPU heuristic.
|
||||
*/
|
||||
if (threshold < 10000 && get_cpu_energy_uj(&self->last_cpu_energy_uj) &&
|
||||
get_igpu_energy_uj(&self->last_igpu_energy_uj)) {
|
||||
LOG_MSG(
|
||||
"Successfully queried power data for the CPU and iGPU. "
|
||||
"Enabling the integrated GPU optimization");
|
||||
self->igpu_optimization_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void game_mode_disable_igpu_optimization(GameModeContext *self)
|
||||
{
|
||||
self->igpu_optimization_enabled = false;
|
||||
}
|
||||
|
||||
static void game_mode_check_igpu_energy(GameModeContext *self)
|
||||
{
|
||||
pthread_rwlock_wrlock(&self->rwlock);
|
||||
|
||||
/* We only care if we're not in the default governor */
|
||||
if (self->current_govenor == GAME_MODE_GOVERNOR_DEFAULT)
|
||||
goto unlock;
|
||||
|
||||
if (!self->igpu_optimization_enabled)
|
||||
goto unlock;
|
||||
|
||||
uint32_t cpu_energy_uj, igpu_energy_uj;
|
||||
if (!get_cpu_energy_uj(&cpu_energy_uj) || !get_igpu_energy_uj(&igpu_energy_uj)) {
|
||||
/* We've already succeeded at getting power information once so
|
||||
* failing here is possible but very unexpected. */
|
||||
self->igpu_optimization_enabled = false;
|
||||
LOG_ERROR("Failed to get CPU and iGPU power data\n");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* The values we query from RAPL are in units of microjoules of energy
|
||||
* used since boot or since the last time the counter rolled over. You
|
||||
* can get average power over some time window T by sampling before and
|
||||
* after and doing the following calculation
|
||||
*
|
||||
* power_uw = (energy_uj_after - energy_uj_before) / seconds
|
||||
*
|
||||
* To get the power in Watts (rather than microwatts), you can simply
|
||||
* divide by 1000000.
|
||||
*
|
||||
* Because we're only concerned with the ratio between the GPU and CPU
|
||||
* power, we never bother dividing by 1000000 the length of time of the
|
||||
* sampling window because that would just algebraically cancel out.
|
||||
* Instead, we divide the GPU energy used in the window (difference of
|
||||
* before and after) by the CPU energy used. It nicely provides the
|
||||
* ratio of the averages and there are no instantaneous sampling
|
||||
* problems.
|
||||
*
|
||||
* Overflow is possible here. However, that would simply mean that
|
||||
* the HW counter has overflowed and us wrapping around is probably
|
||||
* the right thing to do. Wrapping at 32 bits is exactly what the
|
||||
* Linux kernel's turbostat utility does so it's probably right.
|
||||
*/
|
||||
uint32_t cpu_energy_delta_uj = cpu_energy_uj - self->last_cpu_energy_uj;
|
||||
uint32_t igpu_energy_delta_uj = igpu_energy_uj - self->last_igpu_energy_uj;
|
||||
self->last_cpu_energy_uj = cpu_energy_uj;
|
||||
self->last_igpu_energy_uj = igpu_energy_uj;
|
||||
|
||||
if (cpu_energy_delta_uj == 0) {
|
||||
LOG_ERROR("CPU reported no energy used\n");
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
float threshold = config_get_igpu_power_threshold(self->config);
|
||||
double ratio = (double)igpu_energy_delta_uj / (double)cpu_energy_delta_uj;
|
||||
if (ratio > threshold) {
|
||||
game_mode_set_governor(self, GAME_MODE_GOVERNOR_IGPU_DESIRED);
|
||||
} else {
|
||||
game_mode_set_governor(self, GAME_MODE_GOVERNOR_DESIRED);
|
||||
}
|
||||
|
||||
unlock:
|
||||
pthread_rwlock_unlock(&self->rwlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pivot into game mode.
|
||||
*
|
||||
@ -251,7 +349,12 @@ static void game_mode_context_enter(GameModeContext *self)
|
||||
LOG_MSG("Entering Game Mode...\n");
|
||||
sd_notifyf(0, "STATUS=%sGameMode is now active.%s\n", "\x1B[1;32m", "\x1B[0m");
|
||||
|
||||
game_mode_set_governor(self, GAME_MODE_GOVERNOR_DESIRED);
|
||||
if (game_mode_set_governor(self, GAME_MODE_GOVERNOR_DESIRED) == 0) {
|
||||
/* We just switched to a non-default governor. Enable the iGPU
|
||||
* optimization.
|
||||
*/
|
||||
game_mode_enable_igpu_optimization(self);
|
||||
}
|
||||
|
||||
/* Inhibit the screensaver */
|
||||
if (config_get_inhibit_screensaver(self->config))
|
||||
@ -290,6 +393,8 @@ static void game_mode_context_leave(GameModeContext *self)
|
||||
|
||||
game_mode_set_governor(self, GAME_MODE_GOVERNOR_DEFAULT);
|
||||
|
||||
game_mode_disable_igpu_optimization(self);
|
||||
|
||||
char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
|
||||
memset(scripts, 0, sizeof(scripts));
|
||||
config_get_gamemode_end_scripts(self->config, scripts);
|
||||
@ -822,6 +927,9 @@ static void *game_mode_context_reaper(void *userdata)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Check on the CPU/iGPU energy balance */
|
||||
game_mode_check_igpu_energy(self);
|
||||
|
||||
/* Expire remaining entries */
|
||||
game_mode_context_auto_expire(self);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
[general]
|
||||
; The reaper thread will check every 5 seconds for exited clients and for config file changes
|
||||
; The reaper thread will check every 5 seconds for exited clients, for config file changes, and for the CPU/iGPU power balance
|
||||
reaper_freq=5
|
||||
|
||||
; The desired governor is used when entering GameMode instead of "performance"
|
||||
@ -7,6 +7,15 @@ desiredgov=performance
|
||||
; The default governer is used when leaving GameMode instead of restoring the original value
|
||||
defaultgov=powersave
|
||||
|
||||
; The iGPU desired governor is used when the integrated GPU is under heavy load
|
||||
igpu_desiredgov=powersave
|
||||
; Threshold to use to decide when the integrated GPU is under heavy load.
|
||||
; This is a ratio of iGPU Watts / CPU Watts which is used to determine when the
|
||||
; integraged GPU is under heavy enough load to justify switching to
|
||||
; igpu_desiredgov. Set this to -1 to disable all iGPU checking and always
|
||||
; use desiredgov for games.
|
||||
igpu_power_threshold=0.3
|
||||
|
||||
; GameMode can change the scheduler policy to SCHED_ISO on kernels which support it (currently
|
||||
; not supported by upstream kernels). Can be set to "auto", "on" or "off". "auto" will enable
|
||||
; with 4 or more CPU cores. "on" will always enable. Defaults to "off".
|
||||
|
Loading…
x
Reference in New Issue
Block a user