スコープ内の時間計測

以前、関数の時間計測のために返り値をラップする構造体を作っていたが、これはRAII的に考えて

Logger logger;
/*... codes ...*/
{
    stop_watch<std::chrono::milliseconds> sw("block-name", logger);
    /* do_something */
}

とスコープの先頭で宣言すればスコープ内でかかった時間のログが取れるようにできる。

例えば、何かログ用のメソッドをうまく作っておいて、コンストラクタが初期化時の時刻を読み、デストラクタがデストラクト時の時刻を読んで、その差のログを取ればよいのだ。

struct logger
{
    template<typename resolutionT>
    void write(const std::string& str, const resolutionT& elapsed);
};

template<typename resolutionT>
struct stop_watch
{
    stop_watch(const std::string& name Logger& logger)
    : start(std::chrono::system_clock::now()),
      name_(name), logger_(logger)
    {}
    ~stop_watch()
    {
        const auto stop = std::chrono::system_clock::now();
        const auto elapsed =
            std::chrono::duration_cast<resolutionT>(stop - start);
        logger.write(name_, elapsed);
    }

    const std::chrono::system_clock::time_point start;
    const std::string name_;
    Logger& logger_;
};

もちろん、同一ファイルへの書き出しはうまくロックする必要があるし、Loggerへの参照がちゃんと壊れないことを保証する必要がある。

このようにしておけば、例えばループのそれぞれがどの程度の時間がかかったかも簡単に計測できる。

Logger logger;
for(std::size_t i=0; i<100; ++i)
{
    stop_watch<std::chrono::milliseconds>
        sw(std::string("iteration") + std::to_string(i), logger);
    /* do something */
}

最初はデストラクタでLoggerのstaticな関数を呼び出すしかないのかなあと思って気が進まなかったのだが、別に参照を持っていればいいということに気づいたので(寿命が保証されないので気になるならshared_ptrとかを使ったほうが良い)だいぶ心が軽くなった。

こういうやり方をぱぱっと思いつけないうちはまだまだC++的(RAII的)思考に慣れきっていないなあと思う。