以 Intel 的 i7 - 8700 为例,我们来看看 CPU 的主频。
如何理解主频
CPU主频越高,单核性能越强,CPU的运算速度更快。
应该如何理解?
1 |
|
其中在程序代码确定的情况下,即 程序指令数 * 指令平均时钟周期(CPI)得到的时钟周期总数固定,则单个时钟周期时间决定程序的执行时间,而主频决定了单个时钟周期的时间。
1 |
|
举个栗子,假设CPU在一个时钟周期执行一条运算指令,CPU 1GHz 和 2GHz,意味着1ns和0.5ns执行1条运算指令,0.5ns相比于1ns快了一倍,自然运算速度也更快。
(但是实际上更复杂一些,指令周期包含取指令、执行指令,一个指令周期由若干个机器周期组成,机器周期由多个时钟周期组成)
CPU是以主频稳定运行的吗
要解释这个问题,先来看看CPU频率是怎么定义的。
1 |
|
以 Intel 的 CPU 为例,官方给出的CPU频率为基础频率,是 TDP 定义的操作点。说简单点,即CPU在TDP功耗下,能长时间稳定运行的最大频率(注意,并不是指CPU最多只能跑到这个最大频率,后面我们会讲到)。
想要查看到实际CPU的频率,可以通过/proc/cpuinfo查看每个核心的信息,可以看到核心的频率是在不断变化的:
1 |
|
为什么CPU实际频率会超过基础频率
从上图中可以发现,i7-8700 的基础频率是 3.2 GHz,而实际的核心频率已经达到 4.4 GHz 左右,超过了基础频率,原因是 Intel 的 Trubo Boost 睿频加速技术(AMD 也有类似的技术,Trubo Core),根据需要动态调节处理器频率,允许CPU在一段时间内超越它的基础频率, i7 - 8700的最大睿频频率可以达到4.6 GHz。
现代 Intel CPU 基本都支持睿频,并自动开启,同时也是可以通过配置开启或关闭的,
1 |
|
默认为0,表示开启睿频,配置为1则关闭睿频,关闭后,CPU频率稳定在 3.2 GHz左右
(超频也可以让 CPU 实际频率超过基础频率,需要 CPU 和主板支持)
除了CPU的频率可以调整吗
答案当然是肯定的,为了实现CPU调频,Linux 内核提供了 cpufreq 子模块来完成这一目的。
cpufreq 子模块
该模块包含四个部分:Core Framework 核心框架、Scaling Governor 调频器、Scaling Driver 调频驱动、Scaling Policy 调频策略,它们之间的关系如下:
(1) 核心框架,提供通用的代码框架和接口来支持CPU调频
linux/include/cpufreq.h 中定义了数据结构和接口来
(2) 调频器,实现了不同的算法来评估所需的CPU频率
1 |
|
代码展示了调频器设计的核心数据结构和方法,方法均为函数指针,不同的调频器可以自由定义实现
(3) 调频驱动,与硬件直接通信,获取调频器所需要的效能状态,提供接口进行调整
1 |
|
在配置中,cpufreq 提供了一些通用的调频器,不同的调频器提供了功能不同的调频算法,用于不同的场合。
performance 会在 scaling_max_freq 限制的范围内,尽可能进入高频率状态
powersave
会在 scaling_min_freq 限制的范围内,尽可能进入低频率状态
userspace
该调频器不做任何配置,允许通过 scaling_setspeed 自定义 CPU 频率
ondemand
定时基于 CPU 负载进行频率动态设置的方式,负载低时降频,负载高时升频(系统在忙和闲之间切换频繁时效果并不好)
conservative
与 ondemand 类似,定时基于 CPU 负载进行调频,不同在于频率调整采用逐步递变的方式。
schedutil
基于 CPU 使用率,利用内核机制 - utilization update callback, 通过负载变化回调机制来进行调频的方法(相比于 ondemand 和 conservative 的定时获取,能更快获取 CPU 负载变化进行调整)
(4) 每个 CPU 核心独有一份调频策略,保存有当前 CPU 频率状态、调频器、调频驱动和策略配置等
1 |
|
在了解 cpufreq 的结构后,我们来看看如何调整 CPU 频率。
(1) 使用 cpufreq_policy 来调整 CPU 频率
在内核初始化时,cpufreq 会创建 sysfs 目录来展示 cpufreq_policy 调频策略中的部分信息,
ls /sys/devices/system/cpu/cpufreq/policy{X}
其中,{X} 对应 CPU 核心的编号,每一个 CPU 核心都是独立的调频策略。 (对应核心的 policy 也链接到了/sys/devices/system/cpu/cpu{X}/cpufreq)
调整策略也包含一些通用的属性,cpuinfo_* 记录的是CPU硬件支持的频率信息,scaling_* 表示通过 cpufreq 进行扩展调节的所支持频率、配置等信息。
(数据定义对应 cqufreq.h 中的 cpufreq_policy )
cpuinfo_min_freq、cpuinfo_max_freq
CPU支持的最小、最大频率
cpuinfo_cur_freq
从硬件读取到的CPU当前实际频率。如果这个值不确定的话,可能不会展示。(我测试的时候是没有的)
cpuinfo_transition_latency
采用 policy 进行效能状态转换所花费的时间(ns)
affected_cpus
属于当前 policy 的 online cpu
related_cpus
属于当前 policy 的 所有 cpu,包含 online 和 offline
scaling_available_governors
当前内核提供的可用调频器或驱动提供调频算法
scaling_cur_freq
最后一次通过调频驱动获得的 CPU 频率,而非当前时刻的频率。
scaling_driver
当前使用的调频驱动
scaling_governor
当前 policy 使用的调频器或调频算法,该值可修改
scaling_min_freq、scaling_max_freq
当前 policy 允许的最小、最大频率,该值可修改(kHz)
scaling_setspeed
当使用 userspace 调频器时可用,可配置 cpu 频率(kHz)
通过 scaling_setspeed,我们可以对CPU频率进行设置,但是其受限于调频器,只有 userspace 才可以进行 CPU 频率自定义。
这是在我本地查看0号核心的输出结果,当前核心频率944 MHz,在频率范围上与i7 - 8700 的官方定义一致,调频器使用的 powersave,调频驱动使用的是intel_psate,在使用 powersave 调频器的情况下,scaling_setspeed 处于 unsupported 状态,而支持的调速器也不包含 userspace。这种情况下,需要通过其他方式来进行频率调整。
(2) 使用 cpufreq_driver 来调整 CPU 频率
在 Linux 内核源码中,intel_pstate.c 记录了其内部实现。既然是调频驱动,则必须实现 cpufreq 定义的接口,intel_pstate 定义的接口实现如下:
1 |
|
在 intel_pstate_set_policy 中,会设置生效调频策略,并通过 intel_pstate_update_perf_limits 更新 CPU 能耗配置
1 |
|
其中,max_freq 取决于是否开启睿频,未开启则取默认的最大频率,即基础频率;已开启则使用睿频频率。通过睿频可以提升 CPU 频率上限,也符合我们之前实验的结果。可是由此看来,好像没有其他方式来修改 CPU 的频率。
细看代码,通过 intel_pstate_update_perf_limits 的实现,可以发现CPU 的实际性能表现并不仅仅是通过 max_freq 决定的,还涉及一个参数 max_policy_perf,用于设定 CPU 的最大性能比例。
1 |
|
通过 intel_pstate 提供的配置入口可以进行修改
1 |
|
该值实际是设置最大的性能百分比,max_perf_pct = 50 可以限制其主频最多跑到 max_freq 的 50%。
CPU 频率对我们的程序有什么影响
来做个简单的测试,代码如下:
1 |
|
代码逻辑很简单,做循环加法,纯 CPU 运算,最后计算耗时(nano)。在不 桶的CPU 频率配置下,我们来看看结果:
核心频率 | 核心使用率 | 计算耗时(ms) |
---|---|---|
4.57 GHz | 100% | 33581 |
3.2 GHz | 100% | 47130 |
2.0 GHz | 100% | 71848 |
随着主频下降,计算耗时逐渐上升,且主频下降的比例与计算耗时的增长比例相近。主频决定了 CPU 的运算速度,因此当主频降低时,计算耗时也相应增加。
CPU 频率和使用率之间的关系
这一点上往往容易产生误区,会觉得主频越高,使用率一定高,或者使用率越高,主频也一定高。实际上,两者的关系有点像水管和水流的关系,CPU频率类似水管大小,使用率是水流,水管可以很大,水流可以很小。
还是上面的例子,只是在每次循环加之后,增加一个sleep:
1 |
|
因为每次循环计算都会sleep,所以 CPU 实际跑不满的:
本地测试CPU使用率在60%左右,而主频已经达到了4.3 GHz左右的睿频频率,此时相当于水管很大,但是水流只占水管大小的60%左右。
如果我们对 CPU 进行降频,把频率将至3.2GHz左右,相同搞定代码下,结果则会不一样:
此时同样的运算在降频后,CPU 使用率达到了100%,相当于使用了更小的水管后,水流跑满了整个水管。