C++打点计时器工具

本文介绍了为方便程序性能分析而开发的打点计时器工具,该工具基于C++11的chrono库,提供简洁的API进行时间统计和输出。支持设置输出单位、实时输出和查询耗时最大/小的打点等功能。示例代码展示了如何使用该工具进行打点和输出耗时信息。
摘要由CSDN通过智能技术生成

一、工具开发背景及作用

背景: 分析一个程序性能时,需要统计程序不同模块或函数消耗的时间。当采用标准库的时间函数来统计时,需要自己掌握时间函数用法、时间的求差及时间单位转换,且显示不直观
作用: 因此自己开发了打点计时器工具,使用简单,时间统计直观。其提供了如下功能:

  1. 打点时,可输入打点的描述信息
  2. 提供查询每次打点或者整个打点过程的耗时与描述信息
  3. 提供设置输出单位方法(s、ms),默认为:ms
  4. 提供设置打点信息实时输出或者用户自定义时间输出方法
  5. 提供查询耗时最大/小的打点序号

二、基础知识

2.1、C++11 计时标准库 <chrono>用法

该库提供与时间相关的功能,主要提供了:duration、time_point与system_clock三种功能。

2.1.1、Durations:

作用: 时间段,用于表示时间长度。用时间周期(精度)Period和周期个数Rep作为模板参数定义

template <class Rep, class Period = ratio<1>>
class duration;
// 例:
std::chrono::duration<std::uint32_t, std::milli> milliseconds(100); // 表示100ms

2.1.2、time_point:

作用: 时间点,表示自 Clock 的epoch开始以Duration类型表示的到某一时刻的时间间隔值。

template <class Clock, class Duration = typename Clock::duration>
class time_point;

参考资料: C++11 time_point

2.1.3、clocks:

作用: 系统时钟,表示当前的真实时间,有system_clock、steady_clock、high_resolution_clock三种:

  • system_clock:从系统获取的时钟,系统时间也是人为配置的或者从互联网校准的,因此可修改
  • steady_clock:不能被修改的时钟,常用于打点计时器
  • high_resolution_clock:高精度时钟

参考资料: C++ chrono 库中的 steady_clock 和 system_clock

三、代码实现

3.1、ticker.h

// ticker.h
/**
 * @brief 打点计时器工具
 * 功能列表:
    1)打点时,可输入打点的描述信息
    2)提供查询每次打点或者整个打点过程的耗时与描述信息
    3)提供设置输出单位方法(s、ms),默认为:ms
    4)提供设置打点信息实时输出或者用户自定义时间输出方法
    5)提供查询耗时最大/小的打点序号
 * @author zhangzhiyu
 * @date 2023-05-30
 */

#ifndef INCLUDE_TOOLS_TICKER_H_
#define INCLUDE_TOOLS_TICKER_H_

#include <chrono>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

class Ticker {
public:
    static const std::uint16_t MAX_TICK_TIMES = 500; // 最大的打点次数
    using TimeStampInfo_t = std::pair<std::chrono::steady_clock::time_point, std::string>;

    enum TickTimeUnit {
        MS, // milliseconds. 默认。
        S,  // seconds.
    };

public:
    Ticker(std::string info = "Default ticker name");
    ~Ticker();

    /**
     * @brief 设置输出单位(s、ms)
     */
    void setTickTimeUnit(TickTimeUnit unit);

    /**
     * @brief 设置实时输出
     */
    void setOutputTickInfoInRealTime();

    /**
     * @brief 打印计时全过程对应的耗时数据
     */
    void dumpTotalTimeElapseInfo();

    /**
     * @brief 打印每次打点对应的耗时数据
     */
    void dumpEveryTickElapseInfo();

    /**
     * @brief 获取定时器启动的时间
     * @return steady_clock::time_point 启动时间
     */
    std::chrono::steady_clock::time_point getStartTime() const;

    /**
     * @brief 获取打点计数器的总用时
     * 
     * @return std::uint64_t 总共用时(ms)
     */
    std::uint64_t getTotalTimeElapsed();

    /**
     * @brief 获取当前打点的次数
     * @return std::uint32_t 打点次数
     */
    std::uint32_t getCount() const;

    /**
     * @brief 查询打点index对应的打点时间
     * 
     * @param index 打点次数
     * @return steady_clock::time_point 打点时对应的时间
     */
    std::chrono::steady_clock::time_point getTickTime(std::uint32_t index);

