设置CPU使用率的工具-cpuocup

cpuocup是一款设置CPU使用率的工具, 可以设置若干线程的CPU使用率, 可以将线程绑定到对应的CPU核心, 也可以设置线程的执行优先级(需要sudo权限). 在一些需要低效CPU的测试场合, 该工具可能帮得上忙.

已经很久没有更新. 在二月份的时候经历了裁员, 三月份开始找工作, 入职了一家新公司, 每个工作日都需要花费3个小时的通勤时间, 有点累… 停更了很久, 其实在这期间也写了一些内容, 但是因为工作的原因没有及时更新和润色, 并且有些玩意儿做的不是很好, 还需要改进, 所以并没有贴出来. 现在已经工作一个多月, 对工作和通勤都适应起来了, 所以, 博客也要继续搞起来. 期望可以保持原来的更新速度, 以及学习和探索一些更有意思的问题.

没有什么十分明确的契机, 可能因为最近用上了Copilot, 工作上又做了一些性能测试相关的内容, 便想到了做这样一个和性能有些相关的东西.

详细用法可以见仓库的README, 简单用法如下:

1
2
3
4
cpuocup 0.5
#set thread 0 to 50% usage
cpuocup 1,0.5
#set thread 1 to 50% usage, and bind to cpu 1

设计上, cpuocup最多可以启动cpu核心数个线程, 以确保可以让每个核心分配一个工作线程.

如以上cpuocup 0.5指令, 会启动一个线程, 将线程的cpu占用率设置为50%附近, 但是不会绑定到某个核心, 因此实际情况上, 可能会又多个核心分担这个线程的资源消耗, 从而在使用top指令观察的时候, 可能看不到某个核心占用为50%的情况.

又如cpuocup 1,0.5 指令, 会启动一个线程, 将线程的cpu占用率设置为50%附近, 并且将该线程绑定到cpu 1上, 因此在使用top指令观察时, 是可以观察到cpu 1的占用率接近50%的. 当然如果有其他线程也在消耗这个核心, 那么其占用率可能是超过50%的.

体验Copilot

关于cpuocup这个工具, 其实我的核心是体验Copilot. 通过Copilot, 可以减少在浏览器和编辑器之间的切换次数. 还在学校的时候, 我试用过tabnine, tabnine更像是上下文提示(现在不知道是什么程度了), Copilot远不止如此.

比如, 当需要写cpu绑核相关代码的时候, 我可能不知道对应API是什么, 以及如何使用, 那么我可以:

1
// bind thread to cpu

只需要相关的注释, Copilot就有可能帮助我完成相关的代码. 比如, 它可能为我生成:

1
2
3
4
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(arg.cpu_id, &set);
pthread_setaffinity_np(threads.back().native_handle(), sizeof(cpu_set_t), &set);

在这之后, 我先验证设置是否成功, 也只需要注释:

1
// check if cpu affinity set success

那么Copilot就可以帮助生成验证cpu affinity设置时候成功的代码, 比如:

1
2
3
4
cpu_set_t get;
CPU_ZERO(&get);
pthread_getaffinity_np(threads.back().native_handle(), sizeof(cpu_set_t), &get);
check(CPU_ISSET(arg.cpu_id, &get), "Set cpu affinity failed");

注意到, 它还自己动调用了我在这份文件中写的check接口, 并且帮助生成了错误提示代码.

又比如, 我想为每个command绑定函数的时候, 有这样一段代码:

 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
std::unordered_map<char, cmd_job_t> g_cmd_jobs = {
    {'f', [](std::vector<thread_args>& args, thread_args& arg) {
        for (auto i = 0; i < args.size(); ++i) {
            args[i] = arg;
        }
    }},
    {'F', [](std::vector<thread_args>& args, thread_args& arg) {
        for (auto i = 0; i < args.size(); ++i) {
            args[i] = arg;
            args[i].cpu_id = i;
        }
    }},
    {'r', [](std::vector<thread_args>& args, thread_args& arg) {
        for (auto i = 0; i < args.size(); ++i) {
            if (!args[i].specific) {
                args[i] = arg;
            }
        }
    }},
    {'R', [](std::vector<thread_args>& args, thread_args& arg) {
        for (auto i = 0; i < args.size(); ++i) {
            if (!args[i].specific) {
                args[i] = arg;
                args[i].cpu_id = i;
            }
        }
    }},
};

这是Copilot帮助生成的. 在这之前, 我在parse_args函数内部实现了相关的代码, 在整理代码的时候我想改成map的形式, 因此, 当我输入std::unordered_map这几个字符的时候, Copilot就以及帮助我生成了剩下的代码, 我几乎不需要再去修改, 只需要将原来的代码删除即可.

