PG日誌

各記事はブラウザの横幅を1410px以上にすると2カラムの見出しが表示されます。なるべく横に広げてみてください。

C++で時間計測 & ストップウォッチを実装する

C++11から「std::chrono」という時間を表す型がいくつか追加になっています。

従来の時間取得方法からずいぶん使いやすい形で機能が提供されています。

ある区間の時間を図るには以下のように記述します。

#include <iostream>
#include <chrono>

void Foo()
{
    // 開始・終了時間を各々で記録する変数
    std::chrono::system_clock::time_point begin;
    std::chrono::system_clock::time_point end;
        
    // この区間の開始・終了時刻を取る
    begin = std::chrono::system_clock::now();
    AnyProcess();
    end = std::chrono::system_clock::now();
    
    // ミリ秒をdouble型で取得する
    cout << "Elapsed(msec) = " 
        << std::chrono::duration_cast<std::chrono::nanoseconds>(end-begin).count(); << endl;
    
    // > Elapsed(msec) = 1521
}

dulation型で記録されている時間は、duration_cast\<T>という固有のキャストに"std::chrono::nanoseconds"などの時間を表すシンボルを与えることで任意の型に変換することができます。

変換後の値は "cout" メソッドを呼び出すことによって取得できます。

C#風のStopwatchクラスをC++で実装する

毎回この表記を書いて時間を図るのは結構面倒です。なので丁度C#に、いい感じの時間を図るStopwatchクラスがあるのでそれをC++でも実装してみようと思います。

Stopwatchクラスの使い方

まずは実装したStopwatchクラスの使い方から見ていきたいと思います。

void Foo()
{
    // 時間測定を開始した状態でインスタンスを作成
    auto sw1 = diagnostics::Stopwatch::startNew();
    
    // ~~何らかの処理~~
    
    sw1->stop(); // 時間測定を停止
    
    // 結果を取得
    cout << "Elapsed(nano sec) = " << sw1->getElapsedNanoseconds() << endl;
    cout << "Elapsed(milli sec) = " << sw1->getElapsedMilliseconds() << endl;
    cout << "Elapsed(sec) = " << sw1->getElapsedSeconds() << endl;
    // > Elapsed(nano sec) = 74255700
    // > Elapsed(milli sec) = 74.2557
    // > Elapsed(sec) = 0.0742557
    
    sw1->reset(); // リセットして計測を再開
    sw1->start();
}

使い方はそれぞれ以下の通りです。

  • startNew()で測定開始 & Stopwatchクラスのインスタンスを作成
  • stop() で測定停止
  • getElapsedXXXX で目的の時間を取得
  • reset() で計測時間をクリア
  • start() で計測を開始

ちょっとしたことですが、このクラスを使用すると小数点で秒が取得できます。どんな時も普通に使えるテクニックなのでやり方は以下の実装を参照してください。

Stopwachクラスの実装

ヘッダーに全部書いてしまっています。

めちゃくちゃ長いですが基本的にコピペでOKなはずです。

// Stopwatch.hpp

#pragma once
#pragma execution_character_set("utf-8")

#include <chrono>
#include <memory>

namespace diagnostics
{
/**
 * C#の System.Stopwatch クラスっぽい動きをする時間測定クラス
 */
class Stopwatch
{
    // 動作しているかのフラグ
    // true : 動作中 / false : 停止中
    bool isRunning = false;
    // 測定開始時刻
    std::chrono::system_clock::time_point begin;
    // 測定開始時刻
    std::chrono::system_clock::time_point end;
    // 合計時間
    std::chrono::nanoseconds elapsed;

    Stopwatch() = default; // CreateNew or StartNewで作って!

public:

    // 意味ないと思うのでmove以外禁止
    ~Stopwatch() = default;
    Stopwatch(const Stopwatch&) = delete;
    Stopwatch& operator=(const Stopwatch) = delete;
    Stopwatch(Stopwatch&&) = default;

    Stopwatch& operator=(Stopwatch&&) = default;

    bool getIsRunning() { return this->isRunning; }

    // 任意の型で計測時間を取り出す
    // Tempalte
    //   Unit  : 取得したい時間の型
    //   Ratio : 時間の単位
    template <typename Unit, class Ratio>
    Unit getElapsed()
    {
        if (this->isRunning)
        {
            return std::chrono::duration_cast<std::chrono::duration<Unit, Ratio>>(
                std::chrono::system_clock::now() - this->begin).count(); // TooLong!!
        }
        else
        {
            return std::chrono::duration_cast<std::chrono::duration<Unit, Ratio>>(this->elapsed).count();
        }
    }

    // 経過時間を秒の倍精度浮動小数点として取り出す
    double getElapsedSeconds()
    {
        return this->getElapsed<double, std::ratio<1>>();
    }

    // 経過時間をミリ秒の倍精度浮動小数点として取り出す
    double getElapsedMilliseconds()
    {
        return this->getElapsed<double, std::milli>();
    }

    // 経過時間をナノ秒として取り出す
    long long getElapsedNanoseconds()
    {
        return this->getElapsed<long long, std::nano>();
    }

    void reset()
    {
        elapsed = std::chrono::nanoseconds(0);
        begin = end = std::chrono::system_clock::now();
    }

    void restart()
    {
        this->reset();
        this->start();
    }

    void start()
    {
        if (this->isRunning)
        {
            return;
        }

        this->isRunning = true;
        begin = end = std::chrono::system_clock::now();
    }

    void stop()
    {
        if (!this->isRunning)
        {
            return;
        }

        this->isRunning = false;
        end = std::chrono::system_clock::now();
        this->elapsed = this->elapsed + std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
    }

    static std::unique_ptr<Stopwatch> createNew()
    {
        std::unique_ptr<Stopwatch> sw(new Stopwatch());
        return sw;
    }

    static std::unique_ptr<Stopwatch> startNew()
    {
        auto sw = createNew();
        sw->start();
        return sw;
    }
};
}

"getElapsed" メソッドだけ使い方が分かりにくいですが、ヘッダー内で "getElapsedSeconds" や "getElapsedMilliseconds" から利用しているので使用方法はそちらを参照してください。

また、インスタンスはunique_ptrを使用してmoveしている & インスタンスは "createNew" か "startNew" でしか生成できないようにしているので、それが余計なお世話だと感じたら適当に修正してください。

簡単ですが以上です。