【问题标题】:How to generalize std::chrono::duration(s)?如何概括 std::chrono::duration(s)?
【发布时间】:2019-10-12 09:45:15
【问题描述】:

我为我的大学课程编写了三个版本的算法。

一个是蛮力,另一个是贪婪,最后一个是启发式。

我希望能够测量完成每个算法所需的时间。

我正在使用<chrono> 库来实现这一目标

现在我的代码如下所示:

#include <iostream>
#include <chrono>
#include <sstream>

using namespace std;

string getTimeElapsed(long time1, const string &unit1, long time2 = 0, const string &unit2 = "") {
    stringstream s;
    s << time1 << " [" << unit1 << "]";
    if (time2) s << " " << time2 << " [" << unit2 << "]";
    return s.str();
}

int main() {
    auto begin = chrono::system_clock::now();
    // algorithm goes here
    auto solution = /* can be anything */
    auto end = chrono::system_clock::now();
    auto diff = end - begin;

    string timeElapsed;
    auto hours = chrono::duration_cast<chrono::hours>(diff).count();
    auto minutes = chrono::duration_cast<chrono::minutes>(diff).count();
    if (hours) {
        minutes %= 60;
        timeElapsed = getTimeElapsed(hours, "h", minutes, "min");
    } else {
        auto seconds = chrono::duration_cast<chrono::seconds>(diff).count();
        if (minutes) {
            seconds %= 60;
            timeElapsed = getTimeElapsed(minutes, "min", seconds, "s");
        } else {
            auto milliseconds = chrono::duration_cast<chrono::milliseconds>(diff).count();
            if (seconds) {
                milliseconds %= 1000;
                timeElapsed = getTimeElapsed(seconds, "s", milliseconds, "ms");
            } else {
                auto microseconds = chrono::duration_cast<chrono::microseconds>(diff).count();
                if (milliseconds) {
                    microseconds %= 1000;
                    timeElapsed = getTimeElapsed(milliseconds, "ms", microseconds, "μs");
                } else {
                    auto nanoseconds = chrono::duration_cast<chrono::nanoseconds>(diff).count();
                    if (microseconds) {
                        nanoseconds %= 1000;
                        timeElapsed = timeElapsed = getTimeElapsed(microseconds, "μs", nanoseconds, "ns");
                    } else timeElapsed = getTimeElapsed(nanoseconds, "ns");
                }
            }
        }
    }

    cout << "Solution [" << solution << "] found in " << timeElapsed << endl;

    return 0;
}

如您所见,堆叠的if-else 子句看起来非常难看,您可以在这里看到一个模式:

if (timeUnit) { 
    timeElapsed = /* process current time units */
} else {
    /* step down a level and do the same for smaller time units */
}

我想让这个过程成为一个递归函数。

但是,我不知道该函数的参数应该是什么,因为chrono::duration 是一个模板结构(?)

这个函数看起来有点像这样:

string prettyTimeElapsed(diff, timeUnit) {
    // recursion bound condition
    if (timeUnit is chrono::nanoseconds) return getTimeElapsed(timeUnit, "ns");

    auto smallerTimeUnit = /* calculate smaller unit using current unit */
    if (timeUnit) return getTimeElapsed(timeUnit, ???, smallerTimeUnit, ???);
    else return prettyTimeElapsed(diff, smallerTimeUnit);
}

我正在考虑这样做:

auto timeUnits = {chrono::hours(), chrono::minutes(), ..., chrono::nanoseconds()};

然后我可以将指针(甚至是索引)指向时间单位并将其传递给函数。

问题是我不知道如何概括这些结构。

CLion 突出显示错误Deduced conflicting types (duration&lt;[...], ratio&lt;3600, [...]&gt;&gt; vs duration&lt;[...], ratio&lt;60, [...]&gt;&gt;) for initializer list element type

