源代码中插入调频点实现动态调频实验过程记录

写在前面

  1. 本文提及的大多数工作都需要root权限,后文中就不再一一提及。
  2. 本文也充当一个学习笔记的功能,因此补充的资料和相关知识较多,如果仅需找到具体操作,可以跳过介绍,直接在大纲中找到关于具体操作的索引
  3. 由于本文代码是直接在vim里抠下来的,缩进应该是两个tab,未作更改

前置知识

  • 睿频(Turbo Boost):英特尔处理器中的一项技术,旨在提高单个处理器核心的性能。它可以在需要更多计算能力时,自动动态调整处理器的工作频率,以提供更高的性能水平。

  • CPU频率管理驱动(Driver):一种软件或模块,用于操作系统与处理器之间的交互,以管理和调整处理器的工作频率。它可以根据系统负载、性能需求和功耗优化等因素,动态地修改处理器的频率,以平衡性能和能效。
    目前有以下几种常见的驱动程序

    • ACPI CPU 频率管理驱动(acpi-cpufreq):一种在 Linux 系统中广泛使用的 CPU 频率管理驱动。它通过 ACPI 接口与处理器通信,获取处理器的状态信息,并根据需要调整频率。
    • Intel P-state 驱动(intel_pstate):一种在英特尔处理器上使用的驱动。它使用英特尔处理器内部的硬件支持,根据负载情况自动选择最佳频率。Intel P-state 驱动在较新的 Linux 内核版本中被广泛采用。
    • Ondemand 驱动:一种旧版的 CPU 频率管理驱动,在一些较老的处理器上使用。它通过检测负载情况,根据系统负载动态地调整处理器的频率。(并不常见)

简易版解释

  • 睿频:简单说就是CPU超频设置的总开关,可以在BIOS中手动设置
  • CPU频率管理驱动:对于Intel的处理器而言,默认使用的驱动是Intel P-state驱动,这种驱动中只有自动调频的模式。如果需要手动调频,我们需要更换为ACPI CPU驱动,具体步骤见下文。
由于作者知识有限,部分概念不能准确表述,在此提供一些相关资料以供读者自行了解

资料补充
英特尔® 睿频加速技术
Intel SpeedStep®动态节能技术的常见问题。
linux 模块与驱动程序,Linux 内核模块和驱动程序的详细编写
简单认识驱动与模块


基本环境信息

  • CPU型号:GOLD6338
  • CPU架构:AMD64
  • Linux发行版:Ubuntu 20.04.2

仅展示实现过程相关项


实现过程

环境配置

软件安装

我们需要下载用于在线调节CPU时钟频率的工具包cpufrequtils,安装命令如下(不同的发行版可能不同)

apt-get install cpufrequtils


cpufrequtils下主要包含两个程序(具体的使用在后文中介绍)

/usr/bin/cpufreq-info  # used to show the general CPU information
/usr/bin/cpufreq-set   # used for setting

可以通过查看这两个程序来确定cpufrequtils是否成功安装(以cpufreq-info为例子)

cpufreq-info -c -0  # 展示cpu0的相关信息


正确安装cpufrequtils后应当返回的信息大致是如下样式

analyzing CPU 0:
  driver: intel_pstate
  CPUs which run at the same hardware frequency: 0
  CPUs which need to have their frequency coordinated by software: 0
  maximum transition latency:  Cannot determine or is not supported.
  hardware limits: 800 MHz - 4.20 GHz
  available frequency steps:  4.20 GHz, 4.10 GHz, 4.00 GHz, 3.90 GHz, 3.80 GHz, 3.70 GHz, 3.60 GHz, 3.50 GHz, 3.40 GHz, 3.30 GHz, 3.20 GHz, 3.10 GHz, 3.00 GHz, 2.90 GHz, 2.80 GHz, 2.70 GHz, 2.60 GHz, 2.50 GHz, 2.40 GHz, 2.30 GHz, 2.20 GHz, 2.10 GHz, 2.00 GHz, 1.90 GHz, 1.80 GHz, 1.70 GHz, 1.60 GHz, 1.50 GHz, 1.40 GHz, 1.30 GHz, 1.20 GHz, 1.10 GHz, 1.00 GHz, 900 MHz, 800 MHz
  available cpufreq governors: performance, powersave
  current policy: frequency should be within 800 MHz and 4.20 GHz.
                  The governor "powersave" may decide which speed to use
                  within this range.
  current CPU frequency is 800 MHz.

