如何在 Linux 中创建高分辨率计时器来测量程序性能?

Posted

技术标签:

【中文标题】如何在 Linux 中创建高分辨率计时器来测量程序性能?【英文标题】:How to create a high resolution timer in Linux to measure program performance? 【发布时间】:2011-07-19 15:20:24 【问题描述】:

我正在尝试比较 GPU 和 CPU 的性能。对于 NVIDIA GPU,我一直在使用 cudaEvent_t 类型来获得非常精确的计时。

对于 CPU,我一直在使用以下代码:

// Timers
clock_t start, stop;
float elapsedTime = 0;

// Capture the start time

start = clock();

// Do something here
.......

// Capture the stop time
stop = clock();
// Retrieve time elapsed in milliseconds
elapsedTime = (float)(stop - start) / (float)CLOCKS_PER_SEC * 1000.0f;

显然,仅当您以秒为单位计算时,该段代码才有效。而且,有时结果很奇怪。

有人知道在 Linux 中创建高分辨率计时器的方法吗?

【问题讨论】:

看到这个问题:***.com/questions/700392/… 【参考方案1】:

查看clock_gettime,这是高分辨率计时器的 POSIX 接口。

如果在阅读手册页后,您对CLOCK_REALTIMECLOCK_MONOTONIC 之间的区别感到疑惑,请参阅Difference between CLOCK_REALTIME and CLOCK_MONOTONIC?

完整示例请参见以下页面:http://www.guyrutenberg.com/2007/09/22/profiling-code-using-clock_gettime/

#include <iostream>
#include <time.h>
using namespace std;

timespec diff(timespec start, timespec end);

int main()

    timespec time1, time2;
    int temp;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time1);
    for (int i = 0; i< 242000000; i++)
        temp+=temp;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time2);
    cout<<diff(time1,time2).tv_sec<<":"<<diff(time1,time2).tv_nsec<<endl;
    return 0;


timespec diff(timespec start, timespec end)

    timespec temp;
    if ((end.tv_nsec-start.tv_nsec)<0) 
        temp.tv_sec = end.tv_sec-start.tv_sec-1;
        temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
     else 
        temp.tv_sec = end.tv_sec-start.tv_sec;
        temp.tv_nsec = end.tv_nsec-start.tv_nsec;
    
    return temp;

【讨论】:

只是为了让我清楚我所读到的内容,你能给我举个例子,说明如何使用 clock_gettime 来查找以纳秒为单位的经过时间吗? @seljuq70:我添加了一个完整示例的链接。 OP 已发布 C,但您的答案是 C++。仍然有用,但在我没有 C++ 库的 ZedBoard 上没有:D 要修复,请在 timespec 前面加上 struct 并去掉 couts。 所以答案明确谈到了CLOCK_REALTIMECLOCK_MONOTONIC,但我们最终在代码示例中得到CLOCK_PROCESS_CPUTIME_ID?有人可以解决这个问题吗?有什么要走的? @itMaxence 看看这个:***.com/a/3527632/9732482【参考方案2】:

总结目前提供的信息,这是典型应用程序所需的两个功能。

#include <time.h>

// call this function to start a nanosecond-resolution timer
struct timespec timer_start()
    struct timespec start_time;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time);
    return start_time;


// call this function to end a timer, returning nanoseconds elapsed as a long
long timer_end(struct timespec start_time)
    struct timespec end_time;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time);
    long diffInNanos = (end_time.tv_sec - start_time.tv_sec) * (long)1e9 + (end_time.tv_nsec - start_time.tv_nsec);
    return diffInNanos;

这是一个示例,说明如何使用它们来计时计算输入列表的方差所需的时间。

struct timespec vartime = timer_start();  // begin a timer called 'vartime'
double variance = var(input, MAXLEN);  // perform the task we want to time
long time_elapsed_nanos = timer_end(vartime);
printf("Variance = %f, Time taken (nanoseconds): %ld\n", variance, time_elapsed_nanos);

【讨论】:

