【问题标题】:Does standard C++11 guarantee that high_resolution_clock measure real time (non CPU-cycles)?标准 C++11 是否保证 high_resolution_clock 测量实时(非 CPU 周期)?
【发布时间】:2016-11-10 03:48:40
【问题描述】:

众所周知,clock() 可能会显示小于或大于实时值 - 这两种情况都显示在以下示例 1 和示例 2 中。

对于 C++11 中时间的高精度测量,我们可以使用:

  • std::chrono::high_resolution_clock::now(); - 保证高精度
  • std::chrono::steady_clock::now(); - 保证实时测量
  • clock(); - 保证高精度,但测量 CPU 周期而不是时间
  • time(&t_start); - 不是高精度,而是实时测量

1- 例如:http://ideone.com/SudWTM

#include <stdio.h>
#include <time.h>
#include <thread>
#include <iostream>
#include <chrono>

int main(void) {

    std::cout << "sleep(3) took: \n\n";

    clock_t c_start, c_end;
    time_t t_start, t_end;
    std::chrono::high_resolution_clock::time_point h_start, h_end;
    std::chrono::steady_clock::time_point steady_start, steady_end;

    time(&t_start);  // less precise than clock() but always get the real actual time
    c_start = clock(); // clock() get only CPU-time, it can be more than real or less - sleep(3); took 0.00 seconds 
    h_start = std::chrono::high_resolution_clock::now();
    steady_start = std::chrono::steady_clock::now(); 

    std::this_thread::sleep_for(std::chrono::seconds(3));

    steady_end = std::chrono::steady_clock::now();
    h_end = std::chrono::high_resolution_clock::now();
    c_end = clock();
    time(&t_end);

    std::cout << "highres = " << std::chrono::duration<double>(h_end - h_start).count() << " s \n";
    std::cout << "steady = " << std::chrono::duration<double>(steady_end - steady_start).count() << " s \n";

    printf("clock() = %.2lf seconds \n", (c_end - c_start) / (double)CLOCKS_PER_SEC);
    printf("time() = %.2lf seconds \n", difftime(t_end, t_start));

    return 0;
}

g++ (Debian 4.9.2-10) 4.9.2 上的结果:clock() = 0.00 秒

sleep(3) took: 

highres = 3.00098 s 
steady = 3.00098 s 
clock() = 0.00 seconds 
time() = 3.00 seconds 

在 C++ MSVS 2013 v120 (Windows 7x64) 上的结果:

sleep(3) took:

highres = 3.00017 s
steady = 3.00017 s
clock() = 3.00 seconds
time() = 3.00 seconds

2- 第二个示例 OpenMP 或 &lt;thread&gt;http://coliru.stacked-crooked.com/a/2922c85385d197e1

#include <stdio.h>
#include <time.h>
#include <thread>
#include <iostream>
#include <chrono>
#include <vector>

int main(void) {

    std::cout << "for-loop took: \n\n";

    clock_t c_start, c_end;
    time_t t_start, t_end;
    std::chrono::high_resolution_clock::time_point h_start, h_end;
    std::chrono::steady_clock::time_point steady_start, steady_end;

    time(&t_start);  // less precise than clock() but always get the real actual time
    c_start = clock(); // clock() get only CPU-time, it can be more than real or less - sleep(3); took 0.00 seconds 
    h_start = std::chrono::high_resolution_clock::now();
    steady_start = std::chrono::steady_clock::now();

    #pragma omp parallel num_threads(10)
    {
        for (volatile int i = 0; i < 200000000; ++i);
    }

    steady_end = std::chrono::steady_clock::now();
    h_end = std::chrono::high_resolution_clock::now();
    c_end = clock();
    time(&t_end);

    std::cout << "highres = " << std::chrono::duration<double>(h_end - h_start).count() << " s \n";
    std::cout << "steady = " << std::chrono::duration<double>(steady_end - steady_start).count() << " s \n";

    printf("clock() = %.2lf seconds \n", (c_end - c_start) / (double)CLOCKS_PER_SEC);
    printf("time() = %.2lf seconds \n", difftime(t_end, t_start));

    int b = getchar();

    return 0;
}

g++ (Debian 4.9.2-10) 4.9.2 上的结果:clock() = 1.35 秒

for-loop took: 

highres = 0.213906 s 
steady = 0.213905 s 
clock() = 1.35 seconds 
time() = 0.00 seconds 

在 C++ MSVS 2013 v120 (Windows 7x64) 上的结果:

for-loop took:

highres = 1.49109 s
steady = 1.49109 s
clock() = 1.49 seconds
time() = 2.00 seconds

简历:

  1. 当线程休眠时,g++ 4.9.2 上的 clock() 不像其他函数那样测量时间。

  2. 当我们通过 OpenMP 或 &lt;thread&gt; (link) 使用多线程时,g++ 4.9.2 上的 clock() 会测量所有线程的 CPU 周期。

