扩展std::chrono功能以处理运行时(非编译时)常量周期

extending the std::chrono functionality to deal with run-time (non compile-time) constant periods

本文关键字:编译 常量 周期 运行时 chrono std 功能 处理 扩展      更新时间:2023-10-16

我一直在Linux和OSX上试用各种定时器,并希望尝试用std::chrono使用的相同接口封装其中一些定时器。

对于在编译时具有定义明确的"周期"的计时器来说,这很容易做到,例如POSIX clock_gettime()系列、OSX上的clock_get_time()系列或gettimeofday()。

然而,也有一些有用的计时器,其"周期"(虽然是常数)仅在运行时才知道。例如:-POSIX表示clock()的周期CLOCKS_PER_SEC在非XSI系统上可能是一个变量-在Linux上,时间段()在运行时由sysconf(_SC_CLK_TCK)给定-在OSX上,mach_absolute_time()的周期在运行时由mach_timebase_info()给定-在最近的英特尔处理器上,DST寄存器以恒定的速率滴答作响,但当然这只能在运行时中确定

要将这些计时器封装在std::chrono接口中,一种可能性是使用std::chrono::纳秒的周期,并将每个计时器的值转换为纳秒。另一种方法可以是使用浮点表示。然而,这两种方法都会给now()函数带来(非常小的)开销,以及(可能很小的)精度损失。

我试图寻求的解决方案是定义一组类来表示这样的"运行时常量"周期,这些类与std::ratio类一样构建。然而,我预计这将需要重写所有相关的模板类和函数(因为它们假定constexpr值)。

我该如何将这些计时器包装成la std:chrono?

或者在时钟的时间段中使用非常量表达式值?

有人有包装这种计时器的经验吗la std:chrono?

事实上我知道。在OSX上,你感兴趣的平台之一。:-)

你提到:

在OSX上,mach_absolute_time()的周期在运行时由mach_timebase_info()

绝对正确。同样在OSX上,high_resolution_clocksteady_clock的libc++实现实际上是基于mach_absolute_time的。我是这段代码的作者,它是开源的,有着丰厚的许可证(只要你保留版权,就可以用它做任何你想做的事情)。

以下是libc++的steady_clock::now()的源代码。它的建造方式和你预想的差不多。在返回之前,运行时间段将转换为纳秒。在OSX上,转换因子通常为1,代码利用这一事实进行优化。然而,该代码足够通用,可以处理非1转换因子。

在对now()的第一次调用中,将运行时转换因子查询为纳秒的开销很小。在一般情况下,计算浮点转换因子。在常见情况下(转换因子==1),后续开销是通过函数指针进行调用。我发现开销确实很合理。

在OSX上,转换因子虽然直到运行时才确定,但仍然是一个常数(即不会随着程序的执行而变化),因此只需要计算一次。

如果您所处的时期实际上是动态变化的,您将需要更多的基础设施来处理这一问题。本质上,你需要积分(微积分)周期与时间的曲线,然后计算两个时间点之间的平均周期。这需要不断监测周期随时间的变化,而<chrono>不是合适的工具。此类工具通常在操作系统级别进行处理。

[有人有经验吗]或者在时钟的时间段内使用非constexpr值?

在阅读了标准(20.11.5,类模板持续时间)后,"周期"预计将是"比率的专业化":

备注:如果周期不是比例的专业化,则该程序是不正规的。

所有chrono模板都严重依赖constexpr功能。

有人有把这种计时器包装成la std:chrono的经验吗?

我在这里发现了一个建议,使用period=1的持续时间,boost::rational作为rep,尽管没有任何具体的例子。

出于我的目的,我也做了类似的事情,只是针对Linux。你可以在这里找到代码;您可以随意使用任何方式的代码。

我的实施所涉及的挑战与你的问题中提到的挑战部分重叠。具体而言:

  • 节拍因子(从时钟节拍转换为基于秒的时间单位所需)在运行时检索,但仅使用第一次now()&ddagger。如果您担心这会导致较小的开销,那么在测量任何实际间隔之前,可以在启动时调用now()函数一次。刻度因子存储在一个静态变量中,这意味着仍有一些开销,因为–在最低级别–CCD_ 9函数的每次调用都意味着检查静态变量是否已经初始化。然而,在now()的每个调用中,这种开销将是相同的,因此它不应该影响测量时间间隔。

  • 默认情况下,我不会转换为纳秒,因为当测量相对较长的时间段(例如几秒钟)时,这会导致溢出非常快。这实际上是我不使用boost实现的主要原因。我没有转换为纳秒,而是将基本单元实现为模板参数(代码中称为Precision)。我使用C++11中的std::ratio作为模板参数。因此,例如,我可以选择clock<micro>,这意味着调用now()函数将在内部转换为微秒而不是纳秒,这意味着您可以在没有溢出的情况下测量数秒或数分钟的周期,并且仍然具有良好的精度。(这与用于产生输出的单位无关。您可以使用clock<micro>并以秒等单位显示结果。)

  • 我的时钟类型被称为combined_clock,它结合了用户时间、系统时间和挂钟时间。也有一种升压时钟类型,但它与std中的比率类型和单位不兼容,而我的是。

&d达格刻度因子是使用您建议的::sysconf()调用检索的,并且保证在整个过程的生命周期内返回一个相同的值。

因此,您使用它的方式如下:

#include "util/proctime.hpp"
#include <ratio>
#include <chrono>
#include <thread>
#include <utility>
#include <iostream>
int main()
{
  using std::chrono::duration_cast;
  using millisec   = std::chrono::milliseconds;
  using clock_type = rlxutil::combined_clock<std::micro>;
  auto tp1 = clock_type::now();
  /* Perform some random calculations. */
  unsigned long step1 = 1;
  unsigned long step2 = 1;
  for (int i = 0 ; i < 50000000 ; ++i) {
    unsigned long step3 = step1 + step2;
    std::swap(step1,step2);
    std::swap(step2,step3);
  }
  /* Sleep for a while (this adds to real time, but not CPU time). */
  std::this_thread::sleep_for(millisec(1000));
  auto tp2 = clock_type::now();
  std::cout << "Elapsed time: "
            << duration_cast<millisec>(tp2 - tp1)
            << std::endl;
  return 0;
}

上面的用法涉及一个漂亮的打印功能,它生成的输出如下:

Elapsed time: [user 40, system 0, real 1070 millisec]