【问题标题】:setting/pausing clock time for std::chrono clocks in C++在 C++ 中为 std::chrono 时钟设置/暂停时钟时间
【发布时间】:2019-01-30 10:12:11
【问题描述】:

我写了一些东西来测量我的代码运行和打印出来的时间。我现在拥有它的方式支持这些测量值的嵌套。

事情是,在获取时间间隔,将其转换为数字,获取时间格式,然后将所有内容转换为字符串,然后将其打印出来的过程中需要一段时间(2-3毫秒),我/O 似乎特别昂贵。我希望时钟在某种意义上“跳过”这个过程,因为我正在测量的东西会以微秒为单位。 (而且我认为这将是一个很好的功能,如果我还有其他想要跳过的内容)

std::chrono::high_resolution_clock clock;
std::chrono::time_point<std::chrono::steady_clock> first, second;

first = clock.now();

std::chrono::time_point<std::chrono::high_resolution_clock> paused_tp = clock.now();
std::cout << "Printing things \n";
clock.setTime(paused_tp); // Something like this is what I want

second = clock.now();

这个想法是确保firstsecond 有最小的差异,最好是相同的。

据我所知,high_resolution_clock 类(以及所有其他 chrono 时钟)保持其 time_point 私有,您只能从 clock.now() 访问它

我知道那里可能有执行此操作的基准测试库,但我想自己弄清楚如何做(如果只是为了知道如何做)。任何有关其他库如何做到这一点的信息或有关 chrono 如何工作的见解也将不胜感激。我可能误解了chrono 内部如何跟踪。

std::chrono::high_resolution_clock 对于这样的事情是否足够准确?)

(虽然我在这里,但任何有关使 C++ 程序更高效的资源都会很棒)

编辑:我实际上是在我尝试计时的代码部分之后进行打印,例如,当我想对整个程序以及单个函数计时时,才会出现问题。那么函数时间的打印会导致整个程序时间的延迟。

编辑 2:我想我应该有更多的例子来说明我在做什么。 我有一个可以处理所有事情的类,比如说它叫做tracker,它负责处理所有的时钟问题。

tracker loop = TRACK(
    for(int i = 0; i != 100; ++i){
        tracker b = TRACK(function_call());
        b.display();
    }
)
loop.display();

宏是可选的,它只是一个快速的东西,使它不那么混乱,并允许我显示被调用的函数的名称。

将宏显式扩展为

tracker loop = "for(int i = 0; i != 100; ++i){ tracker b = TRACK(function_call()); b.display(); }"
loop.start()
for(int i = 0; i != 100; ++i){
    tracker b = "function_call()"
    b.start();
    function_call();
    b.end();
    b.display();
}
loop.end();
loop.display();

在大多数情况下,打印不是问题,它只跟踪start()end() 之间的内容,但这里b.display() 最终会干扰tracker loop

我的一个目标是让跟踪器尽可能不打扰,所以我希望大部分/全部都在跟踪器类中处理。但后来我遇到了b.display() 是与tracker loop 不同实例的方法的问题。我用static 关键字尝试了一些事情,但遇到了一些问题(仍在尝试)。我可能在这里把自己编码到了死胡同,但还有很多事情要尝试。

【问题讨论】:

  • smth like: `std::chrono::time_point<:chrono::steady_clock> first2 = clock.now(); std::cout second2 = clock.now();第一个+=第二个2-第一个2; `

标签: c++ clock chrono benchmarking


【解决方案1】:

只需分别对两个时间间隔进行计时并将它们相加,即总共保存 4 个时间戳。对于嵌套间隔,您可能只需将时间戳保存到数组中并在最后对所有内容进行排序。 (或者在时间戳被覆盖之前的外部循环内)。存储到数组非常便宜。


或者更好:推迟打印。

如果定时间隔只有几毫秒,只需保存要打印的内容并在定时间隔之外执行。

如果您有嵌套的时间间隔,请至少将打印从最里面的时间间隔中移出,以最大限度地减少您必须执行的停止/重新启动量。

如果您在各处手动检测代码,不妨看看像火焰图这样的分析工具,尤其是当您的时间间隔在函数边界上分解时。 linux perf: how to interpret and find hotspots.


I/O 本身不仅需要时间,它还会使后面的代码运行速度变慢数百或数千个周期。 进行系统调用会涉及很多代码,所以当你返回时对于用户空间,您可能会得到指令缓存和数据缓存未命中。修改页表的系统调用也会导致 TLB 未命中。

(请参阅“系统调用的(实际)成本”部分in the FlexSC paper(Soares,Stumm),在运行 Linux 的 i7 Nehalem 上计时。(第一代 i7 从 ~2008/9)。该论文提出了一个批处理高吞吐量 Web 服务器和类似服务器的系统调用机制,但它们在普通 Linux 上的基线结果很有趣,并且除此之外相关。)

在启用了 Meltdown 缓解措施的现代 Intel CPU 上,您通常会遇到 TLB 未命中。在最近的 x86 上启用 Spectre 缓解后,分支预测历史可能会被清除,具体取决于缓解策略。 (英特尔为内核添加了一种方法,在此之后请求更高权限的分支不会受到低权限分支的预测历史的影响。在当前的 CPU 上,我认为这只会刷新分支预测缓存。)

