2

我正在评估以一致的时间间隔触发事件的繁忙等待循环的性能。我注意到使用以下代码的一些奇怪行为:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

int timespec_subtract(struct timespec *, struct timespec, struct timespec);

int main(int argc, char *argv[]) {
    int iterations = atoi(argv[1])+1;

    struct timespec t[2], diff;

    for (int i = 0; i < iterations; i++) {
        clock_gettime(CLOCK_MONOTONIC, &t[0]);

        static volatile int i;
        for (i = 0; i < 200000; i++)
            ;

        clock_gettime(CLOCK_MONOTONIC, &t[1]);

        timespec_subtract(&diff, t[1], t[0]);
        printf("%ld\n", diff.tv_sec * 1000000000 + diff.tv_nsec);
    }
}

在测试机上(双 14 核 E5-2683 v3 @ 2.00Ghz,256GB DDR4),for 循环的 200k 次迭代大约为 1ms。或者可能不是:

1030854
1060237
1012797
1011479
1025307
1017299
1011001
1038725
1017361
... (about 700 lines later)
638466
638546
638446
640422
638468
638457
638468
638398
638493
640242
... (about 200 lines later)
606460
607013
606449
608813
606542
606484
606990
606436
606491
606466
... (about 3000 lines later)
404367
404307
404309
404306
404270
404370
404280
404395
404342
406005

当时间第三次向下移动时,它们基本上保持一致(大约 2 或 3 微秒内),除了偶尔会跳到大约 450us 进行几百次迭代。这种行为在类似的机器上和多次运行中是可重复的。

我知道编译器可以优化繁忙的循环,但我认为这不是问题所在。我不认为缓存应该影响它,因为不应该发生失效,也不会解释突然的优化。我还尝试使用寄存器 int 作为循环计数器,但没有明显效果。

关于正在发生的事情以及如何使这(更)一致的任何想法?

编辑:有关信息,使用 usleep、nanosleep 运行此程序或显示的忙等待 10k 次迭代都显示 ~20000 次非自愿上下文切换time -v

4

2 回答 2

1

我会提出 2 点 - 由于上下文切换睡眠/睡眠可能比预期的睡眠时间更长 - 此外,如果有一些更高优先级的任务(如中断),可能会出现根本无法执行睡眠的情况。

因此,如果您想在应用程序中获得准确的延迟,您可以使用 gettimeofday 来计算可以从 sleep/usleep 调用的延迟中减去的时间间隔

于 2016-07-07T19:39:22.933 回答
1

忙等待的一个大问题是,除了耗尽 CPU 资源外,您等待的时间将高度依赖于 CPU 块速度。所以同一个循环可以在不同的机器上运行完全不同的时间。

任何睡眠方法的问题在于,由于操作系统调度,您最终可能会比预期的睡眠时间更长。手册页nanosleep说,rem如果您收到信号,它将使用参数告诉您剩余时间,但它没有说明等待时间过长。

您需要在每次通话后获取时间戳,usleep以便知道您实际睡了多长时间。如果你睡得太短,你就会增加赤字。如果你睡得太久,你减去超龄。

以下是我如何在UFTP(一个多播文件传输应用程序)中执行此操作的示例,以便以一致的速度发送数据包:

int64_t diff_usec(struct timeval t2, struct timeval t1)
{
    return (t2.tv_usec - t1.tv_usec) +
            (int64_t)1000000 * (t2.tv_sec - t1.tv_sec);
}

...

        int32_t packet_wait = 10000;
        int64_t overage = 0, tdiff;
        struct timeval current_sent, last_sent;

        gettimeofday(&last_sent, NULL);

        while(...) {
            ...

            if (packet_wait > overage) {
                usleep(packet_wait - (int32_t)overage);
            }
            gettimeofday(&current_sent, NULL);
            tdiff = diff_usec(current_sent, last_sent);
            overage += tdiff - packet_wait;

            last_sent = current_sent;
            ...
        }
于 2016-07-07T19:03:25.380 回答