同样在 Windows MSVS 2013 clock() 在这两种情况下都需要实时测量,但这并不能保证 clock() 在其他平台上测量相同(在 linux 上,g++ 为 0 表示睡眠,x-fold 表示多线程)。

基于此,如果std::chrono::high_resolution_clock::now(); 在 Windows MSVS 2013 和 g++ 4.9.2 两种情况下都需要实时测量,这是否保证它会在所有其他平台上测量真正的高分辨率时间,是否保证标准C++11/14?

【问题讨论】:

  • MSVC 2012 和 MSVC 2013 有一个 std::chrono::high_resolution_clock 的已知错误,它使用系统时间,因此它只有 1 毫秒的分辨率。这已在 MSVC 2015 中修复。

标签: c++ multithreading performance c++11 performance-testing


【解决方案1】:

该标准并未从其时钟中指定此行为。不完全是。

时钟具有is_steady 静态属性,可以检查。 is_steady 为其返回 true 的任何时钟不能是那种仅仅因为你让线程进入睡眠状态而停止运行的时钟。但是,该值为假的时钟可能由于各种原因而不稳定。它可能不稳定,因为它是一个挂钟,如果系统时间改变,它就会改变。或者因为刻度之间的周期是一个平均值,而不是一个确切的数字。

所以is_steady 并没有真正回答你的问题。

标准没有指定high_resolution_clock::is_steady,但它确实需要实现来回答这个问题。如果它是稳定的,那么您可以保证休眠线程不会停止时钟。但如果它不稳定……你根本得不到任何保证。

【讨论】:

  • 谢谢! IE。如果我需要跨平台的时间测量必须稳定,最好是准确的,我需要使用这样的东西:typedef std::conditional&lt;std::chrono::high_resolution_clock::is_steady, std::chrono::high_resolution_clock, std::chrono::steady_clock &gt;::type maxres_steady_clock;
  • @Alex:如果你需要跨平台的稳定时间测量,你应该使用steady_clock。如果您需要跨平台时间测量以提供尽可能高的分辨率,那么您应该使用high_resolution_clock。如果后者不稳定,那可能是因为平台无法在不丢失分辨率的情况下使其稳定。那么……哪个对你更重要?请记住:缺少is_steady 并不意味着它会因为你暂停线程而停止。 is_steady 不是您所问问题的答案。
  • 谢谢,我明白了。但是除了is_steady之外,现在还有什么可以保证它不会因为你暂停线程而停止?而且我不明白如果 HRC 测量具有高分辨率的东西,我可以使用什么,但不知道是什么(不是物理时间),或者这个值是否保证可重复独立于 CPU 时钟上升/下降,CPU 停顿,进程中的线程数(如clock()),...我可以将它用作代码的短单线程部分的保证可重复性能计数器吗?
  • @Alex:您可以只使用 HRC 而不必担心。实现并不愚蠢,也不是故意试图破坏您的代码。仅仅因为标准不能保证某些东西并不意味着您不能合理地依赖它。你依赖复制省略,对吗?这是一个实施质量特性。依赖 HRC 的合理行为作为 QOI 特征有什么问题?
  • 希望HRC的实现永远不如clock()的实现。
【解决方案2】:

简短回答:根据 C++14 标准,high_resolution_clock 并未明确提供您正在寻找的保证。

目前,steady_clocksystem_clock 提供了更好、更明确的保证。然而,大多数实现可能会确保 HRC 在其线程处于休眠状态时前进。尽管如此,最好还是自己做类型别名。请参阅下面的“编辑”部分和 cmets 中的讨论。

长答案:

draft standard 实际上隐含地承认(在注释 30.2.4“时序规范”,注释 5 中)时钟对象不需要在其关联线程处于休眠状态时前进。对于上下文,本节将解释标准库计时器对象的工作原理;计时器的行为基于用于设置它的时钟的行为。

[ 注意: 如果时钟与稳定时钟不同步,例如,a CPU 时钟,这些超时可能无法提供有用的功能。 — 尾注 ]

请注意,在这种情况下,“超时可能无法提供有用的功能”意味着如果您使用计时器来sleep_until 特定时钟时间使用非同步(非实时)时钟,您的线程不会唤醒。所以上面的注释有点轻描淡写。

事实上,时钟规范 (20.13.3) 中没有任何内容实际上需要与稳定时钟同步。

但是,该标准似乎在 20.13.7.3 的定义中隐含地宽恕了 high_resolution_clock 的两个潜在别名:

high_resolution_clock 可能是 system_clock 的同义词或 steady_clock.

steady_clock 当然是稳定的。 system_clock不是,因为在程序运行时系统时间可能会发生变化(例如,由于 NTP 更新)。

但是,system_clock (20.13.7.1) 仍然是“实时”时钟:

system_clock 类的对象表示从 系统范围的实时时钟。