你不是忽略了timespectv_sec吗?另外,为什么是CLOCK_PROCESS_CPUTIME_ID 而不是CLOCK_MONOTONIC 海报正在比较 CPU 和 GPU 的性能。老实说,您正在为代码提供 CPU 时间。 CLOCK_PROCESS_CPUTIME_ID。这意味着他将获得许多数量级的加速。对于 CPU/GPU 性能(这个问题),请始终使用挂墙时间。删除此答案。 @TimZaman 是的,在海报的用例中,实时可能会更好。不过,我不会删除答案,显然人们发现它很有用。干杯。 在使用 CLOCK_PROGRESS_CPUTIME_ID 之前,您应该运行 grep constant_tsc /proc/cpuinfo 以了解此时钟的工作原理。如果您的 CPU 不支持 constant_tsc,则时间反映了实际的 CPU 时钟周期。如果设置了标志,则调整时钟以考虑当前 CPU 频率。我给这个 -1 因为 time_elapsed_nanos 计算不正确。 This 可能是更好的方法。【参考方案3】:
struct timespec t;
clock_gettime(CLOCK_REALTIME, &t);

还有 CLOCK_REALTIME_HR,但我不确定它是否有任何区别..

【讨论】:

我不确定是否支持CLOCK_REALTIME_HR。 Question.【参考方案4】:

您是否对挂墙时间(实际经过了多少时间)或周期数(多少个周期)感兴趣?在第一种情况下,您应该使用 gettimeofday 之类的东西。

最高分辨率计时器使用RDTSC x86 汇编指令。但是,这会测量时钟滴答声,因此您应该确保已禁用省电模式。

TSC 的 wiki 页面提供了一些示例:http://en.wikipedia.org/wiki/Time_Stamp_Counter

【讨论】:

在现代 CPU 上,rdtsc 将 1:1 与挂钟时间相关联,而不是核心时钟周期。当您的进程(或整个 CPU)处于睡眠状态时,它不会暂停,并且它以恒定频率运行,无论涡轮/省电如何。使用性能计数器来测量实际的核心时钟周期。例如perf stat awk 'BEGIN for (i=0 ; i&lt;10000000; i++)'. 我其实对墙上时间很感兴趣。您的回复恰到好处! 是否可以将您的回复链接到我的原始评论?【参考方案5】:

阅读完这个帖子后,我开始针对 c++11 的 chrono 测试 clock_gettime 的代码,但它们似乎不匹配。

他们之间有很大的差距!

std::chrono::seconds(1) 似乎相当于 clock_gettime

~70,000
#include <ctime>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <iomanip>
#include <vector>
#include <mutex>

timespec diff(timespec start, timespec end);
timespec get_cpu_now_time();
std::vector<timespec> get_start_end_pairs();
std::vector<timespec> get_start_end_pairs2();
void output_deltas(const std::vector<timespec> &start_end_pairs);

//=============================================================
int main()

    std::cout << "Hello waiter" << std::endl; // flush is intentional
    std::vector<timespec> start_end_pairs = get_start_end_pairs2();
    output_deltas(start_end_pairs);

    return EXIT_SUCCESS;


//=============================================================
std::vector<timespec> get_start_end_pairs()

    std::vector<timespec> start_end_pairs;
    for (int i = 0; i < 20; ++i)
    
        start_end_pairs.push_back(get_cpu_now_time());
        std::this_thread::sleep_for(std::chrono::seconds(1));
        start_end_pairs.push_back(get_cpu_now_time());
    

    return start_end_pairs;



//=============================================================
std::vector<timespec> get_start_end_pairs2()

    std::mutex mu;
    std::vector<std::thread> workers;
    std::vector<timespec> start_end_pairs;
    for (int i = 0; i < 20; ++i) 
        workers.emplace_back([&]()->void 
            auto start_time = get_cpu_now_time();
            std::this_thread::sleep_for(std::chrono::seconds(1));
            auto end_time = get_cpu_now_time();
            std::lock_guard<std::mutex> locker(mu);
            start_end_pairs.emplace_back(start_time);
            start_end_pairs.emplace_back(end_time);
        );
    

    for (auto &worker: workers) 
        if (worker.joinable()) 
            worker.join();
        
    

    return start_end_pairs;


