开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情
前面我们都在介绍性能,那么我们怎么衡量性能呢?
谜题
有位同学想要测量他写的排序算法的性能,于是他用了如下的代码进行测试:
#include<stdio.h>
#include<time.h>
void my_sort(double *A, int n);
void fill(double *A, int n);
struct timespec start,end;
int main(){
int max = 4 * 1000 * 1000;
int min = 1;
int step = 20 * 1000;
double A[max];
for(int n = min; n < max; n+=step){
fill(A,n); //填充数据
clock_gettime(CLOCK_MONOTONIC, &start);
my_sort(A,n); //排序
clock_gettime(CLOCK_MONOTONIC, &end);
printf("size %d, time %f\n", n, (end-start));
}
}
但是这位同学得到了如下的结果,这是为什么呢?
我们将在文章的最后揭晓原因。
停顿系统
很多人都知道,在进行实验的时候,我们应该尽可能的控制变量。但是在计算机中, 因为其结构复杂性和运算快捷性,一个很细微的操作都可能会影响结果。就像刚才的谜题一样,也许你已经考虑过很多天马行空的原因。
我们给出一些可能的变量:
针对这些变量,我们需要进行控制,例如做如下的操作:
- 关闭其他的计算任务
- 关闭定时和后台任务
- 断开网络
- 停止移动鼠标(虽然看起来很离谱,但也有一定道理)
- 不要将程序运行在core 0上,因为会处理很多中断
- 关闭超线程
- 关闭DVFS
- 关闭Turbo Boost
甚至,在写代码的时候,可能因为代码的长度就会产生性能的影响。
衡量性能
我们可以通过多种方式进行性能的衡量。
可以通过/usr/bin/time衡量程序的运行时间;
也可以通过在程序内部打点的方式来衡量某些代码的性能;
也可以通过gdb或者gprof等工具中断程序,来查看程序到底发生了什么;
也可以通过perf这样的工具来获取程序在硬件上到底发生了什么;
最后,我们可以用cachegrind这样的工具来模拟程序的运行;
性能建模
首先我们需要介绍性能工程的大致流程:
1、衡量程序A的性能;
2、优化A到A';
3、衡量A‘的性能;
4、如果A’的性能更好,就保留,否则跳转到第二步继续优化;
我们需要知道通过什么数学方法来表征程序的性能,这取决于我们程序所处的场景。在一个噪声比较多的场景中,我们运行程序100次,我们该用什么来表征其原始的性能呢?也许你会说算数平均数,但是在这个场景下我们使用最小值最好,因为在这个背景下,噪声对其产生的影响最少。此外,我们给出一些场景和需要关注的指标:
那么,我们怎么去比较A和A’的性能呢?我们假设有A和B两个程序,其运算情况如下所示:
基于这个结果,我们可以得出结论,A的性能是B的三倍吗?
显然是不可以的,因为如果我们用同样的方式计算B/A的话,就会有如下的结果:
在B/A的情况下,B的性能竟然比A还要好,这是两个完全不同的结论。所以我们应该使用几何平均数,因为其对于相反的情况恰好是倒数:
我们如何得出一个模型呢?假设我们想知道指令运行的时间和cache miss的时间,针对如下的数据:
我们可以将runtime T构造为:
其中I是指令数,C是cache miss情况。基于这样的数据,我们可以做计算和后续的验证。
谜题的解
虽然说出来很不可思议,但是这是因为电脑的散热造成的。由于温度变高,芯片会降频使得功率降低,这就使得程序运算的变慢;而程序变慢以后,又会提高频率,程序的运算时间就又减少了。