    /**
     * @brief 查询的打点次数是否有效
     * 
     * @param index 打点次数
     * @return true 无效
     * @return false 有效
     */
    bool isInValidIndex(std::uint32_t index);

    /**
     * @brief 启动计时
     */
    void start(std::string msg = "start");

    /**
     * @brief 打点计时接口
     * 
     * @return std::uint64_t 返回上次打点到此次打点经过的时间(ms)
     */
    std::uint64_t tick(std::string msg = "");

    /**
     * @brief 获取花费时间最长的一个打点索引
     * @return std::uint32_t 花费时间最长阶段对应的打点次数
     */
    std::uint32_t getTickMaxElapsedTimeIndex();

    /**
     * @brief 获取花费时间最短的一个打点索引
     * @return std::uint32_t 花费时间最短阶段对应的打点次数
     */
    std::uint32_t getTickMinElapsedTimeIndex();

private:
    /**
     * @brief 获取当前的时间
     * @return steady_clock::time_point 时间对象
     */
    static std::chrono::steady_clock::time_point getCurrTime();

    /**
     * @brief 获取自上一次打点到此次打点经过的时间信息
     * 
     * @param index 此次打点的索引: 从序号1开始
     * @return std::pair<std::uint64_t, std::string> 经过的时间(ms)及描述
     */
    std::pair<std::uint64_t, std::string> getTickElapsedTimeInfo(std::uint32_t index);

private:
    static const std::unordered_map<TickTimeUnit, std::string> UNIT_STRING; // 单位信息

    bool realTime_ = false;                                                 // 默认不实时输出,不影响程序运行性能

    std::chrono::steady_clock::time_point startTime_;                       // 开始计时的时间

    std::chrono::steady_clock::time_point endTime_;                         // 最后一次计时的时间

    std::uint32_t count_ = 0;                                               // 打点的次数

    std::vector<TimeStampInfo_t> tickInfo_;                                 // 存储每次打点的信息

    std::string tickerInfo_;                                                // 计时器描述

    TickTimeUnit unit_ = TickTimeUnit::MS;                                  // 默认输出单位
};

#endif // INCLUDE_TOOLS_TICKER_H_

3.2、ticker.cpp

// ticker.cpp
/**
 * @brief 打点计时器工具
 * @author zhangzhiyu
 * @date 2023-05-30
 */

#include "tools/ticker.h"
#include <iostream>

const std::unordered_map<Ticker::TickTimeUnit, std::string> Ticker::UNIT_STRING {
    {TickTimeUnit::MS, "ms"},
    {TickTimeUnit::S, "s"},
};

Ticker::Ticker(std::string pInfo) : tickInfo_(MAX_TICK_TIMES) {
    startTime_ = getCurrTime();
    endTime_ = startTime_;
    tickInfo_[count_++] = std::make_pair(startTime_, "Start");
    tickerInfo_ = pInfo;
}

Ticker::~Ticker() {
}

void Ticker::setTickTimeUnit(TickTimeUnit unit) {
    unit_ = unit;
}

void Ticker::setOutputTickInfoInRealTime() {
    realTime_ = true;
}

void Ticker::start(std::string msg) {
    count_ = 0;
    startTime_ = getCurrTime();
    endTime_ = startTime_;
    tickInfo_[count_] = std::make_pair(startTime_, msg);
    count_++;
}

std::uint64_t Ticker::tick(std::string msg) {
    std::chrono::steady_clock::time_point tmpTime = endTime_;
    endTime_ = getCurrTime();
    std::uint64_t spendTime = std::chrono::duration_cast<std::chrono::duration<std::uint64_t, std::milli>>(endTime_ - tmpTime).count();
    spendTime = (unit_ == TickTimeUnit::S) ? spendTime / 1000 : spendTime;

    if (realTime_) {
        std::cout << "Tick " << count_ << ": time:   ";
        std::cout << spendTime << UNIT_STRING.at(unit_) << ";   msg: " << msg << std::endl;
    }
    tickInfo_[count_++] = std::make_pair(endTime_, msg);
    return spendTime;
}

std::chrono::steady_clock::time_point Ticker::getStartTime() const {
    return startTime_;
}

std::uint32_t Ticker::getCount() const {
    return count_;
}

