繁忙等待循环的可变性能?

时间:2016-07-07 18:49:48

标签: c performance loops sleep busy-loop

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

#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的非自愿上下文切换。

2 个答案:

答案 0 :(得分: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;
            ...
        }

答案 1 :(得分:1)

我得2分 - 由于上下文开关睡眠/睡眠可能会比预期睡眠时间更长 - 此外,如果有一些像中断这样的优先级较高的任务,可能会出现根本无法执行睡眠的情况。

因此,如果您想要在应用程序中确切延迟,可以使用gettimeofday计算时间间隔,可以从睡眠延迟/ usleep调用中减去