获取线程ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uint64_t getThreadId(void)
{
#if defined(_WIN32) || defined(_WIN64)
return GetCurrentThreadId(),
#elif defined(__APPLE__)
// return pthread_self();
uint64_t tid;
pthread_threadid_np(NULL, &tid); // 更准确,与 lldb 和 NSLog 中的线程ID一致。
return tid;
#elif defined (__ANDROID__)
return gettid();
#else
return syscall(__NR_gettid);
#endif
}

获取 CPU 核数

C++ 11

1
2
3
4
#include <thread>

//may return 0 when not able to detect
const auto processor_count = std::thread::hardware_concurrency();

sysconf

适用于 android、iOS、macOS(>= 10.4,Tiger)平台。

1
2
3
4
5
6
7
8
9
10
#include <unistd.h>

// 获取CPU核心数(包含禁用的)
long result = sysconf(_SC_NPROCESSORS_CONF);

// 获取可用的CPU核心数
long result = sysconf(_SC_NPROCESSORS_ONLN);

// 最大句柄数
long result = sysconf(_SC_OPEN_MAX));

Android 源码中,sysconf 最终会调用到 get_nprocs()/get_nprocs_conf(), 读取系统目录 sys/devices/system/cpu 下的文件,进行统计返回。

  1. _SC_NPROCESSORS_CONF ==> get_nprocs_conf() ==> 读取系统目录sys/devices/system/cpu下的文件,并匹配 cpu? 形式的目录来统计数目 ==> 返回系统CPU核心数
  2. _SC_NPROCESSORS_ONLN ==> get_nprocs() ==> 读取系统文件/sys/devices/system/cpu/online的值 ==> 获取当前系统可用的CPU核心数

sysctl

适用于 iOS、macOS。

1
2
3
4
5
6
7
8
9
#include <sys/sysctl.h>

int mib[2];
mib[0] = CTL_HW;
mib[1] = HW_NCPU;

int cpuNum;
size_t len = sizeof(cpuNum);
sysctl(mib, 2, &cpuNum, &len, NULL, 0);

Win32

1
2
3
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
int numCPU = sysinfo.dwNumberOfProcessors;

ObjectiveC

1
2
3
4
#import <Foundation/Foundation.h>

NSUInteger a = [[NSProcessInfo processInfo] processorCount];
NSUInteger b = [[NSProcessInfo processInfo] activeProcessorCount];

获取运行当前线程的 CPU 核

syscall

适用于 android 平台。

1
2
3
4
5
6
7
8
#include <unistd.h>
// 获取当前运行的CPU核
size_t cpu = 0;
int iRet = syscall(__NR_getcpu, &cpu, NULL, NULL);
if(iret == 0)
{
printf("current thread[%d-%d]) is running on cpu:%d \n", gettid(), getpid(), cpu);
}

设置亲和性(绑核)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include <unistd.h>

#ifdef __APPLE__
#include <mach/mach.h>
#import <Foundation/Foundation.h>
#endif

bool setCPUAffinity(pthread_t pThread, uint32_t affinity = 0)
{
if (affinity == 0)
{
return false;
}

int cpuNum = getCPUCoreNum();
if (cpuNum <= 0)
{
return false;
}

#if defined(__LINUX__)
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
for (uint32_t i = 0; i < cpuNum; i++)
{
uint32_t mask = 1 << i;
if (mask & affinity)
{
CPU_SET(i, &cpuset);
printf("Set thread [%d] to CPU %d \n", pThread, i);
}
}

int iRet = pthread_setaffinity_np(pThread, sizeof(cpu_set_t), &cpuset);
if (iRet != 0)
{
printf("Failed to set thread [%p] affinity, ret:%d, errno[%d] \n", pThread, iRet, errno);
return false;
}

#elif defined(__APPLE__)
//works on simulators, but definitely doesn't work on iPhone
thread_port_t mach_thread = pthread_mach_thread_np(pThread);
if(mach_thread == MACH_PORT_NULL)
{
return false;
}

// set thread affinity
thread_affinity_policy_data_t policy;
policy.affinity_tag = affinity;

// KERN_SUCCESS
int iRet = thread_policy_set(mach_thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&policy, THREAD_AFFINITY_POLICY_COUNT);
if (iRet != KERN_SUCCESS && iRet != KERN_NOT_SUPPORTED)
{
printf("Failed to set thread [%p] affinity, ret:%d, errno[%d] \n", pThread, iRet, errno);
return false;
}

#elif defined(__ANDROID__)
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
for (uint32_t i = 0; i < cpuNum; i++)
{
uint32_t mask = 1 << i;
if (mask & affinity)
{
CPU_SET(i, &cpuset);
printf("Set thread [%d] to CPU %d \n", pThread, i);
}
}

int iRet = syscall(__NR_sched_setaffinity, gettid(), sizeof(cpu_set_t), &cpuset);
//int iRet = sched_setaffinity(gettid(), sizeof(cpu_set_t), &cpuset);
if (iRet != 0)
{
printf("Failed to set thread [%p] affinity, ret:%d, errno[%d] \n", pThread, iRet, errno);
return false;
}

#endif
return true;
}


