Linux下实现CPU使用率正弦曲线

 编程之美第一道题目就是如何让CPU使用率曲线成为一条正弦曲线,本文在Linux下实现这个效果。

程序运行时间

一个进程的运行时间大致分为user time,kernel time和waiting time

三个时间加起来就是进程从开始到结束用的时间。
user time是进程在用户空间执行的时间
kernel time是进程在内核空间执行的时间
waiting time是进程等待IO或者其他事件所用的时间
例如
  
  
  
  
  1. int main() 
  2.     int i; 
  3.     for(i = 0; i < 100000000; i ++) //用户空间执行 
  4.         getpid(); //系统调用,内核空间执行 
  5.     //scanf和printf是C标准库里的,还要调用Linux的系统调用read和write 
  6.     scanf("%d\n",&i);  
  7.     printf("%d\n",i);  
  8.     return 0; 
使用time命令可以显示进程运行的时间,real,user,sys。大部分时间都用来等IO了,因为人输入的速度远小于计算机的速度。

CPU的使用率是指所有进程的user time和kernel time之和除以real time。而一般情况下,user time远大于kernel time。
如果一个系统中CPU使用率几乎为0,那么我们可以写个程序控制CPU的使用率。
 
  
  
  
  
  1. while(1) 
  2.      for(i = 0; i < 100000; i ++);  //CPU忙,占用的时间是user time 
  3.      usleep(10000); //CPU闲,属于waiting time 
 
让CPU一直维持在50%
 
让进程50%的时间做循环,%50的时间sleep就行了
 
   
   
   
   
  1. int main() 
  2. int i; 
  3. while(1) 
  4. for(i = 0; i < n; i++); 
  5. usleep(m); 
  6. return 0; 
关键是如何确定n和m的值。
我们先把m定死,进程睡眠60ms,linux调度时的时间片好像是15ms,但是usleep精度较低,所以我们让它睡的时间长点。
然后再确定n,先随便给n一个值,看看汇编代码里使用了几条指令:
 
   
   
   
   
  1. .L2: 
  2.         movl    $0, -8(%ebp) 
  3.         jmp     .L3 
  4. .L4: 
  5.         addl    $1, -8(%ebp) 
  6. .L3: 
  7.         cmpl    $3999999, -8(%ebp) 
  8.         jle     .L4 
  9.         movl    $60000, (%esp) 
  10.         call    usleep 
  11.         jmp     .L2 
红色的是内层循环,有3条指令。CPU主频是2.27GHz,如果按一个时钟周期执行一条的话,那么执行三条指令的时间为2.27x10 -9x3 = 6.9x10 -9s,要执行60ms,循环数n的大小为
60x10 -3 / 6.9x10 -9 = 8.69 x 10 6 
试一试这个数,8690000
CPU使用率稳定在38%左右,看来我们低估了CPU的能力。我们可以根据38%这个数计算出n的大小了
CPU时间/(CPU时间+60ms)= 38%,因此86900使用的cpu时间为 36ms,8690000:36=n:60,求得n等于1448333,这次CPU使用率很精确的停留在50%左右。

 


我刚才只注意进程cpu使用的CPU资源了,忘了还有其它进程的,其他进程目前大约占了%3,稍微调整一下n,n=13200000,得到了50%的曲线,当然会有点波动。

 

注:上面说一个时钟周期执行一条指令是不合适的,各种指令的执行时间不同。计算机里的周期主要有时钟周期,机器周期,指令周期。一条指令的周期称为指令周期,由几个机器周期做成,而一个机器组成由几个时钟周期组成。上面的三条指令都需要取内存,因此时间长。如果把循环变量放在寄存器里,那么用的时间要小的多:

将内存数-8(%ebp)改为寄存器数%ebx

   
   
   
   
  1. .L2: 
  2.         movl    $0, %ebx 
  3.         jmp     .L3 
  4. .L4: 
  5.         addl    $1, %ebx 
  6. .L3: 
  7.         cmpl    $13199999, %ebx 
  8.         jle     .L4  
  9.         movl    $60000, (%esp)  
  10.         call    usleep  
  11.         jmp     .L2 

