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

48

我正在尝试比较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中创建高分辨率计时器的方法吗?


请查看此问题:https://dev59.com/O0fRa4cB1Zd3GeqP5w-- - Steve-o
7个回答

67

请查看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;
}

8
原帖是用C语言发布的,但你的回答使用了C++。虽然有用,但我的ZedBoard上没有C++库 :D 为了解决这个问题,需要在timespec前加上struct,并去掉cout输出。 - Owl
14
回答明确提到了 CLOCK_REALTIMECLOCK_MONOTONIC,但代码示例中使用了 CLOCK_PROCESS_CPUTIME_ID?有人能澄清一下吗?哪个应该使用? - itMaxence
@itMaxence看看这个: https://dev59.com/oHA75IYBdhLWcg3wAEDx#3527632 - jplozier
我的观点是,如果在示例代码中最终使用了C部分,那么谈论A和B之间的差异就没有意义。(而不提及C部分) - itMaxence
根据这个链接的手册,CLOCK_PROCESS_CPUTIME_ID 衡量的是“该进程消耗的时间”。但根据使用情况,这可能并不是您想要的。如果进程由于阻塞系统调用或其他原因而停顿,则我也想测量它。 - jacwah
显示剩余2条评论

22

总结目前为止呈现的信息,这是典型应用所需的两个函数。

#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);

7
你是否忽略了 timespec 结构体中的 tv_sec 字段?还有,为什么使用 CLOCK_PROCESS_CPUTIME_ID 而不是 CLOCK_MONOTONIC - amaurea
海报正在比较CPU和GPU的性能。你正在诚实地给出获取CPU时间的代码。CLOCK_PROCESS_CPUTIME_ID。这意味着他将获得许多数量级的加速。对于CPU/GPU性能(此问题),始终使用墙上时间。删除此答案。 - TimZaman
1
@TimZaman 是的,在发布者的使用情况下,实时可能更好。不过我不会删除答案,显然人们已经发现它有用了。干杯。 - Alex
在使用CLOCK_PROGRESS_CPUTIME_ID之前,您应该运行grep constant_tsc /proc/cpuinfo来了解此时钟的工作原理。如果您的CPU不支持constant_tsc,则时间反映实际的CPU时钟周期。如果设置了标志,则时钟会根据当前CPU频率进行调整。我给这个评分为-1,因为time_elapsed_nanos计算不正确。这里可能是一个更好的方法。 - fredk

1
struct timespec t;
clock_gettime(CLOCK_REALTIME, &t);

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


我不确定 CLOCK_REALTIME_HR 是否被支持。问题 - gsamaras

1
你对墙上时间(实际流逝的时间)还是周期计数(多少个周期)感兴趣?如果是前者,你应该使用类似于 gettimeofday 的东西。
最高分辨率计时器使用 RDTSC x86汇编指令。但是,这会测量时钟滴答声,因此您应确保禁用节能模式。
TSC的维基页面提供了一些示例: http://en.wikipedia.org/wiki/Time_Stamp_Counter

在现代CPU上,rdtsc与挂钟时间呈1:1的相关性,而不是核心时钟周期。它不会在进程(或整个CPU)休眠时暂停,并且以恒定频率运行,无论是Turbo /省电。使用性能计数器来测量实际的核心时钟周期。例如:perf stat awk 'BEGIN {for (i=0 ; i<10000000; i++){}}' - Peter Cordes
我实际上对墙上时间很感兴趣。你的回复正中要害! - radato
您能否将您的回复链接到我的原始评论上? - radato

1

在阅读了这个帖子之后,我开始测试使用 clock_gettime 的代码与 C++11 的 chrono,它们似乎不匹配。

它们之间存在很大的差距!

std::chrono::seconds(1) 似乎等同于 ~70,000clock_gettime

#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;是一种类型,你的意思是在diff函数中使用--temp.tv_sec;。 - Simone-Cu
这不是一种类型,当我减去两个结构体时,我考虑到可能会有进位。 - radato
是的,我明白。但是当你从秒到纳秒进位时,应该将秒字段减去1,并将1000000000(1秒)加到纳秒字段中。 假设 (10秒和900纳秒) - (5秒和1000纳秒) --> 5秒和-100纳秒 --> 4秒和(-100+10^9)纳秒。最后一步减少了秒数,因此进行了进位。 - Simone-Cu
是的,正确的,我已经相应地修正了答案。 - radato

0

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);

0

clock_gettime(2)

clock_gettime(2)

是一个与时间相关的函数,它可以用来获取系统时钟的时间。这个函数在编写高精度计时器和性能测试工具时非常有用。

clock_gettime 更可取,因为它可以获取纳秒级别的时间。 - Dirk Eddelbuettel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接