在C ++

时间:2018-08-24 00:07:02

标签: c++ clock chrono benchmarking

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

问题是,在获取时间间隔,将其转换为数字,获取时间格式,然后将所有内容转换为字符串,然后将其打印出来需要一段时间(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()访问它< / p>

我知道那里可能有基准库可以做到这一点,但是我想自己弄清楚如何做到这一点(如果只是为了知道如何去做)。任何其他图书馆如何做到这一点的信息,或关于计时如何工作的见解也将不胜感激。我可能会误解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()最终会干扰{{1} }。

我的一个目标是使跟踪器尽可能不打扰,所以我希望大多数/全部在跟踪器类中进行处理。但是后来我遇到了一个问题:tracker loopb.display()是不同实例的方法。我已经使用tracker loop关键字尝试了一些操作,但是遇到了一些问题(仍然尝试了一些)。我可能在这里已经陷入困境,但是仍然有很多事情可以尝试。

2 个答案:

答案 0 :(得分:1)

只需将两个时间间隔分别计时并添加,即节省4个总时间戳。对于嵌套间隔,您可能只是将时间戳保存到数组中,然后将所有内容整理出来。 (或者在覆盖时间戳之前在外部循环中)。存储到阵列非常便宜。


或更佳:将打印推迟到以后。

如果定时间隔仅为毫秒,则只需保存要打印的内容,然后在定时间隔之外进行打印即可。

如果您有嵌套的定时间隔,请至少将打印从最里面的间隔中抽出,以最大程度地减少停止/重新启动的次数。

如果您要在各处手动检测代码,则可以看一下性能分析工具,例如Flamegraph,尤其是在定时间隔超出函数边界的情况下。 linux perf: how to interpret and find hotspots


I / O本身不仅花费时间,还会使以后的代码运行几百或数千个周期变慢。进行系统调用会涉及很多代码,因此当您返回时到用户空间,很可能会收到指令缓存和数据缓存未命中的情况。修改页表的系统调用也会导致TLB未命中。

(请参阅“系统调用的(实际)成本”一节in the FlexSC paper(Soares,Stumm),该时钟在运行Linux的i7 Nehalem上计时(〜2008 / 9的第一代i7)。高吞吐量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()

最好避免在(外部)定时区域内缓冲I / O,但是避免在您的“真实”(非仪表)代码没有它们的地方进行系统调用非常重要,计时到纳秒。即使在 时间间隔之前,也是如此,不仅仅是内部。

cout << foo如果不进行系统调用,就降低以后的代码速度而言,并不是“特别的”。

答案 1 :(得分:0)

为克服开销,可以通过另一个线程来完成延时打印。主线程将开始时间和结束时间保存到共享的全局变量中,并通知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();
}