1
\$\begingroup\$

The class below will be used to track the execution time of various operations. There is no need to dip into C libraries to get formatted output then.

Output would be something like this:

  • 10 seconds 345 milliseconds 324 microseconds
  • 23 hours 32 minutes 15 seconds 324 milliseconds

It depends on the class initialization units and time passed. My query is whether any improvement is possible on this design or this good enough to be used widely. There are dummy parameters in the various diff_str private functions to enable overloading since seconds::rep and microseconds::rep essentially boils down to same basic types and so one for other pairs. I have done this on VS 2013.

#include <chrono>
#include <iostream>

template<typename clock_type, typename dur>
class ExecutionTimer
{
private:
   typename clock_type::time_point m_start;
   typename clock_type::time_point m_end;

   const std::wstring SS = std::wstring(L" ");

public:
   void set_start(){
     m_start = clock_type::now();
   }
   void set_start(typename clock_type::time_point start){
     m_start = start;
   }
   void set_end(){
     m_end = clock_type::now();
   }
   void set_end(typename clock_type::time_point end){
     m_end = end;
   }
   typename dur::rep diff()
   {
       return std::chrono::duration_cast<dur>(m_end - m_start).count();
   };

   std::wstring diff_str()
   {
      auto ret = diff_str(diff(), std::chrono::duration_cast<dur>(std::chrono::seconds(1)));
      return ret;
   }

private:

std::wstring 
    diff_str(std::chrono::hours::rep value, std::chrono::hours dummy)
 {
    using namespace std::chrono;
    auto ret = std::to_wstring(value) + SS + L"hours";
    return ret;
 }

std::wstring 
    diff_str(std::chrono::minutes::rep value, std::chrono::minutes dummy)
{
    using namespace std::chrono;
    std::wstring ret{ L"" };
    auto cmp_unit = minutes(60).count();
    auto dummy_to_pass = duration_cast<hours>(dummy);

    if (value > cmp_unit){
        ret = diff_str(hours::rep(value / cmp_unit), dummy_to_pass);
        ret += SS + std::to_wstring(value % cmp_unit) + SS + L"minutes";
    }
    else if (value == cmp_unit){
        ret = diff_str(hours::rep(1), dummy_to_pass);
    }
    else{
        ret = std::to_wstring(value) + SS + L"minutes";
    }
    return ret;
 }

std::wstring 
    diff_str(std::chrono::seconds::rep value, std::chrono::seconds dummy)
{
    using namespace std::chrono;
    std::wstring ret{ L"" };
    auto cmp_unit = seconds(60).count();
    auto dummy_to_pass = duration_cast<minutes>(dummy);

    if (value > cmp_unit){
        ret = diff_str(minutes::rep(value / cmp_unit), dummy_to_pass);
        ret += SS + std::to_wstring(value % cmp_unit) + SS + L"seconds";
    }
    else if (value == cmp_unit){
        ret = diff_str(minutes::rep(1), dummy_to_pass);
    }
    else{
        ret = std::to_wstring(value) + SS + L"seconds";
    }
    return ret;
}

std::wstring 
    diff_str(std::chrono::milliseconds::rep value, std::chrono::milliseconds dummy)
{
    using namespace std::chrono;
    std::wstring ret{ L"" };
    auto cmp_unit = milliseconds(1000).count();
    auto dummy_to_pass = duration_cast<seconds>(dummy);

    if (value > cmp_unit){
        ret = diff_str(seconds::rep(value / cmp_unit), dummy_to_pass);
        ret += SS + std::to_wstring(value % cmp_unit) + SS + L"milliseconds";
    }else if (value == cmp_unit){
        ret = diff_str(seconds::rep(1), dummy_to_pass);
    }else{
        ret = std::to_wstring(value) + SS + L"milliseconds";
    }
    return ret;
 }

std::wstring
    diff_str(std::chrono::microseconds::rep value, std::chrono::microseconds dummy)
{
    using namespace std::chrono;
    std::wstring ret{ L"" };
    auto cmp_unit = microseconds(1000).count();
    auto dummy_to_pass = duration_cast<milliseconds>(dummy);

    if (value > cmp_unit){
        ret = diff_str(milliseconds::rep(value / cmp_unit), dummy_to_pass);
        ret += SS + std::to_wstring(value % cmp_unit) + SS + L"microseconds";
    }
    else if (value == cmp_unit){
        ret = diff_str(milliseconds::rep(1), dummy_to_pass);
     }
    else{
        ret = std::to_wstring(value) + SS + L"microseconds";
    }
    return ret;
}
};

