设置CPU使用率的工具-cpuocup

摘要: 作者介绍了cpuocup工具,用于设置CPU使用率、绑定线程到特定核心、调整优先级,适用于性能测试场景。通过示例说明其用法和设计理念,强调最多支持与核心数相同的线程数。作者还分享了对Copilot的体验,指出其在生成代码、补全缺失部分、简化重复性任务方面的帮助,尤其在小型项目中表现出色。整体上,作者强调利用AI辅助提升开发效率,减少重复劳动,专注于设计思路。 (评价: B)



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体验还是非常好的!)