std::chrono::steady_clock::time_point Ticker::getCurrTime() {
    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
    return t1;
}

std::chrono::steady_clock::time_point Ticker::getTickTime(std::uint32_t index) {
    if (isInValidIndex(index)) {
        return std::chrono::steady_clock::time_point::max();
    }
    return tickInfo_[index].first;
}

bool Ticker::isInValidIndex(std::uint32_t index) {
    return index > count_ - 1;
}

void Ticker::dumpEveryTickElapseInfo() {
    std::cout << "Ticker: " << tickerInfo_ << std::endl;
    for (std::uint32_t i = 1; i < count_; i++) {
        std::cout << "Tick " << i << ": time:   ";
        auto tickTimeInfo = getTickElapsedTimeInfo(i);
        std::cout << tickTimeInfo.first << UNIT_STRING.at(unit_) << ";   msg: " << tickTimeInfo.second << std::endl;
    }
}

void Ticker::dumpTotalTimeElapseInfo() {
    std::uint64_t spendTime = getTotalTimeElapsed();
    std::cout << "Ticker: " << tickerInfo_ << std::endl;
    std::cout << "Total spend: " << spendTime << UNIT_STRING.at(unit_) << ";" << std::endl;
}

std::uint64_t Ticker::getTotalTimeElapsed() {
    std::uint64_t spendTime =
        std::chrono::duration_cast<std::chrono::duration<std::uint64_t, std::milli>>(endTime_ - startTime_).count();
    return (unit_ == TickTimeUnit::S) ? spendTime / 1000 : spendTime;
}

std::pair<std::uint64_t, std::string> Ticker::getTickElapsedTimeInfo(std::uint32_t index) {
    if (isInValidIndex(index)) {
        return std::make_pair(std::numeric_limits<std::uint64_t>::max(), "invalid index");
    }
    std::pair<std::uint64_t, std::string> res;
    if (index == 0) {
        res = std::make_pair(0, "start");
    } else {
        auto time = std::chrono::duration_cast<std::chrono::duration<std::uint64_t, std::milli>>(tickInfo_[index].first -
                                                                                                 tickInfo_[index - 1].first)
                        .count();
        if (unit_ == TickTimeUnit::S) {
            time = time / 1000;
        }
        std::string msg = tickInfo_[index].second;
        res = std::make_pair(time, msg);
    }
    return res;
}

std::uint32_t Ticker::getTickMaxElapsedTimeIndex() {
    std::uint32_t result = -1;
    std::uint64_t m_MaxTime = 0;
    std::uint64_t tmpTime;
    for (std::uint32_t i = 0; i < count_; i++) {
        tmpTime = getTickElapsedTimeInfo(i).first;
        if (tmpTime == std::numeric_limits<std::uint64_t>::max()) { // 时间无穷大则说明ID有问题,此次不计入统计
            continue;
        }
        if (tmpTime > m_MaxTime) {
            m_MaxTime = tmpTime;
            result = i;
        }
    }
    return result;
}

std::uint32_t Ticker::getTickMinElapsedTimeIndex() {
    std::uint32_t result = -1;
    std::uint64_t m_MinTime = std::numeric_limits<std::uint64_t>::max();
    std::uint64_t tmpTime;
    for (std::uint32_t i = 0; i < count_; i++) {
        tmpTime = getTickElapsedTimeInfo(i).first;
        if (tmpTime == std::numeric_limits<std::uint64_t>::max()) { // 时间无穷大则说明ID有问题,此次不计入统计
            continue;
        }
        if (tmpTime < m_MinTime) {
            m_MinTime = tmpTime;
            result = i;
        }
    }
    return result;
}

3.3、用法

#include "tools/ticker.h"

int main() {
    Ticker ticker;
    ticker.start();
    for (int i = 0; i < 5; i++) {
        std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 500ms between ticks.
        ticker.tick("zzy");
    }
    ticker.dumpEveryTickElapseInfo();
    ticker.dumpTotalTimeElapseInfo();
    return 0;
}

//运行结果:
Ticker: Default ticker name
Tick 1: time:   1216ms;   msg: zzy
Tick 2: time:   1201ms;   msg: zzy
Tick 3: time:   1204ms;   msg: zzy
Tick 4: time:   1214ms;   msg: zzy
Tick 5: time:   1201ms;   msg: zzy
Ticker: Default ticker name
Total spend: 6037ms;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值