程序调优中如何稳定地测试程序运行时间

程序调优中如何稳定地测试程序运行时间

计算代码段的运行时间

关于程序运行过程中,有效地测试程序运行时间有下面几个方法。

使用clock()函数进行程序运行时间统计

#include <stdio.h>
#include <time.h>

int main(int argc, char** argv) {
    clock_t start_cnt, end_cnt;
    start_cnt = clock();
    // ...
    end_cnt = clock();
    double t = (end_cnt - start_cnt) / CLOCKS_PER_SEC;
    printf("Use time: %f s. \n", t); // 单位为秒
    return 0;
}

CLOCKS_PER_SEC为每秒的计时周期数,一般CLOCKS_PER_SEC的值为1000000,即以us为单位。

使用rdtsc汇编指令计算实际运行CPU周期数

这种方法虽然精度比较高,但是在多核时代其实也不是特别准了。主要是现代处理器体系结构的以下特点影响造成的:

  1. 乱序执行导致测得不准
  2. 如果进程进行了切换,下一次运行在其它CPU核心上,CPU核心计时器不同步影响特别大

并且它的单位是执行时钟周期数,由于自动调频的存在,将CPU时钟周期数转化为计时单位秒比较困难,但是在体系结构分析中可能比较有用。

具体参考多核时代不宜再用 x86 的 RDTSC 指令测试指令周期和时间

使用clock_gettime()函数

#include <stdio.h>
#include <time.h>

int main(int argc, char** argv) {
    struct timespec start_cnt, end_cnt;
    clock_gettime(CLOCK_MONOTONIC, &start_cnt);
    // ...
    clock_gettime(CLOCK_MONOTONIC, &end_cnt);
    double t = (end_cnt.tv_sec - start_cnt.tv_sec) * 1000.0 + (end_cnt.tv_nsec - start_cnt.tv_nsec) / 1000000.0;
    printf("Use time: %f ms. \n", t);  // 单位为毫秒
    return 0;
}

使用clock_gettime()函数达到的精度为ns,属于非常准的计时了。

使用gettimeofday()函数

#include <stdio.h>
#include <sys/time.h>

int main(int argc, char** argv) {
    struct timeval tv;
    double start_cnt, end_cnt;
    gettimeofday(&tv, NULL);
    start_cnt = tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
    // ...
    gettimeofday(&tv, NULL);
    end_cnt = tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
    double t = end_cnt - start_cnt;
    printf("Use time: %f ms. \n", t);
    return 0;
}

使用gettimeofday()函数的精度为us,属于比较准的计时了。

使用times()函数

times()函数计时精度比较低,计时精度一般是10ms,不推荐。参考linux times函数分析

总结

C/C++代码里面有各种各样的计时函数。其中需要注意一点的是,如果程序运行过程中涉及了上下文切换,计算的时间还准吗?这里除了clock()和times()函数会去掉本进程休眠的时间外,其它的都不能去掉进程休眠的影响。但是考虑到一般对于计算密集的程序,如果不涉及到IO或者直接使用sleep进行休眠,基本不会进行上下文切换,对于程序计时的影响不大。

对于程序计时,笔者对他们封装了一下,具体见我的码云。关于获取程序运行时钟周期数,本来打算将其扩展到ARM的,后期再说吧!

避免CPU核心自调整频率的坑

最近在测试程序运行的时候,程序运行时间对比老是跳来跳去。
经过多方排查,发现是CPU的频率动态调节的坑。

CPU频率动态调节对运行时间短的程序影响特别大。特别是2秒以内的程序。
如果是程序运行超过2秒的程序,由于计算密集造成系统提升频率的时间大概在半秒到1秒之间,所以影响不是特别大。
至于是如何发现的,是在perf工具进行性能分析的时候发现的。

那么如何避免这个坑呢?

串行程序

如果是串行运行程序,推荐方法是首先把一个特定的CPU的模式改为peformance模式(即使用最大频率运行模式),然后设置程序在这个特定的CPU上面运行。程序运行完成之后再还原为系统默认的conservative模式。
这样做的好处有以下几点:

  1. 放到一个特定的编号的CPU上面,其它进程对其抢占的几率比较小,可能几乎就没有进程的上下文切换开销,避免了进程上下文切换的干扰。
  2. 在一个特定的CPU上面运行便于监控。

具体做法如下(以使用21号CPU为例):

  1. 将在程序代码里面加入如下的设置cpu的函数,并且在主函数运行开始便指定cpu:
#define _GNU_SOURCE   //这行要加
#include <sched.h>
void set_cpu(int cpu) {
    cpu_set_t cpu_mask;
    CPU_ZERO(&cpu_mask);
    CPU_SET(cpu, &cpu_mask);
    sched_setaffinity(0, sizeof(cpu_mask), &cpu_mask);
}
int main(int argc, char** argv) {
    set_cpu(21); //设置当前进程运行在21号CPU
    // ...
    return 0;
}
  1. 设置21号CPU的模式为performance:
su root # 需要root权限
cpupower -c 21 frequency-set -g performance
exit    # 设置完之后一定要退出root
  1. 运行加入set_cpu(21)的代码。
  2. 测试完成后,设置21号CPU为默认的conservative模式:
su root # 需要root权限
cpupower -c 21 frequency-set -g conservative
exit    # 设置完之后一定要退出root

**PS:**设定进程运行在特定的CPU上面,也可以使用taskset命令。

并行程序

推荐做法是把所有cpu设为performance模式,之后测试程序。测完之后记得将CPU改为默认模式。

su root # 需要root权限
cpupower frequency-set -g performance # 设置所有CPU为performance模式
exit    # 设置完之后一定要退出root
# 运行你的测试
su root # 需要root权限
cpupower frequency-set -g conservative # 运行完成后调为默认模式
exit    # 设置完之后一定要退出root

关于为什么最后还要调回默认模式

对于服务器来说,第一个因素肯定是耗电呀!可能有人说服务器在机房,不用你管耗不耗电。但是就是动一动手指的事,就算为地球环保做贡献吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值