本题来自《编程之美》
- 操作系统:Mac OS X 10.15.3
- 硬件信息3.1GHz i5四核处理器
- 代码使用C语言
问题描述
1. CPU的占用率固定在50%,是一条直线;
2. CPU的具体占用率由命令行参数决定(参数范围1~100);
3. CPU占用率的状态是一个正弦曲线。
基础知识
MacOS系统下,「活动监视器」可以查看进程、处理器核数,也可以在「终端」输入top查看任务进程及其详细信息。
Mac OS X活动监视器
CPU占用率 = CPU执行应用程序的时间:刷新周期总时间。
某个进程的CPU使用率就是这个进程在一段时间内占用的CPU时间占总的CPU时间的百分比。比如某个开启多线程的进程1s内占用了CPU0 0.6s, CPU1 0.9s, 那么它的占用率是150%。
时间片(timeslice)又称为“量子(quantum)”或” 处理器片(processor slice)”是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间。
问题 1. 占用率固定为50%
无限循环情况下:
int main(){
while (1){
;
}
}
书中的例子应该是单核处理器,一个无限循环可以让CPU占用率几乎达到100%;但是实际操作后,我的电脑占用率总体是一条25%-30%的直线——这是因为处理器是多核的。
可以通过多线程编程来提高CPU占用率。
多线程编程(Multithreading Programming)
Mac OS是POSIX系统,用pthread创建两个线程就可以让CPU占用率达到50%
简单代码如下:(更多多线程函数介绍详见 (C语言多线程编程-线程的基本函数_c/c++_shuaixio的博客))
#include <pthread.h>
#include <stdio.h>
void
dead_loop(void)
{
while (1){
;
}
}
int
main(void)
{
pthread_t thread[2];
for (int i=0;i<2;i++) pthread_create(&thread[i], NULL, dead_loop, NULL); //创建两个线程
pthread_join(thread[0], NULL); //等待第一个线程结束(永远也不会结束)
printf("Program Ends.");
}
之后编译程序
$ gcc -lpthread program_name.c
结果达成要求——CPU占用率大约为50%,直线的波动是截图和其他系统活动造成的干扰。
问题2. 自定义CPU占用率
OS X 不支持GetTickCount() , Sleep(), 解决方法如下:
用time.h头文件内的函数clock()得到当前CPU clock ticks的次数作差后除以CLOCKS_PER_SEC 就可以得到CPU时间:
while ((clock()-start)/CLOCKS_PER_SEC*1000<busy_time){
/* code */
}
另外要注意Sleep()的单位是ms,sleep()的单位是s;前者仍然是Windows特有函数,在Mac上不能使用。我在Stack Overflow上发现了一段sleep_ms()函数代码,适用于Windows、老版POSIX和新版POSIX(新版不再支持usleep()函数):
#ifdef WIN32
#include <windows.h>
#elif _POSIX_C_SOURCE >= 199309L
#include <time.h> // for nanosleep
#else
#include <unistd.h> // for usleep
#endif
void sleep_ms(int milliseconds) // cross-platform sleep function
{
#ifdef WIN32
Sleep(milliseconds);
#elif _POSIX_C_SOURCE >= 199309L
struct timespec ts;
ts.tv_sec = milliseconds / 1000;
ts.tv_nsec = (milliseconds % 1000) * 1000000;
nanosleep(&ts, NULL);
#else
usleep(milliseconds * 1000);
#endif
}
题目代码:
#include<stdio.h>
#include<pthread.h>
#include<time.h>
int busy_time, idle_time; //定义全局变量
void dead_loop(void);
void sleep_ms(long milliseconds);
int main(){
float percent;
printf("Enter the target percentage (in float, e.g. 0.7): ");
scanf("%f",&percent); // 读入目标CPU占用率
/* 确定时间参数 */
int total_time = 500; //经过大致试验得出的比较合适的值
busy_time = (int)total_time*percent;
idle_time = total_time-busy_time;
/*创建多线程*/
pthread_t thread[3];
for (int i=0;i<3;i++) pthread_create(&thread[i],NULL,dead_loop,NULL);
dead_loop(); //程序一共有4个线程
pthread_join(thread[0], NULL);
return 1;
}
void sleep_ms(long milliseconds){
struct timespec ts;
ts.tv_sec = milliseconds / 1000;
ts.tv_nsec = (milliseconds % 1000) * 1000000;
nanosleep(&ts, NULL);
}
void dead_loop(void){
static __thread clock_t start; // 创建线程内记录CPU时间的局部变量
while (1){
start = clock();
while ((clock()-start)/CLOCKS_PER_SEC*1000<busy_time){
;
}
sleep_ms(idle_time);
}
}
结果
0.5以上的参数得出的结果还是比较准确的,但是0.5以下的参数就不那么准确了(因为我是新手+比较菜,并且时间有限,目前无法解决这个问题)——分析出原因是
- 程序频繁sleep、awake所以系统内核调度程序会产生比较大的干扰。
- 自定义了一些函数,比如sleep_ms()。
问题3. CPU占用率的状态是一个正弦曲线。
我选择的正弦曲线 y=0.25sin(x)+0.75
大概思路是对一整个周期的正弦曲线采样(代码示例里采样25个点),计算出对应的busy_time,idle_time。
#include<stdio.h>
#include<pthread.h>
#include<time.h>
#include<math.h>
#define GAP 25 //正弦曲线每周期样本点的个数
int busy_time[GAP], idle_time[GAP]; //定义全局变量
void dead_loop(void);
void sleep_ms(long milliseconds);
int main(){
int i;
/* 确定时间参数 */
int total_time = 500;
float percent[GAP];
for (i=0;i<GAP;i++){
percent[i] = 0.25*sin(0.5*i)+0.75;
busy_time[i] = total_time*percent[i];
idle_time[i] = busy_time[i]-percent[i];
}
/*创建多线程*/
pthread_t thread[3];
for (i=0;i<3;i++) pthread_create(&thread[i],NULL,dead_loop,NULL);
dead_loop(); //程序一共有4个线程
pthread_join(thread[0], NULL);
return 1;
}
void sleep_ms(long milliseconds){
struct timespec ts;
ts.tv_sec = milliseconds / 1000;
ts.tv_nsec = (milliseconds % 1000) * 1000000;
nanosleep(&ts, NULL);
}
void dead_loop(void){
static __thread clock_t start; // 创建线程内记录CPU时间的局部变量
while (1){
for (int j=0;j<GAP;j++){
for (int k=0;k<10;k++){ //让正弦曲线的周期变长
start = clock();
while ((clock()-start)/CLOCKS_PER_SEC*1000<busy_time[j]){
;
}
sleep_ms(idle_time[j]);
}
}
}
}
结果:总体来说是平滑的sin曲线(User曲线的波动仍然是因为其他应用的干扰)
参考资料
理论基础
时间片-乌龟运维-51CTO博客
CPU使用率原理及计算方式 - gatsby123
多线程编程相关
Multithreading in C
Basics of multithreading in C
linux编程 - C/C++每线程(thread-local)变量的使用 (参考了第一小节)
C library function - clock()
Sleep()函数在Mac上的替代