• C++高性能优化编程之如何测量性能(一)


    系列文章目录

    点击进入系列文章目录

    C++高性能优化编程系列
    深入理解设计原则系列
    深入理解设计模式系列
    高级C++并发线程编程

    1、为什么要进行性能优化

    不好的编程习惯,不重视程序性能测量分析让代码跑的更快,会导致浪费大量的CPU周期、程序响应时间慢以及卡顿,用户满意度下降,进而浪费大量的时间返工去重构本应该一开始就更加高效的代码。性能优化与编码对开发过程而言有着同等的重要性。对于用户而言,性能糟糕的让人无法接受,这个问题的严重程度不亚于出现bug和未实现的特性。

    性能优化的目的:是通过改善正确的程序行为使满足客户对处理速度、吞吐量、内存占用以及能耗等各种指标的需求。

    2、性能测试的指标是什么

    • 启动时间
      通过测量程序进入main()函数到进入主循环的时间来得到启动时间。
    • 退出时间
      通过测量接收退出命令到程序退出main()的时间来得到退出时间。(退出时间也包含停止所有的线程和所依赖的进程所需的时间)
    • 响应时间
      执行一个命令的平均时间或最长时间。
    • 吞吐量
      在一定的测试负载下,系统在每个时间单位内所执行的操作的平均数。更适合批量处理程序。

    3、测量性能及改善变更记录文档

    优秀的开发人员都会系统的完善他们的开发任务:

    • 做出的预测都是可测试的,并且记录下预测;
    • 保留代码的变更记录;
    • 可以使用最优秀的工具进行测量;
    • 保留结果的详细笔记。

    4、性能测量分析工具的使用

    4.1、使用Linux Perf性能分析器进行性能测量分析

    介绍一下第一个性能分析器工具Linux Perf性能分析器。这是Linux上流行的性能分析器之一,因为它随大多数发行版一起安装。此性能分析器使用硬件性能计数器和基于时间间隔的采样结合起来,记录正在运行程序的性能分析。它将记录每个样本程序计数器的位置(要执行的指令的地址)和用户正在监控的性能计数器的值。运行该性能分析器后,即可对数据进行分析;样本最多的函数和代码占据大部分执行时间。

    值得注意的是,性能分析器在运行时收集指令地址,要将其转换为原始源代码中的行号,必须使用调试信息编译程序。使用两种编译模式组合:optimized(优化)和debug non-optimized(调试非优化) - 调试和优化都被启用。
    使用这种选项组合的原因是我们需要分析将在生产环境中运行的相同代码,否则数据大多没有意义。

    计数器
    cycles(周期):cycles时间是CPU频率的倒数。
    instructions(指令):可测量已执行的处理器指令数。
    branches(分支):是条件指令,每个if语句和每个带条件的for循环都将至少生成这些指令之一。
    branch-misses(分支未命中):
    cache-references(缓存引用):统计的是CPU从内存中获取某些东西所需的次数。大多数情况下,这里说的“某些东西”是一段数据,例如字符串中的一个字符。根据处理器和内存的状态,这种获取可能非常快,也可能非常慢。对于慢的,计入cache- misses。
    cache-misses(缓存未命中):代码中低效的内存访问,访问速度非常慢。

    4.2、使用Google GperfTools性能分析器进行性能测量分析

    Gperftools是google开发的一款非常实用的工具集,主要包括:性能优异的malloc free内存分配器tcmalloc;基于tcmalloc的堆内存检测和内存泄漏分析工具heap-profiler,heap-checker;基于tcmalloc实现的程序CPU性能监测工具cpu-profiler.

    Goole CPU性能分析器可以使用硬件性能计数器,还需要代码的链接时检测(但不需要编译时检测)。要准备用于性能分析的代码,必须将其与性能分析器的profiler库链接。

    与Linux Perf不同的是,Google CPU性能分析器不需要任何特殊的工具来调用程序,必要的代码已连接到可执行文件中。

    已检测的可执行文件不会自动开始性能分析。因此,我们必须通过将环境变量CPU PROFILE设置为想要存储结果的文件的文件名来激活性能分析。

    5、性能测量微基准测试分析

    5.1、使用计时时间测量函数执行时长微基准测试分析

    用一个Stopwatch类来测量程序中部分代码的执行时间并分析代码,以下是我编写的一个计时类的代码:

    /*
     * @Author: Allen
     * @Date: 2023/5/26
     * @Description: 函数测量计时
     */
    
    #ifndef STUDYTEST_STOPWATCH_H
    #define STUDYTEST_STOPWATCH_H
    
    #include <iostream>
    #include <chrono>
    
    using namespace std::chrono;
    class Stopwatch {
    public:
        /*
         * 初始化构造函数
         * _activity:传递功能名称用于log打印
         * */
        explicit Stopwatch(char const* _activity = "Stopwatch"){
            activity_ = _activity;
            start_ = steady_clock::time_point::min();
        }
    
        ~Stopwatch()= default;
    
        /*
         * 开始计时
         * */
        void Start(){
            std::cout << "[ " << activity_ << " Start ]" << std::endl;
            start_ = steady_clock::now();
        }
    
        /*
         * 判断是否已开始计时
         * */
        bool IsStarted() const{
            return start_.time_since_epoch() != steady_clock::duration(0);
        }
    
        /*
         * 清除计时
         * */
        void Clear(){
            start_ = steady_clock::time_point::min();
        }
    
        /*
         * 获取间隔时间
         * */
        unsigned long GetMS(){
            if (IsStarted()){
                steady_clock::duration diff;
                diff = steady_clock::now() - start_;
                return (unsigned)(duration_cast<milliseconds>(diff).count());
            }
            return 0;
        }
    
        /*
         * 停止计时
         * */
        void Stop(){
            unsigned long interval = GetMS();
            std::cout << "[ " << activity_ << " Stop  ] Use Time : " << interval << "ms" << std::endl;
            Clear();
        }
    
    private:
        steady_clock::time_point start_;
        char const* activity_;
    };
    
    #endif //STUDYTEST_STOPWATCH_H
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    引入Stopwatch类使用:

    {
       	Stopwatch sw("MyString");
        sw.Start();
        { 
                // 运行的热点函数代码
                // ...
        }
        sw.Stop();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    运行结果打印:

    [ MyString Start ]
    [ MyString Stop  ] Use Time : 20ms
    
    • 1
    • 2

    5.2、使用Google Benchmark库微基准测试分析

    google benchmark主要是对c++中的函数进行基准功能测试。

    点击进入系列文章目录

  • 相关阅读:
    Python文件的操作处理,一看就会
    map和set
    linux基本操作
    人工电销时代,你来不来 ,ai智能电话机器人
    【Java基础】位运算符与原码,补码,反码
    校招中的“熟悉linux操作系统”一般是指达到什么程度?
    mac nvm的使用
    [Framework] Android Handler 工作原理
    洗衣行业在线预约小程序系统源码搭建 支持直播功能+在线预约下单+上门取件
    看到我们的IDE插件代码被友商复制粘贴了,所以我们做了一个愉快的决定
  • 原文地址:https://blog.csdn.net/weixin_30197685/article/details/130905843