另外, 在忘记#include <unordered_map>时, 光标移动到include区域, Copilot也自动补全了缺少的部分.

再比如, helper_str也是Copilot生成的, 可以看看它的内容:

 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
std::string g_helper_str =
// unix style helper
"\033[1mNAME\033[0m\n"
"    cpuocup - set cpu userspace usage rate\n"
"\033[1mVERSION\033[0m\n"
"    " + std::string(VERSION_STRING) + "\n"
"\033[1mUSAGE\033[0m\n"
// can set [rate] [cpu_id,rate] [cpu_id,priority,rate] [cmd,rate] [cmd,priority,rate]
"    \033[1mcpuocup\033[0m [\033[4mrate\033[0m] [\033[4mcpu_id\033[0m,\033[4mrate\033[0m] [\033[4mcpu_id\033[0m,\033[4mpriority\033[0m,\033[4mrate\033[0m] [\033[4mcmd\033[0m,\033[4mrate\033[0m] [\033[4mcmd\033[0m,\033[4mpriority\033[0m,\033[4mrate\033[0m] ...\n"
"\033[1mDESCRIPTION\033[0m\n"
"    This program is used to set cpu rate. Max " + std::to_string(g_hardware_threads) + " threads are supported at the device.\n"
"    \033[1mrate\033[0m: thread rate, 0.0 <= rate <= 1.0\n"
"    \033[1mcpu_id\033[0m: cpu id, -1 means thread not bind any cpu, range: [-1, " + std::to_string(g_hardware_threads-1) + "]\n"
"    \033[1mpriority\033[0m: thread priority, range: [0, 99]\n"
// cmd support: f, F, r, R
"    \033[1mcmd\033[0m: f, r, F, R\n"
"        f: set to all threads\n"
"        r: set to all threads which is not specific\n"
"        F: set to all threads, and bind to corresponding cpu\n"
"        R: set to all threads which is not specific, and bind to corresponding cpu\n"
"\033[1mEXAMPLE\033[0m\n"
// give 5 classic examples of each usage, and explain the meaning of each example
"    \033[1mcpuocup\033[0m 0.5 0.9\n"
"        set thread 0 to 50% usage, and thread 1 to 90% usage\n"
"    \033[1mcpuocup\033[0m 1,0.5\n"
"        set thread 1 to 50% usage, and bind to cpu 1\n"
"    \033[1mcpuocup\033[0m 1,20,0.5\n"
"        set thread 1 to 50% usage, and bind to cpu 1, and set thread priority to 20\n"
"    \033[1mcpuocup\033[0m f,0.5\n"
"        set all threads to 50% usage\n"
"    \033[1mcpuocup\033[0m 1,20,0.5, r,40,0.9\n"
"        set thread 1 to 50% usage, and bind to cpu 1, and set thread priority to 20\n"
"        set all threads which is not specific to 90% usage, and set thread priority to 40, and bind to corresponding cpus\n"
"\033[1mAUTHOR\033[0m\n"
"    Written by \033[1mcaibingcheng\033[0m.\n"
"\033[1mREPORTING BUGS\033[0m\n"
"    Report bugs to \033[1mjack_cbc@163.com\033[0m.\n"
"\033[1mCOPYRIGHT\033[0m\n"
"    This is free software: you are free to change and redistribute it.\n"
"    There is NO WARRANTY, to the extent permitted by law.\n"
;

当然, 这已经是迭代了好几个版本之后的内容, 在Copilot描述的cpuocup用法上, 稍微有点不准, 但是需要修改的地方不多. 在生成helper_str的过程中, 我同样只需要填写注释, 比如开头填写:

1
// unix style helper

那么, 它会帮助我生成UNIX风格的helper信息. 在用法描述上, Copilot有缺失项, 那么我同样用注释告知其正确用法:

1
// can set [rate] [cpu_id,rate] [cpu_id,priority,rate] [cmd,rate] [cmd,priority,rate]

小结

以上.

项目中, 有注释的部分基本都是Copilot协助完成的, 这些部分, 我基本只需要输出注释, 有些注释因为在早期开发变动较大, 就删除了. 项目中的一些重复性较大的代码, 也是由Copilot协助完成.

对此, 我最大的感触是, 对于这些独立的小项目, 我只需要关心整体设计, 至于代码如何实现, 接口如何调用我已经不需要关心了, 甚至也不需要浏览器. 不过对于比较大的项目, 目录结构比较复杂, Copilot就显得没有那么"机智"了.

(这篇文章有点水~除去代码时间, 文章内容可能只准备两个小时. 不过博客可算更新了, 也算是又步入正轨了吧, 用输出来促进我的输入! 前段时间还做了心率检测相关的东西, 以及调查了文件buffer指针相关的内容, 期望能尽快有输出. 还有, Copilot体验还是非常好的!)