所以system_clock 不会在您的线程休眠时停止前进。 这证实了 Nicol Bolas 的观点,即 high_resolution_clockis_steady 可能为 false,即使时钟按您预期的方式运行(即,无论其关联线程的状态如何,它都会前进)。

基于此,期望大多数主流实现为high_resolution_clock 使用某种实时(即同步)时钟似乎是合理的。毕竟,实现被设计成有用的,如果时钟不是实时的,则通常不太有用,特别是如果按照上面关于“有用的功能”的说明,它与计时器一起使用。

因为它不是保证,但是,您应该检查您要使用的每个实现的行为和/或文档。

编辑:我已经针对这个问题发起了discussion on the ISO C++ Standards group,表明这是标准中的一个错误。霍华德·欣南特的第一个回复值得引用,他将其纳入标准中,值得一提:

我不反对弃用 high_resolution_clock,并打算在适当的弃用期后将其删除。现实情况是它始终是steady_clocksystem_clock 的typedef,程序员最好选择这两者中的一个并知道他得到了什么,而不是选择high_resolution_clock 并通过一卷得到其他时钟骰子。

...因此,根据 Hinnant 的说法,道德是不要使用high_resolution_clock

编辑 2:

根据 Hinnant 的说法,high_resolution_clock 的问题并不是你可能会遇到 HRC 的问题(尽管根据参数,即使使用符合要求的编译器 也是可能的上面),但是由于您实际上通常不会获得比使用其他两个时钟之一更低的分辨率(尽管您需要在 type-alias 或 typedef 中手动比较它们的分辨率以获得“最大值分辨率“非睡眠时钟),没有具体的好处。因此,您需要权衡让线程在符合标准的实现上永远休眠的风险与名称 high_resolution_clock 的语义优势以及避免仅创建自己的 typedef 或 type-alias 的简单/简洁优势。

以下是各种方法的一些实际代码:

  • 使用static_assert检查high_resolution_clock 是否实际上是真实时钟的别名。这可能永远不会触发,这意味着您将自动获得最高分辨率的“实时”时钟,而不会弄乱您自己的 typedef:

     static_assert(
          std::is_same<high_resolution_clock, steady_clock>::value
       || std::is_same<high_resolution_clock, system_clock>::value,
       "high_resolution_clock IS NOT aliased to one of the other standard clocks!");
    
  • 如果high_resolution_clock::is_steady 为真,则使用 HRC;否则更喜欢system_clocksteady_clock 之间的更高分辨率时钟。 注意,如果high_resolution_clock::is_steady 为假,这可能只是意味着 HRC 别名为 system_clock,在这种情况下,您最终会得到一个新类型-alias 实际上与high_resolution_clock 的类型相同。但是,创建您自己的类型别名会明确这一点,并保证即使是恶意但符合规范的实现也不会出现上述问题。

    using maxres_sys_or_steady =
        std::conditional<
            system_clock::period::den <= steady_clock::period::den,
            system_clock, steady_clock
          >::type;
    using maxres_nonsleeping_clock =
        std::conditional<
            high_resolution_clock::is_steady,
            high_resolution_clock, maxres_sys_or_steady
          >::type;
    

【讨论】:

  • 谢谢! IE。时钟对多线程程序有用,它们必须是稳定的,并尽可能提高分辨率。然后我应该使用这样的东西:typedef std::conditional&lt;std::chrono::high_resolution_clock::is_steady, std::chrono::high_resolution_clock, std::chrono::steady_clock &gt;::type maxres_steady_clock;ideone.com/uqSyZY 另外,如果我想计算所有线程在此进程中花费的时间coliru.stacked-crooked.com/a/2922c85385d197e1,那么我可以在 gcc(linux_x86)上使用clock(),但不能在 MSVS(Windows )。不是吗?
  • 如果有一个标准的 CPU 周期消耗函数 - 即特定代码的资源消耗 - 它是可重复的值(除了使用超线程,如果同一核心上的第二个线程)占用相同的 ALU 或端口habrastorage.org/files/b7c/1f3/f80/… ),但此测量排除(1)其他线程的影响,这些线程占用核心并取代我们的线程,也排除(2)节流效应或加速时由(3)涡轮加速提升。
  • 我不在乎 Howard Hinnant 是否设计了这个库;他的“道德”是错误的。如果他认为 HRC 是个坏主意,那他就错了。 HRC 作为一种独特类型的用处是显而易见的。
  • @NicolBolas 确实:我完全同意你对 Hinnant 的回应。但是,如果“在实践中”high_resolution_clock当前在几乎所有实现中都别名为 steady_clocksystem_clock,那么 Hinnant 是正确的,现在最好只需明确选择一个。因此,如果 high_resolution_clock 的规范在 17 标准中发生变化,我会更新我的答案。
  • @KyleStrand, @NicolBolas:这是&lt;chrono&gt; 时钟实施调查:howardhinnant.github.io/clock_survey.html
猜你喜欢
  • 2016-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多