其他软件如 cpupower 也提供了类似功能,这里仅以 cpufrequtils 工具包为例说明

关闭睿频

关于睿频的相关介绍如上,这里主要讲关闭睿频的具体步骤

在摸索着进行此项工作时,我采取的方式是直接进入BIOS关掉睿频
实际上进入BIOS的方式和BIOS中的具体内容呈现都和使用的主板相关,因此在具体实现时的具体操作可能不同。要做的就是进入BIOS,找到“睿频”,并将“启用”切换为“禁用”(当然也可能不是中文显示的)

后来在别的资料中看到也可以在使用Intel P-state 驱动的情况下,使用以下命令关闭睿频

echo 1 >  /sys/devices/system/cpu/intel_pstate/no_turbo

相反的也可以通过echo 0来开启睿频

echo 0 >  /sys/devices/system/cpu/intel_pstate/no_turbo

该命令主要是找到系统文件 no_turbo 并进行更改,如果在不同的架构或者驱动下,也可以尝试通过一下命令找到该文件的具体位置并进行修改

find / -name "no_turbo"

照相关资料的说法是可以即可生效,如果没有生效的话也许可以通过重启服务器解决。
当然事实上也可能找不到 no_turbo ,这个时候就老老实实去BIOS里改吧

由于最开始在摸索着实现动态调频时对睿频的理解还比较浅显,因此采取了直接关闭睿频的做法
现在想来事实上跟BIOS直接相关的也只有performance模式(后文将详细解释),因此关闭睿频未必是个必要步骤。这里暂且存疑,之后确定了再来完善。

  • 目前状态:存疑

推荐一篇介绍BIOS的小文章《如何进入BIOS?》
Intel官方的问题解决 英特尔® 睿频加速技术是如何启用或禁用的?
关于通过修改no_turbo关闭睿频的介绍 linux intel 关闭睿频

更改驱动

相关介绍

关于驱动的介绍如上,这里需要了解的还有驱动所对应的可用频率调节策略。
例如在上文中输入cpufreq-info -c 0后返回的CPU0的信息中

analyzing CPU 0:
  driver: intel_pstate
  ...
  available cpufreq governors: performance, powersave
  ...

这里展示了driver(驱动)和available cpufreq governors(可用频率调节策略)信息
而能进行手动设置频率的模式是userspace

Intel P-state 驱动(intel_pstate)在 CPU 频率和性能管理方面使用了不同的方法,不像其他传统的 cpufreq 驱动程序那样使用多个 governor 选项,而仅仅有performance, powersave两种模式
而传统的ACPI CPU 驱动(acpi-cpufreq)在大多数主板上都会配置,该驱动提供了conservative, ondemand, userspace, powersave, performance五种频率调节策略

  • Performance(性能):将 CPU 频率设置为最大值,以获取最佳的计算速度。在此模式下,CPU 会以最高频率运行,以提供最大的处理能力,但同时也会消耗更多的功耗。(CPU会固定工作在其支持的最高运行频率上)

  • Powersave(节能):将 CPU 频率设置为较低的值,以最大程度地降低功耗和热量产生。这种模式适用于需要长时间运行的轻负载任务,如普通办公应用程序,以节约电能并保持低温运行。(CPU会固定工作在其支持的最低运行频率上)

  • Ondemand(按需):根据 CPU 负载动态调整频率。当 CPU 负载较低时,它会自动将频率降低以节省功耗。当负载增加时,它会自动将频率提高以提供更好的响应能力。这种模式旨在平衡性能和功耗之间的关系。(也是动态调整,不过比较极端,cpufreq一使用则立刻最高,用完直接最低)

  • Conservative(保守):类似于 Ondemand 模式,但它更加保守。它倾向于在负载开始变高之前就主动提高频率,以提供更快的响应能力。这种模式适用于需要减少频繁频率切换的任务,以减少功耗波动和响应延迟。(动态调整,load高则高频,load低则低频)

  • Userspace(用户态):允许用户自定义 CPU 频率。用户可以通过特定的工具程序,如 cpufrequtils,在用户空间手动设置所需的频率。这种模式提供了更高的灵活性,使用户能够根据具体需求来调整 CPU 频率。(让用户态的程序可以通过接口动态调整cpufreq)