【问题讨论】:

  • 所以在伪代码中,你需要std::cout &lt;&lt; correctly_formatted(end - begin)。我说的对吗?
  • @L.F.是的。 correctly_formattedchrono 库中的函数吗?如果是,那将为我解决问题
  • 不幸的是,这是伪代码......我发布了该评论以确保我正确理解了您的问题。
  • 能否提供一些时间格式化函数的输入输出示例?
  • @L.F.例如,算法在1000000000 纳秒后找到解决方案。我想看看Solution [...] found in 1 [s]。如果算法适用于 1234 纳秒:... 1 [μs] 234 [ns] 123456789 纳秒 => 123 [ms] 456 [μs]123 [ms] 457 [μs] 我不太关心较小的单位精度(可以舍入或截断)

标签: c++ templates generics chrono


【解决方案1】:

使用chrono 时最好的一般建议是仅在绝对必要时才转义类型系统(使用.count())。这可能是与 C 或一些不理解 chrono 的 C++ 库交互。在 C++ 20 之前,这也意味着输出到流。

如果我们将自己留在类型系统中,我们可以获得很多始终正确的良好转换。

让我们更正问题中的代码以反映这一点:

#include <iostream>
#include <chrono>
#include <sstream>

std::string getTimeElapsed(long time1, const std::string &unit1, long time2 = 0, const std::string &unit2 = "") {
    std::stringstream s;
    s << time1 << " [" << unit1 << "]";
    if (time2) s << " " << time2 << " [" << unit2 << "]";
    return s.str();
}

int main() {
    auto begin = std::chrono::system_clock::now();
    // algorithm goes here
    auto solution = "solution"; /* can be anything */
    auto end = std::chrono::system_clock::now();
    auto diff = end - begin;

    std::string timeElapsed{""};
    // Let's make the typing and reading easier for us but requires C++14
    using namespace std::chrono_literals;
    auto hours = std::chrono::duration_cast<std::chrono::hours>(diff);
    auto minutes = std::chrono::duration_cast<std::chrono::minutes>(diff % 1h);
    if (hours != 0h) {
        // We need to escape the type system to call getTimeElapsed
        timeElapsed = getTimeElapsed(hours.count(), "h", minutes.count(), "min");
    } else {
        auto seconds = std::chrono::duration_cast<std::chrono::seconds>(diff % 1min);
        if (minutes != 0min) {
            timeElapsed = getTimeElapsed(minutes.count(), "min", seconds.count(), "s");
        } else {
            auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(diff % 1s);
            if (seconds != 0s) {
                timeElapsed = getTimeElapsed(seconds.count(), "s", milliseconds.count(), "ms");
            } else {
                auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(diff % 1ms);
                if (milliseconds != 0ms) {
                    timeElapsed = getTimeElapsed(milliseconds.count(), "ms", microseconds.count(), "μs");
                } else {
                    auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(diff % 1us);
                    if (microseconds != 0us) {
                        timeElapsed = timeElapsed = getTimeElapsed(microseconds.count(), "μs", nanoseconds.count(), "ns");
                    } else timeElapsed = getTimeElapsed(nanoseconds.count(), "ns");
                }
            }
        }
    }

    std::cout << "Solution [" << solution << "] found in " << timeElapsed << std::endl;

    return 0;
}

现在我们会尽可能长时间地使用chrono。调用 getTimeElapsed 尚不兼容 chrono

我并不完全满意,所以让我们也支持getTimeElapsed 中的durations:

template <typename Duration1, typename Duration2>
std::string getTimeElapsed(Duration1 time1, const std::string &unit1, Duration2 time2, const std::string &unit2) {
    std::stringstream s;
    s << time1.count() << " [" << unit1 << "]";
    if (time2 != Duration2::zero()) s << " " << time2.count() << " [" << unit2 << "]";
    return s.str();
}

template <typename Duration1>
std::string getTimeElapsed(Duration1 time1, const std::string &unit1) {
    std::stringstream s;
    s << time1.count() << " [" << unit1 << "]";
    return s.str();
}

我们需要两个版本的getTimeElapsed,因为在最后一个else 中我们只使用一个时间和单位参数对,这意味着我们不能满足两个Duration 类型的template 参数要求。 现在代码看起来好多了(只保留相关的更改):