//=============================================================
void output_deltas(const std::vector<timespec> &start_end_pairs)

    std::cout << "size: " << start_end_pairs.size() << std::endl;
    for (auto it_start = start_end_pairs.begin(); it_start < start_end_pairs.end(); it_start += 2)
    
        auto it_end = it_start + 1;
        auto delta = diff(*it_start, *it_end);

        std::cout
                << std::setw(2)
                << std::setfill(' ')
                << std::distance(start_end_pairs.begin(), it_start) / 2
                << " Waited ("
                << delta.tv_sec
                << "\ts\t"
                << std::setw(9)
                << std::setfill('0')
                << delta.tv_nsec
                << "\tns)"
                << std::endl;
    


//=============================================================
timespec diff(timespec start, timespec end)

    timespec temp;
    temp.tv_sec = end.tv_sec-start.tv_sec;
    temp.tv_nsec = end.tv_nsec-start.tv_nsec;

    if (temp.tv_nsec < 0) 
        ++temp.tv_sec;
        temp.tv_nsec += 1000000000;
    
    return temp;


//=============================================================
timespec get_cpu_now_time()

    timespec now_time;
    memset(&now_time, 0, sizeof(timespec));
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &now_time);

    return now_time;

输出:

Hello waiter
 0 Waited (0    s       000843254       ns)
 1 Waited (0    s       000681141       ns)
 2 Waited (0    s       000685119       ns)
 3 Waited (0    s       000674252       ns)
 4 Waited (0    s       000714877       ns)
 5 Waited (0    s       000624202       ns)
 6 Waited (0    s       000746091       ns)
 7 Waited (0    s       000575267       ns)
 8 Waited (0    s       000860157       ns)
 9 Waited (0    s       000827479       ns)
10 Waited (0    s       000612959       ns)
11 Waited (0    s       000534818       ns)
12 Waited (0    s       000553728       ns)
13 Waited (0    s       000586501       ns)
14 Waited (0    s       000627116       ns)
15 Waited (0    s       000616725       ns)
16 Waited (0    s       000616507       ns)
17 Waited (0    s       000641251       ns)
18 Waited (0    s       000683380       ns)
19 Waited (0    s       000850205       ns)

【讨论】:

我猜是 ++temp.tv_sec;是一种类型,你的意思是 --temp.tv_sec;在 diff 函数中。 它不是一种类型,当我减去 2 个结构时,我考虑到可能存在 结转 是的,明白了。但是,当您从 sec 到 nsec 进行进位时,您应该在 seconds 字段中减去 1 并将 1000000000 (1s) 与 nsec 字段相加。假设(10s and 900ns) - (5s and 1000ns) --> 5s and -100ns --> 4s and (-100+10^9)ns。最后一步减少秒,从而进行进位。【参考方案6】:

clock_gettime(2)

【讨论】:

clock_gettime 更可取,因为它可以让您获得纳秒。【参考方案7】:

epoll 实现: https://github.com/ielife/simple-timer-for-c-language

这样使用:

timer_server_handle_t *timer_handle = timer_server_init(1024);
if (NULL == timer_handle) 
    fprintf(stderr, "timer_server_init failed\n");
    return -1;

ctimer timer1;
    timer1.count_ = 3;
    timer1.timer_internal_ = 0.5;
    timer1.timer_cb_ = timer_cb1;
    int *user_data1 = (int *)malloc(sizeof(int));
    *user_data1 = 100;
    timer1.user_data_ = user_data1;
    timer_server_addtimer(timer_handle, &timer1);

    ctimer timer2;
    timer2.count_ = -1;
    timer2.timer_internal_ = 0.5;
    timer2.timer_cb_ = timer_cb2;
    int *user_data2 = (int *)malloc(sizeof(int));
    *user_data2 = 10;
    timer2.user_data_ = user_data2;
    timer_server_addtimer(timer_handle, &timer2);

    sleep(10);

    timer_server_deltimer(timer_handle, timer1.fd);
    timer_server_deltimer(timer_handle, timer2.fd);
    timer_server_uninit(timer_handle);

【讨论】:

以上是关于如何在 Linux 中创建高分辨率计时器来测量程序性能?的主要内容,如果未能解决你的问题,请参考以下文章

reedlau高精度 高分辨率 计时函数 Linux

reedlau高精度 高分辨率 计时函数 Linux

SylixOS定时器测试误差分析

如何在JAVA中创建高优先级有界子队列和低优先级有界子队列

Linux计时体系结构

Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()