因此我们需要将驱动从 intel_pstate 更换为 acpi-cpufreq,并将频率调节策略更换为userspace

具体操作

只需要进入etc/default/grub文件并在 GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" 后添加intel_pstate=disable
具体命令和示例如下

vi /etc/default/grub

会显示如下界面
未更改
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" 后添加intel_pstate=disable

在这里插入图片描述
修改完成后更新grub

 update-grub

然后可以通过

cpufreq-info -c -0

查看驱动是否修改成功,如果修改成功,返回的信息应为

analyzing CPU 0:
  driver: acpi-cpufreq
  ...
  available cpufreq governors: conservative, ondemand, userspace, powersave, performance
  ...

如果返回信息中的driver仍然显示intel_pstate,可以尝试通过重启服务器完成更换。

命令行控制

基本命令

cpufreq-info  # 展示所有CPU相关信息
cpufreq-set   # 对所有CPU进行设置

相关选项

cpufreq-info/set -c ...        # 命令将特定作用于...号CPU
cpufreq-info -f                # 显示CPU频率(如果不指定就显示cpu0)
cpufreq-set -f ...             # 将cpu频率设置为...(如果不指定就作用于cpu0)
cpufreq-info -g                # 显示可用的CPU频率调节模式
cpufreq-set -g ...             # 将cpu频率调节模式设置为...(如果不指定就作用于cpu0)

具体步骤

  1. 将CPU调节模式设置为userspace(为避免关于具体分配核映射的讨论,这里采用更改所有cpu的方式)
cpufreq-set -g userspace
  1. 将CPU频率设定为需要的值
cpufreq-set -f 1000000
  1. 通过cpufreq-info查看更改效果(查看时仅需以cpu0为代表,避免返回信息冗杂)
cpufreq-info -c 0

以上只展示了部分常用和调节过程中会用到的命令,提供几个比较详细的相关资料以供读者自行查看
How to use cpufrequtils
CPU Performance Scaling
比较通俗和常用的介绍 ubuntu控制cpu的频率

源代码控制

由于要实现的是在诸如C++源码中插入调频点并实现频率调节,下面要进行的是将这个命令加入到源码中
C/C++中提供了执行操作系统命令的函数

int system( const char* command );

command即相当于直接输入到terminal的命令,返回值是该命令的执行情况

具体说明可见于 std::system

具体方式

将所有cpu的频率调节至指定值

void transcpufreq_all(int tarfreq)
{
		string command="cpufreq-set -f "+to_string(tarfreq); //cpufreq-set -f 900000
        int ret=system(command.c_str());
        if(ret==0)
                cout<<"all cpufreq are set to "<<tarfreq<<endl;
        else
                puts("defeat");
        return;
}

将某一cpu的频率调节至指定值

void transcpufreq_spe(int tarcpu,int tarfreq)
{
        string command="cpufreq-set -c "+to_string(tarcpu)+" -f "+to_string(tarfreq); //cpufreq-set -c 0 -f 900000
        // cout<<command;
        int ret=system(command.c_str());
        if(ret==0)
                cout<<"The frequency of cpu"<<tarcpu<<" is set to "<<tarfreq<<endl;
        else
                puts("defeat");
        return;
}

项目构建

为便于函数调用和具体测试,构建一个比较简单的项目如下,仅供参考

  • Project
    • build
      • main                    // 主文件的可执行程序
      • trans.o                 // trans.cpp的编译文件
      • forward.sh           // 用于读取程序执行信息时性能参数的脚本,后文会详细介绍
    • include
      • trans.h                 // 解释文件,依赖于trans.cpp
    • src
      • trans.cpp             // 调节频率的函数
      • main.cpp             // 测试程序
    • compile.sh                 // 整个项目的编译脚本



所有相关代码附于文末

具体使用过程如下

  1. main.cpp的构建

main.cpp设置为需要插入调频点的程序
在需要调频的地方直接插入