...
    if (hours != 0h) {
        timeElapsed = getTimeElapsed(hours, "h", minutes, "min");
    } else {
        auto seconds = std::chrono::duration_cast<std::chrono::seconds>(diff % 1min);
        if (minutes != 0min) {
            timeElapsed = getTimeElapsed(minutes, "min", seconds, "s");
        } else {
            auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(diff % 1s);
            if (seconds != 0s) {
                timeElapsed = getTimeElapsed(seconds, "s", milliseconds, "ms");
            } else {
                auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(diff % 1ms);
                if (milliseconds != 0ms) {
                    timeElapsed = getTimeElapsed(milliseconds, "ms", microseconds, "μs");
                } else {
                    auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(diff % 1us);
                    if (microseconds != 0us) {
                        timeElapsed = timeElapsed = getTimeElapsed(microseconds, "μs", nanoseconds, "ns");
                    } else timeElapsed = getTimeElapsed(nanoseconds, "ns");
                }
            }
        }
    }
    ...

太棒了!但是,我们仍然邀请用户向getTimeElapsed 发送他们想要的任何内容,除非他们碰巧有.count() 成员,否则将导致编译器错误。 让我们稍微限制一下template

template <typename Rep1, typename Ratio1, typename Rep2, typename Ratio2>
std::string getTimeElapsed(std::chrono::duration<Rep1, Ratio1> time1, const std::string &unit1, std::chrono::duration<Rep2, Ratio2>  time2, const std::string &unit2) {
    std::stringstream s;
    s << time1.count() << " [" << unit1 << "]";
    if (time2 != time2.zero()) s << " " << time2.count() << " [" << unit2 << "]";
    return s.str();
}

template <typename Rep, typename Ratio>
std::string getTimeElapsed(std::chrono::duration<Rep, Ratio> time1, const std::string &unit1) {
    std::stringstream s;
    s << time1.count() << " [" << unit1 << "]";
    return s.str();
}

我们不需要为此更改调用代码。我相信这足以帮助您了解如何在更通用的上下文中使用std::chrono::duration,这是您的一个子问题。

现在我们可以开始解决您的问题,我认为(通过阅读 cmets)实际上是“我如何整理嵌套的 if 语句并仅打印前两个非零单元。”

这并不像最初看起来那么简单。在我看来,递归很少是答案。将其视为单元类型的循环也对其进行了过度设计,您需要编写一些代码来从元组中获取当前类型的索引,将其加一,然后使用它来索引相同的元组以获得更高分辨率的下一个单元。然后,当所有这些都说完了,你仍然需要知道要打印什么单位来为值提供它的上下文。我宁愿看到getTimeElapsed这样写:

std::string getTimeElapsed(std::chrono::nanoseconds elapsed, size_t maxUnits = 2)
{
    using namespace std::chrono_literals;
    std::ostringstream formatted("");

    int usedUnits{};

    auto hours = std::chrono::duration_cast<std::chrono::hours>(elapsed);
    if (hours != 0h)
    {
        formatted << hours.count() << " [h] ";
        ++usedUnits;
    }

    auto minutes = std::chrono::duration_cast<std::chrono::minutes>(elapsed % 1h);
    if (minutes != 0min)
    {
        formatted << minutes.count() << " [min] ";
        ++usedUnits;
    }

    auto seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed % 1min);
    if (seconds != 0min && usedUnits != maxUnits)
    {
        formatted << seconds.count() << " [s] ";
        ++usedUnits;
    }

    auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed % 1s);
    if (milliseconds != 0ms && usedUnits != maxUnits)
    {
        formatted << milliseconds.count() << " [ms] ";
        ++usedUnits;
    }

    auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(elapsed % 1ms);
    if (microseconds != 0us && usedUnits != maxUnits)
    {
        formatted << microseconds.count() << " [us] ";
        ++usedUnits;
    }

    auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed % 1us);
    if (nanoseconds != 0us && usedUnits != maxUnits)
    {
        formatted << nanoseconds.count() << " [us] ";
        ++usedUnits;
    }

    return formatted.str();
}

