0x00 前言

生活离不开手机,安卓占据了手机系统的半壁江山。手机cpu频率是手机性能的重要指标之一,目前顶级梯队的骁龙855芯片中“大核”频率已经达到了2.96GHz,即使它使用7nm工艺制造,运行在如此高频必然需要较高的核心电压,也同时会增加耗电。为了提高能耗比,除了物理上改进cpu设计,软件上优化软件算法……还有一个重要的措施————使用CPU调速器(Governor)。

0x01 cpu如何手动调速

本文实验均在lineageOS进行(关闭高通热插拔与跳频设置)。本地化系统(如miui,flyme,emui等)或高通的热插拔技术可能会干预cpu频率,无法简单地进行手动调频。

//对cpu0进行调整
cd /sys/devices/system/cpu/cpu0/cpufreq/
//调整cpu0最大频率
echo 2200000 > scaling_max_freq
//调整cpu最小频率
echo 1596000 > scaling_min_freq
//若将cpu最大最小频率设置为相同频率,即锁定cpu频率。

0x02 什么是cpu调速器

机器处在相对空闲的状态下,此时CPU不需要运行在高频率上,调速器会降低cpu频率从而省电。当机器需要进行大量计算时,调速器会升高cpu频率以防止应用出现卡顿。广义上讲,AI优化也是一种调速器,因为现在手机厂商所谓AI优化很大一部分优化效果归功于对cpu频率的精准控制(当然AI优化也不全是调速器,还有动态分辨率调整之类的其他技术)。
简单来说,调速器就是根据负载情况自动化调节cpu频率的工具。

0x03 有哪些常见的cpu调速器

【interactive】交互模式(在安卓使用最广泛的调速器)
【conservative】保守模式
【performance】高性能模式
【powersave】省电模式

【Wheatley】惠特利模式
【lionheart】狮心模式
【smartass】聪明模式
【smartassV2】聪明V2模式

0x04 如何用好调速器

(以interactive调速器为例)——调速器参数调整的学问

1.above_hispeed_delay:

核心频率超过hispeed_freq时,升频延迟时间(above_hispeed_delay)(单位uS)
(example:40000 1400000:30000)
hispeed_freq<频率<1.4GHz,且连续40000uS达到目标负载,才进行升频。
频率>1.4GHz,且连续30000uS达到目标负载,才进行升频。

2.boost:

如果非0则将频率设定为hispeed_freq,用于应对瞬间大量任务。

3.boostpulse_duration

boost时间限制(单位uS)

4.go_hispeed_load:

example: (90)
在最低频状态,且负载达到 go_hispeed_load 即90%时,CPU直接提频到hispeed_freq。

5.hispeed_freq:

example:(1728000)(即1728MHz)
设定hispeed_freq

6.min_sample_time:

example: (40000)
需要保持当前频率多久才进行降频(单位uS)。

7.sampling_down_factor:

example: (1(default))

8.target_loads:

example: (85
1200000:75
1500000:85)
负载达到85时进行升频。频率为1.2GHz-1.5GHz时负载75%进行升频。频率>1.5GHz时负载85%进行升频。
1.jpg
2.jpg

注:go_hispeed_load优先级高于target_loads优先级。


补充:实际案例

用实际案例解释各个调速器参数

安卓手机打开软件时的cpu频率折线图

(单位为0.1s)
3.jpg

  • 上图为软件打开时的cpu频率曲线,可以看到cpu瞬间提频至1.7GHz(hispeed_freq)
  • 但此时软件还在加载过程中,负载依然很高,延迟大约200ms(above_hispeed_delay)进行升频。升至最高频率1.98GHz
  • 最终软件完全加载完毕,仍然保持当前频率(min_sample_time),后降频至300MHz。
  • 进入软件后进行滑动,ui绘制需要较多资源,cpu瞬间升至1.7GHz(hispeed_freq)。

个人见解:

  1. 为了省电:

    1. hispeed降低,将target_loads提高(cpu快速进入一个较低的频率,然后缓慢升频)
  2. 为了性能:

    1. 一是提高hispeed,当负载升高时,cpu快速进入高频,保证响应速度(保证突发性能)。提高target_loadabove_hispeed_delay防止过快升频,为省电考虑。
    2. 二是降低hispeed,降低target_loadabove_hispeed_delay缓慢提升频率,保证正常升频。

拓展:引入前台应用/时间/用户动作(如拿起手机,是否连续触摸)等多维度信息来调整调速器参数


2019-10-21更新

0x05 调速器代码逻辑简析

