본문 바로가기
C++/프로젝트

[c++][프로젝트]Pandora라이브러리 LapLogger

by 계양구놈팽이 2023. 3. 5.

실행하는 코드라인의 execution time을 측정하는 class이다.

Lap 이란 스톱워치에서 측정되는 기능으로, 각 구간의 차이를 측정해 주는 기능이다.

(달리기에서 1등 선수와 2등 선수의 시간 차이 같은 거 측정 한거)

    //lap_logger.h
    #include <string>
	#include "stop_watch.h"
	#include <unordered_map>
	#include <iostream>
    
    class LapLogger
    {
    public:
        LapLogger(bool enableModule = false);

        static const int LAP_LOGGER_MICROSECOND = 1000000;
        static const int LAP_LOGGER_MILLISECOND = 1000;
        static const int LAP_LOGGER_SECOND = 1;

        bool bEnable;
        void operator() (const std::string& item, bool resetTime = true);

        void Reset();
        void Start();
        double GetTime(const std::string& item) const;
        double GetTotalTime() const;
        std::string ShowResult(int type = LAP_LOGGER_MILLISECOND) const;

    private:
        StopWatch watch;
        std::unordered_map<std::string, double> ptimes;
        double mTimeSum;
    };

디버깅 단계에서는 사용하지만, 실제 완성된 코드에서는 사용되지 않을 때를 대비해서 생성자에서 argument로 사용여부를 결정할 수 있게 하였다.

 

맨 처음에 이 상수들에 대하여 enum을 써야 할지 static 상수를 사용할지 고민하였다.

        static const int LAP_LOGGER_MICROSECOND = 1000000;
        static const int LAP_LOGGER_MILLISECOND = 1000;
        static const int LAP_LOGGER_SECOND = 1;

 

 

단순히 ShowResult에서 결과를 보여주기 위한 단위를 정하기 위해 서만 사용한다면 Enum으로 해도 좋았겠지만, ShowResult에서 저 상수들을 사용해 elapsed time에 받은 argument에 따라 상수를 사용해서 단위에 맞추어 elapsed time에 곱해줘야 한다.

그렇기에 static 상수를 사용하였다. 

 

Start함수에서 부터 시간을 측정하여, 그다음 operator [](&<msg>)가 호출되기 전까지 시간을 계속 측정하게 할 생각이다.그러고 나서  그다음 operator [](&<msg>) 가 불릴 때까지 또 시간을 측정한다. operator [](&<msg>) 는 일종의 stop watch에서 lap타임을 측정하는 것과 같다고 생각하면 좋다.

//lap_logger.cpp
#include "lap_logger.h"
#include <iomanip>
#include <sstream>

    LapLogger::LapLogger(bool enableModule)
        : bEnable(enableModule)
        , mTimeSum(0)
    {}

    void LapLogger::Reset()
    {
        if (!bEnable) return;
        for (auto & it: ptimes)
        {
            it.second = 0;
        }
        mTimeSum = 0;
    }

    void LapLogger::Start()
    {
        if (!bEnable) return;
        watch.Stop();
    }

    void LapLogger::operator() (const std::string& item, bool resetTime)
    {
        if (!bEnable) return;
        double etime = watch.Stop();

        if (resetTime)
        {
            ptimes[item] = etime;
        }
        else
        {
            ptimes[item] += etime;
        }

        watch.Stop();

        mTimeSum += etime;
    }

    double LapLogger::GetTime(const std::string& item) const
    {
        return ptimes.at(item);
    }

    double LapLogger::GetTotalTime() const
    {
        return mTimeSum;
    }

    std::string LapLogger::ShowResult(int type) const
    {
        if (!bEnable) return "";

        int maxStringLength(0);
        for (const auto& it : ptimes)
        {
            maxStringLength = std::max(maxStringLength, static_cast<int>(it.first.size()));
        }

        maxStringLength++;

        double sum_of_time(0.0);
        std::string unit;
        switch (type)
        {
        case LAP_LOGGER_MICROSECOND:
            unit = std::string("[us]");
            break;
        case LAP_LOGGER_MILLISECOND:
            unit = std::string("[ms]");
            break;
        case LAP_LOGGER_SECOND:
            unit = std::string("[ s]");
            break;
        }

        const int maxFieldWidth = (maxStringLength + 16 > std::numeric_limits<int>::max())
            ? std::numeric_limits<int>::max() : maxStringLength + 16;

        std::ostringstream output;

        output << std::left << std::setw(maxStringLength) << "Item" << "Elapsed Time " << unit << std::endl;
        output << std::setfill('-') << std::setw(maxFieldWidth) << "-" << std::endl;
        output << std::setfill(' ');

        // write down on screen
        for (const auto& it : ptimes)
        {
            double etime = static_cast<double>(it.second) * type;

            output << std::left << std::setw(maxStringLength) << it.first << std::fixed << std::setprecision(4) 
                << std::setw(10) << etime << " " << unit << std::endl;
            sum_of_time += etime;
        }

        output << std::setfill('-') << std::setw(maxFieldWidth) << "-" << std::endl;
        output << std::setfill(' ');
        output << std::left << std::setw(maxStringLength) << "Total Time : " << std::fixed << std::setprecision(4) 
            << std::setw(10) << sum_of_time << " " << unit << std::endl;


        return output.str();
    }

operator [](&<msg>)가 호출되면 msg를 키로 그리고 측정 결과를 값으로 hash자료구조 기반의 unorderd_map에 저장.

unorderd_mapdms GetTime으로 사용자가 특정 구간에서의 elapsedtime만을 알고 싶을때 빠르게 서치를 해서 제공 하기위해서 사용 하였다. Tree자료구조 기반의 map보다 빠른데다가, 불필요하게 key값을 기준으로 sorting 하지 않아주기에 훨씬 용이하다.

 

이 class를 사용하는 예시는 다음과 같다.

#include <iostream>
#include <thread>
#include "lap_logger.h"
#include "periodic_task.h"


int main()
{
    pandora::LapLogger lap_logger(true);
    std::cout << "Time logger start " << std::endl;
    lap_logger.Start();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    lap_logger("step1");
    std::this_thread::sleep_for(std::chrono::seconds(1));
    lap_logger("step2");

    std::this_thread::sleep_for(std::chrono::seconds(1));
    lap_logger("step3");
    std::this_thread::sleep_for(std::chrono::seconds(2));
    lap_logger("step4");

    std::cout << lap_logger.ShowResult(pandora::LapLogger::LAP_LOGGER_MICROSECOND) <<  std::endl;


    return 0;
}

위의 예제 코드를 실행하면 다음과 같은 결과가 나온다.

Time logger start
Item  Elapsed Time [us]
----------------------
step1 1011985.7000 [us]
step2 1005382.5000 [us]
step3 1014848.3000 [us]
step4 2011307.0000 [us]
----------------------
Total Time : 5043523.5000 [us]

실행 시간에 민감한 프로그램을 만들면서 실시간으로 디버깅을 하고자 할 때 유용하게 사용 가능하다.