bool getCPUAffinity(pthread_t pThread, uint32_t& affinity)
{
affinity = 0;

int cpuNum = getCPUCoreNum();
if (cpuNum <= 0)
{
return false;
}

#if defined(__LINUX__)
cpu_set_t cpuset;
CPU_ZERO(&cpuset);

int iRet = pthread_getaffinity_np(pThread, sizeof(cpu_set_t), &cpuset);
if (iRet != 0)
{
printf("Failed to get thread [%p] affinity, ret:%d, errno[%d] \n", pThread, iRet, errno);
return false;
}

for (uint32_t i = 0; i < cpuNum; i++)
{
if (CPU_ISSET(i, &cpuset))
{
printf("Get thread [%d] on CPU %d \n", pThread, i);
affinity |= (1 << i);
}
}

#elif defined(__APPLE__)
thread_port_t mach_thread = pthread_mach_thread_np(pThread);
if(mach_thread == MACH_PORT_NULL)
{
return false;
}

boolean_t get_default = false;
mach_msg_type_number_t count = THREAD_AFFINITY_POLICY_COUNT;
thread_affinity_policy_data_t policy;
policy.affinity_tag = 0;

int iRet = thread_policy_get(mach_thread, THREAD_AFFINITY_POLICY, (thread_policy_t)&policy, &count, &get_default);
if (iRet != KERN_SUCCESS && iRet != KERN_NOT_SUPPORTED)
{
printf("Failed to set thread [%p] affinity, ret:%d, errno[%d] \n", pThread, iRet, errno);
return false;
}

affinity = policy.affinity_tag;

#elif defined(__ANDROID__)
cpu_set_t cpuset;
CPU_ZERO(&cpuset);

int iRet = syscall(__NR_sched_getaffinity, gettid(), sizeof(cpu_set_t), &cpuset);
// int iRet = sched_getaffinity(gettid(), sizeof(cpu_set_t), &cpuset);
if (iRet != 0)
{
printf("Failed to get thread [%p] affinity, ret:%d, errno[%d] \n", pThread, iRet, errno);
return false;
}

for (uint32_t i = 0; i < cpuNum; i++)
{
if (CPU_ISSET(i, &cpuset))
{
printf("Get thread [%d] on CPU %d \n", pThread, i);
affinity |= (1 << i);
}
}
#endif

return true;
}
  1. sched_setaffinity 即可以设置进程绑核,也可以对线程进行绑核。Android 平台需要通过 syscall 间接调用,直接调用 sched_setaffinity 无效。
  2. thread_policy_set/thread_policy_set 在 iphone 真机(arm)验证,返回 KERN_NOT_SUPPORTED。在模拟器(x86)验证,调用成功。

工具

Systrace(Android)

命令行方式

Android SDK 中提供了 systrace 命令,用于跟踪系统的 I/O 操作、内核工作队列、CPU 负载以及 Android 各个子系统的运行状况等。命令路径在 android-sdk/platform-tools/systrace 文件夹下。

1
2
3
4
# 进入到 SDK/platform-tools/systrace 目录
# 运行 python systrace.py {tags}

python systrace -t 10 -o my_systrace.html sched freq idle

常用的 options:

options 描述
-h 帮助
-l 列举可用的tags,也可以使用命令: adb shell atrace –list
-o < FILE > 输出的目标文件,默认当前目录
-t < TIME > 执行时间,默认5s
-a < APP_NAME > 追踪应用包名,用逗号进行分隔
-e < DEVICE_SERIAL > 指定设备

抓取到的 my_systrace.html 需要使用 chrome 浏览器打开。

首次执行时,可能遇到 Python 执行错误:No module named xxx(如 win32con、six 等)。此时,执行 pip install xxx 即可。

图形操作

在 Android 9(API 级别 28)或更高版本的设备上,还可以使用“系统跟踪”系统应用生成 Systrace 报告,然后在 Perfetto 界面 中打开。使用步骤:

  1. 启用开发者选项。在调试部分,进入 System Tracing(系统跟踪)菜单,打开 Show Quick Settings tile(显示快捷设置图标)选项。
  2. 通过系统快捷设置,开启录制。
  3. 运行 app 逻辑。
  4. 通过系统快捷设置,关闭录制。(一般会缓存最近的 10-30 秒事件)
  5. 通过分享或者 adb 命令行方式获取录制的 Perfetto 文件 .perfetto-trace(老版本为 Systrace 文件 .ctrace)。
  6. 使用 Perfetto 打开 trace 文件并进行分析。(对于 Perfetto 文件,使用 Open trace file。对于 Systrace 文件,使用 Open with legacy UI)

示例

1
2
3
4
5
# adb 获取录制的 trace 文件
adb pull /data/local/traces/ .

# 将 trace 文件生成 html 报告
systrace --from-file {xxx.ctrace | xxx.perfetto-trace}

Perfetto 要用 最新版本的 chrome 浏览器。

System Trace(iOS)

Snapdragon profiler(Android)

对于使用高通骁龙芯片的安卓手机(Android 5.0以上)可以使用 Snapdragon profiler (简称 sdp)进行系统跟踪。此工具可以进行截帧分析,捕获CPU、GPU、DSP、内存、功率、网络连接和设备运行时的发热数据等等。

snapdragon

sdp 既可以通过 UI 运行,也可以在命令行中运行。如果遇到异常问题(闪退、无响应等),可以在 console 日志中找到原因。

sdp 支持以下三种分析方式:

  1. 实时性能数据(RealTime)
  2. 时间线抓取分析(Trace Capture)
  3. 抓帧分析(Snapshot Capture)

Snapdragon profiler 依赖 adb,macOS 上需要安装 mono。

参考文献

https://developer.android.com/ndk/guides/cpu-features
https://www.cnblogs.com/roger-yu/p/15233874.html
https://mlog.club/article/1863514
https://developer.android.com/topic/performance/tracing/command-line?hl=zh-cn
https://developer.qualcomm.com/software/snapdragon-profiler
https://www.jianshu.com/p/ab22238a9ab1