CPU利用率由50%降到了20%。性能提高了60%。可见即使C语言写的程序,也有很大的优化空间,其实编译时加上-O也能达到这个效果。使用gcc的-O选项,也是20%,查看汇编代码,发现就是把-8(%ebp)用%eax代替了,和我直接修改的是一样。-O默认使用2级优化。其实gcc还可以进一步优化,将循环展开,不过这里的n太大,展开的话代码太多,优化到使用寄存器替代内存数就挺好了。
Java写的程序,性能是C程序的几十分之一!
 
让CPU使用率为正弦曲线
上面的程序n值在不同的机器上是不一样的。我们换个思想实现。还是把睡眠时间定死,60ms。
     
     
     
     
  1. //伪代码 
  2. int main() 
  3.     int start_time, current_time; 
  4.     while(1) 
  5.     { 
  6.         start_time = GetCurrentTime(); 
  7.         current_time = start_time; 
  8.         while(current_time - start_time < 60) 
  9.             current_time = GetCurrentTime(); 
  10.         sleep(60); 
  11.     } 
关键是如何获取当前时间。Windows下可以使用GetTickCount(),Linux下可以使用gettimeofday()。
 
   
   
   
   
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. int main() 
  4.     struct timeval tv; 
  5.     long long start_time,end_time; 
  6.     while(1) 
  7.     { 
  8.         gettimeofday(&tv,NULL); 
  9.         start_time = tv.tv_sec*1000000 + tv.tv_usec; 
  10.         end_time = start_time; 
  11.      
  12.         while((end_time - start_time) < 60000) 
  13.         { 
  14.             gettimeofday(&tv,NULL); 
  15.             end_time = tv.tv_sec*1000000 + tv.tv_usec; 
  16.         } 
  17.         usleep(60000); 
  18.     } 
  19.     return 0; 

 

 

现在我们用这种方法实现CPU使用率的正弦曲线。

首先要确定这个曲线的函数。这个函数的最大值是1,最小值是0,因此肯定是0.5(sin(tx) + 1)。

怎么确定t呢?

我们可以认为,曲线的更新周期应该大于100ms,我们以100ms为单位,把100ms的平均使用率作为这100ms末的使用率。

 

假如我们希望10s能出一个完整的波形,100ms计算一次,那就需要计算100次。这样我们要准备两个大小为100的数组,分别保存循环时间和睡眠时间。
而且满足,第一个数组循环时间为0,睡眠时间为100,第50个数组循环时间为100,睡眠时间为0,第100个数组循环时间为0,睡眠时间为100.
这样我们就确定了t了。100个数组下标为横坐标,那么周期是100,t=2x3.14/100 = 0.0628.
代码如下:
   
   
   
   
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <math.h> 
  4. int main() 
  5.     struct timeval tv; 
  6.     long long start_time,end_time; 
  7.     long long busy_time[100]; 
  8.     long long idle_time[100]; 
  9.     int i; 
  10.     for(i = 0; i < 100; i++) 
  11.     { 
  12.         busy_time[i] = 100000 * 0.5 * (sin(i*0.0628) + 1); 
  13.         idle_time[i] = 100000 - busy_time[i]; 
  14.     } 
  15.     i = 0; 
  16.     while(1) 
  17.     { 
  18.         gettimeofday(&tv,NULL); 
  19.         start_time = tv.tv_sec*1000000 + tv.tv_usec; 
  20.         end_time = start_time; 
  21.      
  22.         while((end_time - start_time) < busy_time[i]) 
  23.         { 
  24.             gettimeofday(&tv,NULL); 
  25.             end_time = tv.tv_sec*1000000 + tv.tv_usec; 
  26.         } 
  27.         usleep(idle_time[i]); 
  28.         i = (i+1)%100;   
  29.     } 
  30.     return 0; 

 

效果还不错:

 
上面的程序都是在单CPU下完成的。如果在多CPU下,可以指定此进程只运行在某个CPU上,不再赘述了。
 

 

 

本文出自 “牛哥的博客” 博客,转载请与作者联系!

你可能感兴趣的:(linux,编程之美,CPU使用率,gettimeofday,正弦曲线)