///////////////////////////////////////////////////////////////////////////////
int main()
{
   using namespace std::chrono;
   using namespace std;

   ExecutionTimer<high_resolution_clock, microseconds> exe_timer_micro;
   ExecutionTimer<high_resolution_clock, milliseconds> exe_timer_milli;
   exe_timer_micro.set_start();
   exe_timer_milli.set_start();

   for (size_t i = 0; i < (std::numeric_limits<unsigned int>::max()); i++)
   {
       if (0 == i % (32 * std::numeric_limits<unsigned short>::max())){
           wcout << L".";
           exe_timer_micro.set_end(high_resolution_clock::now());
           exe_timer_milli.set_end(high_resolution_clock::now());
       }
    }
    wcout << endl;
    wcout << to_wstring(exe_timer_micro.diff()) << L" microseconds" << endl;
    wcout << exe_timer_micro.diff_str() << endl;

    wcout << to_wstring(exe_timer_milli.diff()) << L" milliseconds" << endl;
    wcout << exe_timer_milli.diff_str() << endl;
}
\$\endgroup\$
  • \$\begingroup\$ This is somewhat similar to this stopwatch implementation. \$\endgroup\$ – Edward Apr 15 '15 at 14:06
  • \$\begingroup\$ Yes, that stopwatch is almost like this, but I added extra functionality for printing which you also wanted to do it seems. \$\endgroup\$ – Atul Kumar Apr 15 '15 at 18:49
2
\$\begingroup\$

My query is whether any improvement is possible on this design or this good enough to be used widely.

This is good enough to be used widely (IMHO), but it could use some small improvements :) :

Since you are using it to measure execution time, I'm not sure you need to support both milliseconds and microseconds clocks (I would just remove the template arguments and work on microseconds).

As it is, the class does too much (timing code, string manipulation and measuring arbitrary intervals).

Consider how your client code would look using this public interface (this is a similar class I wrote a few months ago):

class execution_timer final
{
public:
    execution_timer();
    ~execution_timer();

    /// @brief Time elapsed since construction or last reset_timer call
    double elapsed() const;
    void reset_timer();
private:
    typedef std::chrono::high_resolution_clock  high_res_clock;
    std::chrono::time_point<high_res_clock> start_time_;
};

execution_timer::execution_timer()
: start_time_(high_res_clock::now())
{
}

execution_timer::~execution_timer()
{
}

void execution_timer::reset_timer() {
    start_time_ = high_res_clock::now();
}

double execution_timer::elapsed() const {
    typedef std::chrono::duration<double,std::ratio<1>>   seconds;
    auto result = std::chrono::duration_cast<seconds>(
        high_res_clock::now() - start_time_
    ).count();

    return result;
}

This class also hides all std::chrono client code.

\$\endgroup\$
  • \$\begingroup\$ As you can see in my code, there are two public interfaces diff and diff_str. I will definitely rename those to elapsed and elapsed_str now. The idea is to be able to use multiple kind of clocks and different intervals. Sometimes when we know that any useful operation could not be done in less than a second, it would make sense to go with second interval. I will think about whether to mark set_start as reset_start. \$\endgroup\$ – Atul Kumar Apr 15 '15 at 18:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.