我们该如何衡量性能?

254 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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));
    }
}

但是这位同学得到了如下的结果,这是为什么呢?

image-20230207204700276

我们将在文章的最后揭晓原因。

停顿系统

很多人都知道,在进行实验的时候,我们应该尽可能的控制变量。但是在计算机中, 因为其结构复杂性和运算快捷性,一个很细微的操作都可能会影响结果。就像刚才的谜题一样,也许你已经考虑过很多天马行空的原因。

我们给出一些可能的变量:

image-20230207205333581

针对这些变量,我们需要进行控制,例如做如下的操作:

  • 关闭其他的计算任务
  • 关闭定时和后台任务
  • 断开网络
  • 停止移动鼠标(虽然看起来很离谱,但也有一定道理)
  • 不要将程序运行在core 0上,因为会处理很多中断
  • 关闭超线程
  • 关闭DVFS
  • 关闭Turbo Boost

甚至,在写代码的时候,可能因为代码的长度就会产生性能的影响。

衡量性能

我们可以通过多种方式进行性能的衡量。

可以通过/usr/bin/time衡量程序的运行时间;

也可以通过在程序内部打点的方式来衡量某些代码的性能;

也可以通过gdb或者gprof等工具中断程序,来查看程序到底发生了什么;

也可以通过perf这样的工具来获取程序在硬件上到底发生了什么;

最后,我们可以用cachegrind这样的工具来模拟程序的运行;

性能建模

首先我们需要介绍性能工程的大致流程:

1、衡量程序A的性能;

2、优化A到A';

3、衡量A‘的性能;

4、如果A’的性能更好,就保留,否则跳转到第二步继续优化;

我们需要知道通过什么数学方法来表征程序的性能,这取决于我们程序所处的场景。在一个噪声比较多的场景中,我们运行程序100次,我们该用什么来表征其原始的性能呢?也许你会说算数平均数,但是在这个场景下我们使用最小值最好,因为在这个背景下,噪声对其产生的影响最少。此外,我们给出一些场景和需要关注的指标:

image-20230207210539173

那么,我们怎么去比较A和A’的性能呢?我们假设有A和B两个程序,其运算情况如下所示:

image-20230207210617804

基于这个结果,我们可以得出结论,A的性能是B的三倍吗?

显然是不可以的,因为如果我们用同样的方式计算B/A的话,就会有如下的结果:

image-20230207210814408

在B/A的情况下,B的性能竟然比A还要好,这是两个完全不同的结论。所以我们应该使用几何平均数,因为其对于相反的情况恰好是倒数:

image-20230207210905220

我们如何得出一个模型呢?假设我们想知道指令运行的时间和cache miss的时间,针对如下的数据:

image-20230207211105588

我们可以将runtime T构造为:

T=aI+bCT = a * I + b * C

其中I是指令数,C是cache miss情况。基于这样的数据,我们可以做计算和后续的验证。

谜题的解

虽然说出来很不可思议,但是这是因为电脑的散热造成的。由于温度变高,芯片会降频使得功率降低,这就使得程序运算的变慢;而程序变慢以后,又会提高频率,程序的运算时间就又减少了。