——从代码理解调速器逻辑(博主能力有限,正在研究代码中,持续更新

综合两文做了一些修改和补充,原文链接:https://blog.csdn.net/xujianqun/article/details/7681011
https://blog.csdn.net/u014089131/article/details/68490573

如何判断跳频(跳到hispeed_freq的实现)

if (cpu_load >= tunables->go_hispeed_load || tunables->boosted) {
	if (pcpu->policy->cur < tunables->hispeed_freq &&
	    cpu_load <= MAX_LOCAL_LOAD) {//当前频率未达到最大频率,可以直接设置为最大频率
		new_freq = tunables->hispeed_freq;
	} else {//此时需要选择比hispeed_freq更大的频率
		new_freq = choose_freq(pcpu, loadadjfreq);

		if (new_freq < tunables->hispeed_freq)
			new_freq = tunables->hispeed_freq;
	}
} else {//choose_freq 设定新的频率,此时需要降频处理
	new_freq = choose_freq(pcpu, loadadjfreq);
}

把代码转换成语言描述就是

1、如果负载超过阈值(或开启boost),就直接升频到hispeed
2、如果负载超过阈值,频率也大于hispeed(或开启boost),就调用choose_freq确定目标频率”最大频率*负载”。
3、如果负载没有超过阈值(未开启boost),需要降频,就调用choose_freq确定目标频率”当前频率负载”,至于判断是否执行降频请继续向下阅读。

如何确定目标频率具体数值?(choose_freq的实现)

注:loadadjfreq算法

cputime_speedadj = (u64)sched_get_busy(data) * pcpu->policy->cpuinfo.max_freq;
loadadjfreq = (unsigned int)cputime_speedadj * 100;//内核不支持浮点运算

     /*
     choose_freq函数用来选频,使选频后的系统workload小于或等于target load
     核心思想是:选择最小的频率来满足target load
     loadadjfreq一段时间内工作量
     */
    static unsigned int choose_freq(struct cpufreq_interactive_cpuinfo *pcpu,
		unsigned int loadadjfreq)
    {
	unsigned int freq = pcpu->policy->cur;//当前频率
	unsigned int prevfreq, freqmin, freqmax;
	unsigned int tl;//target load
	int index;
 
	freqmin = 0;
	freqmax = UINT_MAX;
 
	do {
		prevfreq = freq;
		//计算当前频率对应的workload
		tl = freq_to_targetload(pcpu->policy->governor_data, freq);
 
		/*
		 * Find the lowest frequency where the computed load is less
		 * than or equal to the target load.
		 */
		//从freq_table中获取最优频率对应的index,取大于等于loadadjfreq / tl (target freq)的最小值
		if (cpufreq_frequency_table_target(
			    pcpu->policy, pcpu->freq_table, loadadjfreq / tl,
			    CPUFREQ_RELATION_L, &index))
			break;
		freq = pcpu->freq_table[index].frequency;//**计算出的目标频率,调速器核心之一**
 
		if (freq > prevfreq) {//需要提高频率
			/* The previous frequency is too low. */
			freqmin = prevfreq;
 
			if (freq >= freqmax) {//如果目标频率高于最大频率,取最大频率
				/*
				 * Find the highest frequency that is less
				 * than freqmax.
				 */
				if (cpufreq_frequency_table_target(
					    pcpu->policy, pcpu->freq_table,
					    freqmax - 1, CPUFREQ_RELATION_H,
					    &index))
					break;
				freq = pcpu->freq_table[index].frequency;
 
				if (freq == freqmin) {//这种情况就是锁频的情况,最大频率==最小频率
					/*
					 * The first frequency below freqmax
					 * has already been found to be too
					 * low.  freqmax is the lowest speed
					 * we found that is fast enough.
					 */
					freq = freqmax;
					break;
				}
			}
		} else if (freq < prevfreq) {//需要降低频率
			/* The previous frequency is high enough. */
			freqmax = prevfreq;
 
			if (freq <= freqmin) {//目标频率比最低频率还小,取最小频率
				/*
				 * Find the lowest frequency that is higher
				 * than freqmin.
				 */
				if (cpufreq_frequency_table_target(
					    pcpu->policy, pcpu->freq_table,
					    freqmin + 1, CPUFREQ_RELATION_L,
					    &index))
					break;
				freq = pcpu->freq_table[index].frequency;
 
				/*
				 * If freqmax is the first frequency above
				 * freqmin then we have already found that
				 * this speed is fast enough.
				 */
				if (freq == freqmax)//锁频的情况,最大频率==最小频率
					break;
			}
		}
 
		/* If same frequency chosen as previous then done. */
	} while (freq != prevfreq);
 
	return freq;
    }

待更新: 在应用频率前,调速器还做了那些事?

2019-10-21待更新