繁忙的循环减慢了延迟关键计算

Busy loop slows down latency-critical computation

我的代码执行以下操作:

  1. 做一些长时间的运行密集计算(下面称为无用
  2. 做一个小的延迟关键任务

我发现使用长 运行 计算执行延迟关键任务所需的时间比没有长计算要长。

下面是一些重现此效果的独立 C++ 代码:

    #include <stdio.h>
    #include <stdint.h>

    #define LEN 128
    #define USELESS 1000000000
    //#define USELESS 0

    // Read timestamp counter
    static inline long long get_cycles()
    {
            unsigned low, high;
            unsigned long long val;
            asm volatile ("rdtsc" : "=a" (low), "=d" (high));
            val = high;
            val = (val << 32) | low;
            return val;
    }

    // Compute a simple hash
    static inline uint32_t hash(uint32_t *arr, int n)
    {
            uint32_t ret = 0;
            for(int i = 0; i < n; i++) {
                    ret = (ret + (324723947 + arr[i])) ^ 93485734985;
            }
            return ret;
    }

    int main()
    {
            uint32_t sum = 0;       // For adding dependencies
            uint32_t arr[LEN];      // We'll compute the hash of this array

            for(int iter = 0; iter < 3; iter++) {
                    // Create a new array to hash for this iteration
                    for(int i = 0; i < LEN; i++) {
                            arr[i] = (iter + i);
                    }

                    // Do intense computation
                    for(int useless = 0; useless < USELESS; useless++) {
                            sum += (sum + useless) * (sum + useless);
                    }

                    // Do the latency-critical task
                    long long start_cycles = get_cycles() + (sum & 1);
                    sum += hash(arr, LEN);
                    long long end_cycles = get_cycles() + (sum & 1);

                    printf("Iteration %d cycles: %lld\n", iter, end_cycles - start_cycles);
            }
    }

当使用 -O3 编译且 USELESS 设置为 10 亿时,三个迭代分别花费了 588、4184 和 536 个周期。在 USELESS 设置为 0 的情况下进行编译时,迭代分别需要 394、358 和 362 个周期。

为什么会发生这种情况(尤其是 4184 个周期)?我怀疑由密集计算引起的缓存未命中或分支预测错误。然而,如果没有密集的计算,延迟关键任务的第零次迭代非常快,所以我不认为冷 cache/branch 预测器是原因。

将我的推测性评论移至答案:

有可能当你的忙循环是运行时,服务器上的其他任务正在将缓存的arr数据从L1缓存中推出,所以[=中的第一个内存访问11=] 需要从较低级别的缓存中重新加载。如果没有计算循环,这不会发生。您可以尝试将 arr 初始化移动到计算循环之后,看看效果如何。