将总经过时间作为std::chrono::nanoseconds(您已经从end - begin 获得)并将其传递给getTimeElapsed。我们现在进行与以前相同的计算以获取组件单位,但还要跟踪我们计算了多少单位。如果elapsed为1'000'000'000ns,则结果为“1 [s]”;如果elapsed 为 1'234'568ns,则结果为“1 [ms] 234 [us]”。后面有空格,但我会留给你解决。

这也意味着我们不再需要我们之前重构的templates,但我添加它们是为了展示我在整个重构过程中的思考过程。最终程序如下:

#include <chrono>
#include <iostream>
#include <sstream>

std::string getTimeElapsed(std::chrono::nanoseconds elapsed, size_t maxUnits = 2)
{
    using namespace std::chrono_literals;
    std::ostringstream formatted("");

    int usedUnits{};

    auto hours = std::chrono::duration_cast<std::chrono::hours>(elapsed);
    if (hours != 0h)
    {
        formatted << hours.count() << " [h] ";
        ++usedUnits;
    }

    auto minutes = std::chrono::duration_cast<std::chrono::minutes>(elapsed % 1h);
    if (minutes != 0min)
    {
        formatted << minutes.count() << " [min] ";
        ++usedUnits;
    }

    auto seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed % 1min);
    if (seconds != 0min && usedUnits != maxUnits)
    {
        formatted << seconds.count() << " [s] ";
        ++usedUnits;
    }

    auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed % 1s);
    if (milliseconds != 0ms && usedUnits != maxUnits)
    {
        formatted << milliseconds.count() << " [ms] ";
        ++usedUnits;
    }

    auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(elapsed % 1ms);
    if (microseconds != 0us && usedUnits != maxUnits)
    {
        formatted << microseconds.count() << " [us] ";
        ++usedUnits;
    }

    auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed % 1us);
    if (nanoseconds != 0us && usedUnits != maxUnits)
    {
        formatted << nanoseconds.count() << " [us] ";
        ++usedUnits;
    }

    return formatted.str();
}

int main() {
    auto begin = std::chrono::system_clock::now();
    // algorithm goes here
    auto solution = "solution"; /* can be anything */
    auto end = std::chrono::system_clock::now();
    auto diff = end - begin;

    using namespace std::chrono_literals;
    std::cout << "Solution [" << solution << "] found in " << getTimeElapsed(1'234'567ns) << std::endl;

    return 0;
}

如果您想更进一步并且永远不需要逃避类型系统,那么我建议您查看Howard Hinnant's date 库。该库是 C++20 中新的 chrono 功能的基础,并将字符串格式化引入表中。只需以适合您的任何方式从库中包含date.h,然后修改getTimeElapsed,如下所示:

std::string getTimeElapsed(std::chrono::nanoseconds elapsed, size_t maxUnits = 2)
{
    using namespace std::chrono_literals;
    std::ostringstream formatted("");

    int usedUnits{};

    auto hours = std::chrono::duration_cast<std::chrono::hours>(elapsed);
    if (hours != 0h)
    {
        formatted << hours << " ";
        ++usedUnits;
    }

    auto minutes = std::chrono::duration_cast<std::chrono::minutes>(elapsed % 1h);
    if (minutes != 0min)
    {
        formatted << minutes << " ";
        ++usedUnits;
    }

    auto seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed % 1min);
    if (seconds != 0min && usedUnits != maxUnits)
    {
        formatted << seconds << " ";
        ++usedUnits;
    }

    auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed % 1s);
    if (milliseconds != 0ms && usedUnits != maxUnits)
    {
        formatted << milliseconds << " ";
        ++usedUnits;
    }

    auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(elapsed % 1ms);
    if (microseconds != 0us && usedUnits != maxUnits)
    {
        formatted << microseconds << " ";
        ++usedUnits;
    }

    auto nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed % 1us);
    if (nanoseconds != 0us && usedUnits != maxUnits)
    {
        formatted << nanoseconds << " ";
        ++usedUnits;
    }

    return formatted.str();
}

使用与以前相同的值,现在的结果将是:“1ms 234us” for 1'234'567ns。

【讨论】:

    猜你喜欢
    • 2016-07-20
    • 2018-02-03
    • 1970-01-01
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-28
    相关资源
    最近更新 更多