能量感知调度(Energy Aware Scheduling,简称EAS)是目前Android手机中Linux线程调度器的基础功能,它使调度器能预测其决策对CPU能耗的影响。依靠CPU的能量模型(Energy Model,简称EM),EAS能为每个线程选择一个最能节约能量的CPU,并把对系统性能的影响降到最低。
EAS仅在异构CPU拓扑(如Arm big.LITTLE)上运行,因为这是EAS节约能量潜力最大的CPU拓扑结构。
注:本文分析整理基于OPPO Reno9 Pro+的开源代码https://github.com/oppo-source/android_kernel_oppo_sm8475
一、关键概念
1.1capacity
算力(capacity)是CPU调度中的一个基础概念,它反映的是一个CPU的计算能力,是个规格化的值,可以通过读取Android手机的文件节点
/sys/devices/system/cpu/cpu*/cpu_capacity获得每个CPU的最大算力。
CPU的最大计算能力= capacity-dmips-mhz * cpuinfo_max_freq / 1000。
其中”capacity-dmips-mhz”表示该cpu在1mHz频率下运行时可以执行多少个dmips,可以从处理器的device tree文件中获取到;
”cpuinfo_max_freq”表示该CPU支持的最大频率,单位kHz,所以上面的公式才除以1000,把计算单位kHz转为mHz。
为了便于算力的比较与计算,把处理器中计算能力最强的CPU的最大算力规格化为了1024。
在CPU算力与频率呈线性关系的处理器中:CPU某一频率点的算力 =
(该CPU某一频点频率 / 该CPU最大频率)* 该CPU的最大算力。
1.2 opp
Operating Performance Point (OPP),表示每个CPU支持的电压频率对(voltage/frequency tuple)。CPU的每个运行频率点,都有一个对应的电压。频率与电压正相关,频率越高,需要的电压越大。
1.3 power
在弄清了CPU某一频率点的算力后,再来看看CPU某一频率点的功率。CPU的Energy Model模块提供了相关文件节点,可以用来读取到CPU某一频率点的功率。
读取文件节点/sys/kernel/debug/energy_model/pd0/*/power,可以获取小核簇CPU各个频率点的功率(mW)
Energy Model代码中通过如下公式来计算CPU每个频率点的功率:
P = C * V^2 * f,其中C是CPU的电容(可以从处理器的device tree文件中读取“dynamic-power-coefficient”获取到),V和f是一个OPP的电压和频率。
1.4 能效比
CPU每个频率点对应的power/capacity值越低,其能效比越好,同一CPU,低频率比高频率的能效比好。整体上来说,小核簇CPU的能效比优于大核簇CPU的能效比,大核簇CPU的能效比优于超大核簇CPU的能效比;但是小核簇CPU高频段能效比差于大核簇CPU低频段的能效比,大核簇CPU高频段的能效比差于超大核簇CPU低频段的能效比。
从上图的能效比曲线上,可以清楚地看出如下特点:
- 在同为200 util算力时,小核簇CPU比大核簇CPU更耗电,因此在系统负载不重时,可以让线程倾向性的运行在大核CPU的低频段,从而不让小核CPU的频率运行在高频率段,来到达到省电而不影响系统性能的目的。
- 超大核簇CPU比大核簇CPU的能效比差很多,超大核CPU能不用需尽量不要用。
资料直通车:Linux内核源码技术学习路线+视频教程内核源码
学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈
二、能量可感知的线程选核
EAS代替CFS的线程唤醒负载均衡代码(task wake-up balancing code
),利用CPU的Energy Model和PELT/WALT统计到的CPU、线程负载信息为唤醒线程选择一个最能省电的CPU来运行。
EAS为线程选运行CPU的代码流程如下:
2.1find_energy_efficient_cpu
find_energy_efficient_cpu()为唤醒任务找到最节能的目标CPU。在每个性能域中查找空闲算力最大的CPU,并将其作为线程运行的潜在候选CPU。然后,使用Energy Model来确定哪个CPU候选是最节能的。
一个性能域一般对应一个CPU簇,如果线程调度到性能域空闲算力最大的那个CPU上运行,能保证该簇的CPU能运行在需求的最低频率。
因为线程迁移的性能代价比较大(比如cache失效),只有选出的最节能CPU比线程当前运行的CPU节约能量大于6%时,线程才会迁移到该CPU运行。
下图列出了find_energy_efficient_cpu()中最核心的代码,并对代码进行了详细的注释。
2.2compute_energy
compute_energy()预估线程p迁移到dst_cpu运行时,性能域pd的能量消耗。compute_energy()预估线程p迁移后,pd里util最大的cpu的max
_util及所有cpu的util之和sum_util,并调用Energy Model提供的API em_cpu_energy()计算线程迁移到性能域pd时的能量消耗。
下图列出了compute_energy ()的代码,并对代码进行了详细的注释。
2.3em_cpu_energy
em_cpu_energy() 是Energy Model提供的估算性能域所有cpu的能量消耗之和的api。它有4个参数,@pd需要估算能量消耗的性能域;@max_util性能域中利用率最高的CPU的利用率,它决定了整个性能域CPU的运行频率;@sum_util性能域中所有CPU的利用率之和,用于估算整个性能域的能量消耗;@allowed_cpu_cap 性能域允许的CPU的最大算力(可能由于thermal的限制,比原始值小)。
em_cpu_energy()运行流程如下:
- 根据性能域中利用率最高的CPU的利用率max_util估算性能域CPU需要的最低运行频率,这里有两点需要注意,估算频率用的利用率是1.25倍max_util,同时预期的CPU调频Governor是Schedutil或者与其类似的CPU的频率遵循它的利用率的Governor。
- 在CPU能量模型中找到满足frequency需求的最低性能状态ps。
- 根据性能域中所有CPU的利用率之和sum_util,cpu的算力,性能状态ps中的cost变量,估算整个性能域的能量消耗。计算公式:
ps->cost * sum_util / cpu的算力,其中ps->cost = ps->power * cpu最大频率 / ps->frequency,其值在能量模型初始化CPU各个性能状态时已计算好。
下图列出了em_cpu_energy ()的代码,并对代码进行了详细的注释。
三、EAS与负载均衡
从一般的角度来看,EAS最能提供帮助的是那些轻中等CPU利用率的场景。当重载CPU-bound任务在运行时,它们需要尽可能多的CPU算力,EAS很难做到在不严重损害性能的情况下节约能量。为了避免EAS影响性能,一旦某个CPU的利用率超过其算力的80%,整个根域标记为‘overutilized’,EAS被禁用。当根域里所有CPU的利用率小于其算力的80%,负载均衡被禁用,EAS覆盖了唤醒负载均衡代码。在不影响系统性能时,EAS会选择最省电的CPU来运行。因此,负载均衡被禁用来阻止其对EAS选核规则的破坏。当系统没有overutilized时,这样做是安全的。因为低于80%临界点意味着:
- 所有cpu都有空闲时间,因此EAS使用的utilization信号可以准确地代表系统中各种任务的“大小”;
- 所有任务都被提供了足够的CPU算力,不管它们的nice值是多少;
- 因为有空闲CPU算力,所有任务能满足规律的blocking/sleeping,在唤醒时,做了足够的负载均衡。
一旦某个cpu的算力超过80%这个临界点,上面三个假设至少有一个是不正确的。在这种情况下,整个根域的overutilized标志被置为true,EAS被禁用,负载均衡被重新使能。
由于overutilization的概念很大程度上依赖于检测系统中是否有空闲时间,因此必须考虑由更高(比CFS)调度类(以及IRQ)“窃取”的CPU算力。因此,overutilization的检测不仅包括CFS任务使用的CPU算力,还包括其他调度类和IRQ使用的CPU算力。
四、小结
EAS只在系统负载不重时,即系统中每个CPU的利用率都低于其算力的80%时才被启用,而且选出的最节能CPU只有比线程当前运行的CPU节约能量大于6%时,线程才会迁移到该CPU运行。因此EAS为线程选择最节约能量的CPU来运行的前提条件是很苛刻的,针对重载场景(比如游戏),EAS的功能应该很少被使用起来,针对重载场景的功耗优化,这里可能是一个值得尝试的点。