速度优化:绑定 CPU 大核

3,002 阅读3分钟

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

目前手机的 CPU 都是多核的,比如骁龙 8gen3 这款 CPU 就有 8 个核心,其中大核 Cortex-X4 的性能最好,时钟周期频率为 3.3GHZ,其他核心的性能就要差很多,其中两颗小核 Cortex-A520 的时钟频率只有 2.27GHZ。如果用大核来执行主线程,自然会让主线程在执行 UI 渲染等逻辑时拥有更快的速度。

一 线程绑核函数

Linux 系统提供了 pthread_setaffinity_np 和 sched_setaffinity 这两个函数用于将指定的线程绑定到指定的核心上,但是在 Android 系统中,屏蔽了 pthread_setaffinity_np 函数的使用,所以我们只能通过 sched_setaffinity 函数来进行绑核操作,函数如下:

#include <sched.h>
int sched_setaffinity(pid_t pid, size_t cpusetsize,cpu_set_t *mask);
  • 第一个入参 pid 指的是线程的 id,如果 pid 的值为 0,则表示是主线程
  • 第二个入参 cpusetsize 是第三个入参 mask 的长度
  • 第三个入参 mask 是需要绑定的 CPU 序列的掩码

通过这个函数实现线程绑核的代码如下:

void bindCore(int coreNum){
    cpu_set_t mask;    //CPU核的集合    
    CPU_ZERO(&mask);    //将mask置空     
    CPU_SET(coreNum,&mask);   //将需要绑定的CPU核序列设置给mask,核为序列0,1,2,3……     
    if (sched_setaffinity(0, sizeof(mask), &mask) == -1){    //将主线程绑核
         printf("bind core fail");
    }
}

通过上面的代码中,便实现了将主线程绑定到了核心序列为 coreNum 的 CPU 核上。我们接着还需要进一步确定哪一个 CPU 核心为大核心。

二 获取大核序列

通过 /sys/devices/system/cpu/ 目录下的文件,可以查看当前设备有几个 CPU 核心 。笔者用来测试的是一台 Pixel3,可以看到有 cpu0 到 cpu7 共 8 个 CPU 核心。

/sys/devices/system/cpu $ ls 
core_ctl_isolated  cpu1  cpu3  cpu5  cpu7     cpuidle                hang_detect_gold    hotplug   kernel_max  offline  possible  present
cpu0               cpu2  cpu4  cpu6  cpufreq  gladiator_hang_detect  hang_detect_silver  isolated  modalias    online   power     uevent

接着进入到某个 CPU 核心对应的 cpufreq 文件,即可查看具体某个 CPU 核心的详细参数,下面是序列为 0 的 CPU 核心的详细数据:

/sys/devices/system/cpu/cpu0/cpufreq $ ls
affected_cpus     cpuinfo_max_freq  cpuinfo_transition_latency  scaling_available_frequencies  scaling_boost_frequencies  scaling_driver    scaling_max_freq  scaling_setspeed  stats
cpuinfo_cur_freq  cpuinfo_min_freq  related_cpus                scaling_available_governors    scaling_cur_freq           scaling_governor  scaling_min_freq  schedutil

该文件下的 cpuinfo_max_freq 就是当前 CPU 核心的时钟周期频率。下面是笔者的测试机 Piexl3 骁龙 845 芯片的每个核的时钟周期频率。可以看到 4、5、6、7 序列都是大核,时钟频率为 2.8GHZ,而其他的小核只有 1.7GHZ。

/sys/devices/system/cpu $ cat cpu0/cpufreq/cpuinfo_max_freq
1766400
/sys/devices/system/cpu $ cat cpu1/cpufreq/cpuinfo_max_freq
1766400
/sys/devices/system/cpu $ cat cpu2/cpufreq/cpuinfo_max_freq
1766400
/sys/devices/system/cpu $ cat cpu3/cpufreq/cpuinfo_max_freq
1766400
/sys/devices/system/cpu $ cat cpu4/cpufreq/cpuinfo_max_freq
2803200
/sys/devices/system/cpu $ cat cpu5/cpufreq/cpuinfo_max_freq
2803200
/sys/devices/system/cpu $ cat cpu6/cpufreq/cpuinfo_max_freq
2803200
/sys/devices/system/cpu $ cat cpu7/cpufreq/cpuinfo_max_freq
2803200

所以在代码实现中,只需要遍历 /sys/devices/system/cpu/ 目录下的 CPU 节点,然后读取 cpuinfo_max_freq 文件的值就能找到大核了,下面是详细的代码实现:

  1. 统计该设备 CPU 有多少个核。
int getNumberOfCPUCores() {
    int cores = 0;
    DIR *dir;
    struct dirent *ent;
    if ((dir = opendir("/sys/devices/system/cpu/")) != NULL) {
        while ((ent = readdir(dir)) != NULL) {
            std::string path = ent->d_name;
            if (path.find("cpu") == 0) {
                bool isCore = true;
                for (int i = 3; i < path.length(); i++) {
                    if (path[i] < '0' || path[i] > '9') {
                        isCore = false;
                        break;
                    }
                }
                if (isCore) {
                    cores++;
                }
            }
        }
        closedir(dir);
    }
    return cores;
}
  1. 读行遍历每个核,找出时钟频率最高的那个核。
int getMaxFreqCPU() {
    int maxFreq = -1;
    for (int i = 0; i < getNumberOfCPUCores(); i++) {
        std::string filename = "/sys/devices/system/cpu/cpu" + 
                                std::to_string(i) + "/cpufreq/cpuinfo_max_freq";
        std::ifstream cpuInfoMaxFreqFile(filename);
        if (cpuInfoMaxFreqFile.is_open()) {
            std::string line;
            if (std::getline(cpuInfoMaxFreqFile, line)) {
                try {
                    int freqBound = std::stoi(line);
                    if (freqBound > maxFreq) maxFreq = freqBound;
                } catch (const std::invalid_argument& e) {
                    
                }
            }
            cpuInfoMaxFreqFile.close();
        }
    }
    return maxFreq;
}

至此,便找出了大核的序列,然后调用 sched_setaffinity 进行绑核即可。除了主线程,也可以根据业务需要,将其他核心线程,比如渲染线程等线程绑定大核。当我们通过上面的逻辑将主线程绑定大核后,可以通过抓取 Trace 到 Pefetto 来验证目标线程运行在哪个核上,以此来确认是否绑定成功。