您可以通过让iostream 为您缓冲来避免系统调用开销。格式化和复制数据仍然是一项重要的工作,但比写入终端便宜得多将程序的stdout 重定向到文件将使cout 默认为全缓冲,而不是行缓冲。 即像这样运行它:

 ./my_program > time_log.txt

最终输出将与您在终端上的输出相匹配,但是(只要您不做任何愚蠢的事情,例如使用 std::endl 强制刷新)它只会被缓冲。默认缓冲区大小可能类似于 4kiB。使用strace ./my_program 或类似的工具来跟踪系统调用,并确保最后得到一个大的write(),而不是很多小的write()s。

避免在(外部)定时区域内缓冲 I/O 会很好,但如果您正在计时到纳秒。即使在之前时间间隔也是如此,而不仅仅是在内部。

cout &lt;&lt; foo 如果它不进行系统调用,就减慢后面的代码而言并不是“特殊的”。

【讨论】:

  • 推迟打印会很麻烦,因为我有多个这样的实例,所以打印可能会在另一个间隔内发生,这实际上是唯一会出现问题的时间。实际上,我确实将打印推迟到以后,问题只出现在,比如说,当我想对整个程序以及个别功能计时。那么函数时间的打印会导致整个程序时间的延迟。您关于添加更多时间间隔的评论实际上给了我一个想法,所以我会尝试一下。
  • @ChilliDoughnuts:你的意思是你有嵌套的区间?将时间戳记录到数组中可能是一个好主意,因此您可以将所有打印和分析从定时区域中提取出来。如果您想要更多的全局配置文件,或者使用分析工具(例如硬件性能计数器)进行分析。见linux perf: how to interpret and find hotspots,其中包括flamegraph的截图
  • @ChilliDoughnuts:扩展了更多关于打印和计时的问题。
  • 感谢您的详细回复,我会尝试这些,尤其是iostream 的东西。我已经编辑了原始问题,以提供更多关于我所拥有内容的背景信息。所有这一切都是因为我必须让别人的 RnD 项目“更高效”(阅读 RnD 代码:\),这意味着代码库可能会发生巨大变化,所以我希望它尽可能准确、轻量和灵活.这基本上是我做这件事的动机。
【解决方案2】:

为了克服开销,延时打印可以由另一个线程完成。主线程将开始和结束时间保存到共享全局变量中,并通知condition variable打印线程正在等待。

#include<iostream>
#include<thread>
#include<chrono>
#include<mutex>
#include<condition_variable>
#include<atomic>

std::condition_variable cv;
std::mutex mu;
std::atomic<bool> running {true};
std::atomic<bool> printnow {false};

// shared but non-atomic: protected by the mutex and condition variable.
// possible bug: the main thread can write `now` before print_thread wakes up and reads it
std::chrono::high_resolution_clock::time_point start;
std::chrono::high_resolution_clock::time_point now;


void print_thread() {
    std::thread([]() {
        while (running) {
            std::unique_lock<std::mutex> lock(mu);
            cv.wait(lock, []() { return !running || printnow; });
            if (!running) return;
            std::chrono::milliseconds lapse_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
            printnow = false;
            std::cout << " lapse time " << lapse_ms.count() << "  ms\n";
        }
    }).detach();
}

void print_lapse(std::chrono::high_resolution_clock::time_point start1, std::chrono::high_resolution_clock::time_point now1) {
    start = start1;
    now = now1;
    printnow = true;
    cv.notify_one();
}

int main()
{
    //launch thread
    print_thread(); 

    // laspe1
    std::chrono::high_resolution_clock::time_point start1 = std::chrono::high_resolution_clock::now();
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    std::chrono::high_resolution_clock::time_point now1 = std::chrono::high_resolution_clock::now();
    print_lapse(start1,now1);

    // laspe2
    start1 = std::chrono::high_resolution_clock::now();
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    now1 = std::chrono::high_resolution_clock::now();
    print_lapse(start1, now1);

    //winding up
    std::this_thread::sleep_for(std::chrono::milliseconds(300));
    running = false;
    cv.notify_one();
}

【讨论】:

  • 这有一个竞争条件,我认为:主线程中的.notify_one() 可以在print_thread 读取start 之前返回,可能导致之前写入(或部分写入)一个新值旧的 val 被读取。这就是为什么您更改为仅设置本地,而不是设置 start 直到定时间隔之后?这在实践中可能适用于较长的时间间隔,但在理论上仍然不安全,并且在实践中可能适用于非常短的时间区域(只有数百到数千个时钟周期)。
  • cv.notify_one() 仍然需要进行系统调用,因此并非没有成本(请参阅我的回答)。不过,它比阻塞写入慢速终端要便宜得多,因此在某些情况下它可能是一种改进。尽管如此,我仍然犹豫不决/推荐这个作为手头问题的真正解决方案,而不是将时间戳保存在数组中以备后用。
  • @PeterCordes:同意推迟打印/分析的时间戳似乎是个好主意。
猜你喜欢
  • 2014-01-22
  • 1970-01-01
  • 2023-04-07
  • 2013-11-12
  • 1970-01-01
  • 2014-11-02
  • 2018-11-25
  • 1970-01-01
  • 2022-01-25
相关资源
最近更新 更多