transcpufreq_all(int tarfreq)

如果是做简单的测试仅需要反复调用函数

void Delay()
{
        int i,j;
        for(i=0;i<int(1e7);i++)          //具体参数依照实际情况设置
                j=i;
}
void Test()
{
        for(int i=1;i<=20;i++)
                Delay();
}

也即

int main()
{
		transcpufreq_all(1000000);
		Test();
		transcpufreq_all(800000);
		Test();
		transcpufreq_all(1500000);
		Test();
		...
		return 0;
}
  1. 调用项目编译脚本

Project 目录下使用

./compile.sh

3.执行main

经过编译后 build 中会生成可执行文件 main,在 build 目录下使用

./main

这样就可以让设置了调频点的程序简单的跑起来了,但如果需要进一步的读取程序执行时的性能参数,还需要进行后文中的工作。


结果提取

功耗实时读取

主要用到的工具是 perf ,用于在程序执行过程中实时读取功耗

perf stat -e power/energy-pkg/ -I 100 ./main

可以通过> out.txt的方式记录到文本中

perf stat -e power/energy-pkg/ -I 100 ./main > out.txt

如果要将程序中设置的一些标记点的输出一起输出到该文件,可以使用

perf stat -e power/energy-pkg/ -I 100 ./main > out.txt 2>&1
# 2>&1 表示将标准错误流(stderr)重定向到标准输出流(stdout)

这里只简单的使用了 perf 的极小部分功能,更详尽的使用方法介绍可见于相关资料

更详细的介绍资料 :
用于上手的介绍 perf record对C++程序耗时进行分析
比较全面的介绍 系统性能分析工具:perf

主频实时读取

开始设想的是能在perf list中找到能够反应cpu主频的event,但我所使用的处理器架构中似乎并没有相关的
因此采取了比较笨拙的读法

#!/bin/bash
./main &
while true; do
    cpufreq_info=$(cpufreq-info -f)
    echo "Current CPU Frequency: $cpufreq_info"
    sleep 0.1
done

