【问题标题】:Measure the CPU cycles of C++ code测量 C++ 代码的 CPU 周期
【发布时间】:2015-05-12 16:38:02
【问题描述】:

我的目标是使用简单的代码来衡量(不同的)缓存的效果。我正在关注这篇文章,特别是第 20 页和第 21 页: https://people.freebsd.org/~lstewart/articles/cpumemory.pdf

我正在开发 64 位 Linux。 L1d缓存为32K,L2为256K,L3为25M。

这是我的代码(我用 g++ 编译这段代码,没有标志):

#include <iostream>

// ***********************************
// This is for measuring CPU clocks
#if defined(__i386__)
static __inline__ unsigned long long rdtsc(void)
{
    unsigned long long int x;
    __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
    return x;
}
#elif defined(__x86_64__)
static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
#endif
// ***********************************


static const int ARRAY_SIZE = 100;

struct MyStruct {
    struct MyStruct *n;
};

int main() {
    MyStruct myS[ARRAY_SIZE];
    unsigned long long cpu_checkpoint_start, cpu_checkpoint_finish;

    //  Initializing the array of structs, each element pointing to the next 
    for (int i=0; i < ARRAY_SIZE - 1; i++){
        myS[i].n = &myS[i + 1];
        for (int j = 0; j < NPAD; j++)
            myS[i].pad[j] = (long int) i;
    }
    myS[ARRAY_SIZE - 1].n = NULL;   // the last one
    for (int j = 0; j < NPAD; j++)
        myS[ARRAY_SIZE - 1].pad[j] = (long int) (ARRAY_SIZE - 1);

    // Filling the cache
    MyStruct *current = &myS[0];
    while ((current = current->n) != NULL)
        ;

    // Sequential access
    current = &myS[0];

    // For CPU usage in terms of clocks (ticks)
    cpu_start = rdtsc();

    while ((current = current->n) != NULL)
        ;

    cpu_finish = rdtsc();

    unsigned long long avg_cpu_clocks = (cpu_finish - cpu_start) / ARRAY_SIZE;

    std::cout << "Avg CPU Clocks:   " << avg_cpu_clocks << std::endl;
    return 0;
}

我有两个问题:

1- 我将 ARRAY_SIZE 从 1 更改为 1,000,000(因此我的数组大小在 2B 到 2MB 之间),但平均 CPU 时钟始终为​​ 10。

根据该 PDF(第 21 页的图 3-10),当阵列完全适合 L1 时,我预计会获得 3-5 个时钟,而当它超过 L1 的大小时,我会获得更高的数字(9 个周期)。

2- 如果我将 ARRAY_SIZE 增加到 1,000,000 以上,我会得到分段错误(核心转储),这是由于堆栈溢出。我的问题是使用动态分配 (MyStruct *myS = new MyStruct[ARRAY_SIZE]) 是否不会导致任何性能损失。

【问题讨论】:

  • 您需要让您的编译器针对基准测试进行优化。所以用g++ -O2 -Wall -mtune=native编译;不要使用rdtsc,而是阅读time(7)
  • @BasileStarynkevitch 我使用了这些标志,现在平均 cpu 时钟为 4,仍然与数组的长度无关。这表明要么一切都适合 L1d(事实并非如此),要么预取做得很好(我怀疑)。
  • @BasileStarynkevitch 另外,你能告诉我为什么 rdtsc 不适合这个目的吗?
  • @narengi:rdtsc 不可靠,因为每个内核都不同。如果它检查一个核心,做数学运算,然后检查另一个核心,它可以报告它在 -100 滴答声中完成。
  • 对于 4 个时钟,您的 while 循环实际上什么都不做,因此编译器为您删除了它。基准测试很难。

标签: c++ x86 cpu-cache


【解决方案1】:

这是我的代码(我用 g++ 编译这段代码,没有任何标志)

如果不传递-O3,那么while ((current = current-&gt;n) != NULL) 将被编译为多个内存访问,而不是单个加载指令。通过传递-O3,循环将被编译成:

.L3:
mov     rax, QWORD PTR [rax]
test    rax, rax
jne     .L3

这将按照您的预期以每次迭代 4 个周期运行。

请注意,您可以使用 __rdtsc 编译器内在函数而不是内联汇编。请参阅:Get CPU cycle count?

【讨论】:

  • 感谢您的回复。你提到我的测量方法不可靠。我在代码中将 rdtsc 更改为 rdtscp,但仍然得到相同的结果。基准测试的正确方法是什么?我刚刚遇到了英特尔的 PCM 工具,但在使用它时遇到了一些问题。我肯定会对一个工具感兴趣,它给我缓存命中/未命中cnts,每条指令的时钟,......。你有任何工具/库吗?
  • @narengi 我已经更新了答案,以防你仍然感兴趣。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-10
  • 1970-01-01
  • 2011-04-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多