扩展std::chrono功能以处理运行时(非编译时)常量周期
extending the std::chrono functionality to deal with run-time (non compile-time) constant periods
我一直在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_clock
和steady_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]
- C++常量数组的编译时间较长
- 在编译时将常量字符* 转换为常量 char_type*
- 将编译时常量向量转换为堆分配版本
- 如何在常量计算表达式中获取编译时错误?
- 如何在编译时解析静态常量 std::string?
- 有没有办法在类实例中存储编译时常量?
- 检测编译时文本和常量
- ARM C++编译器不会编译具有可变常量成员的结构
- 强制在编译时计算类的类的常量成员
- Constexpr 类采用常量引用不编译
- 为什么对数组的常量左值引用不编译在下面?
- 由于缺少常量而出现编译错误
- 为什么在将常量 ptr 分配给常量引用时没有收到编译错误?
- 为什么可以在编译时访问非常量、非静态成员而无需类的实例
- 不是 VS2017 中的编译时常量表达式
- 将编译时定义大小的数组初始化为常量表达式
- 将大小为编译时常量的数组初始化为单个值
- 如何为 std::array 声明全局编译时常量?
- 返回非常量引用编译的 Const 方法
- 常量正确性编译错误到模板函数中的无效转换错误