通过“后台运行符”(background operator&的标识,将任务放在后台执行,而不会阻塞终端或当前脚本的执行,从而实时的多次读取主频信息,通过返回的主频信息也能检验设置的频率调节点是否成功更改CPU频率。

脚本控制和一些简单的原理介绍

然后通过脚本把上述两项和在一起就好了,也就是上述 forwar.sh 的功能

#!/bin/bash
perf stat -e power/energy-pkg/ -I 100 ./main > output.txt &

while true; do
    cpufreq_info=$(cpufreq-info -f)
    echo "Current CPU Frequency: $cpufreq_info"
    sleep 0.1
done

同样的,如果要将源代码输出的一些标识信息一并输出到out.txt的话,就加上2>&1就好啦

补充一些关于pipe的说法

由于作者目前也还是一知半解,就简单地描述一下上述过程

cpufreq-infoperf 命令是同时执行的,它们并不会相互阻塞。具体来说,该脚本使用了Linux的管道(pipe)机制,将 perf 的输出通过管道传递给 tee 命令,而 tee 命令则将数据流分别重定向到文件和标准输出流中。同时,脚本也在循环中调用了 cpufreq-info 命令,获取当前CPU频率并打印输出。

这样,脚本实现了在监测程序运行过程中,同时读取CPU频率和功耗的操作。可以通过文件输出和屏幕输出两个渠道获得这些信息。具体流程如下:

  1. 执行perf命令启动perf的性能监测,并将其输出重定向到output.txt文件中;
  2. perf产生的输出会传递给管道,在管道中通过tee命令将数据分别写入文件和标准输出流中;
  3. 脚本在循环中每次调用cpufreq-info命令获取CPU频率,并在终端屏幕上打印输出。

由于perf和cpufreq-info是独立的进程,它们不会相互阻塞,得以同时监测程序的功耗和CPU频率,同时不会影响程序的执行。

对所执行程序的影响

当然这两个命令肯定还是会占据一定的CPU资源,但 cpufreq-info 本身并不会产生太多的 CPU 占用,它只是获取当前 CPU 的频率。在循环中每次调用它并不会对执行程序造成明显的影响;perf 命令也对自身的CPU占用做了优化,在默认情况下,它只会占用一个 CPU 核心进行监测,以避免对程序的执行造成过多影响。

整体来说所产生的影响应该是非常小的。
而就测试实际情况来看,在加入监控性能的指令后无论从功耗还是执行时间的角度来看,都稳定的产生了影响;如果是进行较大规模或精确度需求较高的测试时,还需要进一步的测量来消除这部分误差。

一些相关资料
Linux进程间通信——pipe应用实例
https://blog.csdn.net/Exziro/article/details/72876979
图解 | Linux进程通信 - 管道实现


数据处理

这里还没有特别详细的处理,之后做了相关工作再填坑吧。

后续工作

主要是一些实际测试的实验,之后再填咯。


代码

compile.sh

#!/bin/bash

src_path="./src"
icd_path="./include"
bld_path="./build"

main_file="${src_path}/main.cpp"

g++ -c ${src_path}/trans.cpp -I ${icd_path} -o ${bld_path}/trans.o

g++ ${main_file} ${bld_path}/trans.o -I ${icd_path} -o ${bld_path}/main


# auto-link will be writen later

forward.sh

#!/bin/bash

cpp_executable="/home/wiz/cputrans/Project_Gauss/build/main"

perf_command="perf stat -e power/energy-pkg/ -I 100"

$perf_command $cpp_executable > output.txt &

while true; do
    cpufreq_info=$(cpufreq-info -f)
    echo "Current CPU Frequency: $cpufreq_info"
    sleep 0.1
done

trans.h

#ifndef TRANS_H
#define TRANS_H

void transcpufreq_spe(int tarcpu,int tarfreq);

void transcpufreq_all(int tarfreq);

#endif

trans.cpp

#include "trans.h"
#include<bits/stdc++.h>
using namespace std;

void transcpufreq_spe(int tarcpu,int tarfreq)
{
        string command="cpufreq-set -c "+to_string(tarcpu)+" -f "+to_string(tarfreq); //cpufreq-set -c 0 -f 900000
        // cout<<command;
        int ret=system(command.c_str());
        if(ret==0)
                cout<<"The frequency of cpu"<<tarcpu<<" is set to "<<tarfreq<<endl;
        else
                puts("defeat");
        return;
}

void transcpufreq_all(int tarfreq)
{
        string command="cpufreq-set -f "+to_string(tarfreq); //cpufreq-set -f 900000
        int ret=system(command.c_str());
        if(ret==0)
                cout<<"all cpufreq are set to "<<tarfreq<<endl;
        else
                puts("defeat");
        return;
}

main.cpp

#include "trans.h"
#include<bits/stdc++.h>
#include<ctime>
#include<chrono>
using namespace std;

auto start = std::chrono::high_resolution_clock::now();

void Delay()
{
        int i,j;
        for(i=0;i<int(1e7);i++)
                j=i;
}

void Test1()
{
        for(int i=1;i<=20;i++)
                Delay();
}

void Test2()
{
        for(int i=1;i<=50;i++)
                Delay();
}

void Test3()
{
        for(int i=1;i<=100;i++)
                Delay();
}

void PutTime()
{
/*
        time_t now = time(nullptr);
        tm* timestamp = localtime(&now);
        cout << "Timestamp: " << asctime(timestamp) << endl;
*/
        auto end = chrono::high_resolution_clock::now();
        chrono::duration<double, std::milli> duration = end - start;
        double executionTime = duration.count();
        cout << "Execution Time: " << executionTime << " ms" << endl;
        return;
}

/*
void transcpufreq_spe(int tarcpu,int tarfreq);

void transcpufreq_all(int tarfreq);
*/

int main()
{
        int frq1=800000,frq2=1000000,frq3=1500000;
        transcpufreq_all(frq1);
        cout<<"-------------------------------------------Test1-------------------------------------------\n";
        PutTime();
        Test1();
        PutTime();

        transcpufreq_all(frq2);
        cout<<"-------------------------------------------Test2-------------------------------------------\n";
        PutTime();
        Test2();
        PutTime();

        transcpufreq_all(frq3);
        cout<<"-------------------------------------------Test3-------------------------------------------\n";
        PutTime();
        Test3();
        PutTime();
        return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值