20207.13 更新:以下内容不可行。在移动平台上,移动厂商限制了通过自行设置CPU亲和度来控制进程具体在哪个核心上调度的功能。所以以下做法在移动平台上是无效的。
定义
CPU亲和度(CPU Affinity)一种表征软件在特定处理器运作倾向的方法。比如目前的骁龙处理器一般是八个核心,由四个大核和四个小核,或者2个大核六个小核组成。在Android平台上,通过以下命令查看对应的CPU最大频率。频率高的就是大核,反之就是小核。
adb shell cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
通常,一个应用程序默认设置为软CPU亲和度,这表示没有限制必须在哪个核心上跑这个应用程序,由调度程序来决定它当前时刻跑在哪个CPU核心上。而硬CPU亲和度指定了应用程序只能跑在特定的一个或者多个处理核心上。这种想法来源于以下几个事实:
- 应用程序由一个核心切换到另一个核心会丢失已经缓存好的数据,导致缓存命令率下降,从而降低了性能。
- 每次应用程序被重新调度,都可能会被分配到其他核心上。在最坏的情况下,每次调度之后应用程序都在与之前不同的CPU核心上运行,这就是“乒乓效应”。
- 大核心的CPU性能会比小核心更好,在一些最求性能的应用程序上,将主进程绑定到特定的大核中,往往可以提升应用程序的整体性能。
读写CPU亲和度
在Android(接口和数据结构与Linux的大体一致)系统上,用数据结构cpu_set_t
表示CPU亲和度,它在头文件sched.h
中被定义:
#ifdef __LP64__
#define CPU_SETSIZE 1024
#else
#define CPU_SETSIZE 32
#endif
#define __CPU_BITTYPE unsigned long int /* mandated by the kernel */
#define __CPU_BITS (8 * sizeof(__CPU_BITTYPE))
typedef struct {
__CPU_BITTYPE __bits[ CPU_SETSIZE / __CPU_BITS ];
} cpu_set_t;
注意这个结构虽然包含一个数组,但其实是一个整体,每个bit都对应一个CPU核心,所以它其实是对应每个CPU核心的亲和度掩码。在32bit系统下,cpu_set_t
的内存占用是32bit,在64bit系统下,内存占用是1024bit,分别表示最多能够表示32个核心和1024个核心的CPU亲和度。同时,这个头文件还提供了一些方法用来读写这个掩码:
#define CPU_ZERO(set) CPU_ZERO_S(sizeof(cpu_set_t), set)
#define CPU_SET(cpu, set) CPU_SET_S(cpu, sizeof(cpu_set_t), set)
#define CPU_CLR(cpu, set) CPU_CLR_S(cpu, sizeof(cpu_set_t), set)
#define CPU_ISSET(cpu, set) CPU_ISSET_S(cpu, sizeof(cpu_set_t), set)
#define CPU_COUNT(set) CPU_COUNT_S(sizeof(cpu_set_t), set)
#define CPU_EQUAL(set1, set2) CPU_EQUAL_S(sizeof(cpu_set_t), set1, set2)
#define CPU_AND(dst, set1, set2) __CPU_OP(dst, set1, set2, &)
#define CPU_OR(dst, set1, set2) __CPU_OP(dst, set1, set2, |)
#define CPU_XOR(dst, set1, set2) __CPU_OP(dst, set1, set2, ^)
同时,这个头文件还提供两个函数,分别利用这个掩码来设置或者读取CPU的亲和度:
int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
下面就可以书写Native代码实战了:
#include <sched.h>
#include <unistd.h>
int SetCpuAffinity(void *p)
{
cpu_set_t mask;
CPU_ZERO(&mask);
char *cpus = (char *)p;
int size = 8;
for(int i=0;i<size;i++)
{
char cpu = cpus[i];
if(cpu == '1')
{
CPU_SET(i, &mask);
}
}
pid_t pid = getpid();
if(sched_setaffinity(pid, sizeof(cpu_set_t), &mask)==-1)
return -1;
return 0;
}
int GetCpuAffinity(void *p)
{
cpu_set_t mask;
pid_t pid = getpid();
if(sched_getaffinity(pid, sizeof(cpu_set_t), &mask) == -1)
return -1;
char *a = (char *)p;
for(int i=0;i<CPU_SETSIZE;i++)
{
if(CPU_ISSET(i, &mask))
a[i] = '1';
else
a[i] = '0';
}
return 0;
}
然后参考文章 Unity 与 NDK 开发 里介绍的方法,将这两个函数暴露给Unity的C#层调用。C#层可以这样使用这两个代码:
IntPtr getP = Marshal.AllocHGlobal(100 * sizeof(char));
string Test_GetCpuAffinity()
{
GetCpuAffinity(getP);
string cpuInfo = Marshal.PtrToStringAnsi(getP);
Debug.Log(cpuInfo);
return cpuInfo;
}
void Test_SetCpuAffinity()
{
string cpus = "10000000"; // 绑定到核心0上,剩下的都为0
e_SetCpuAffinity(cpus);
}