【问题标题】:clflush not giving correct answer in i7 for const data typeclflush 在 i7 中没有给出 const 数据类型的正确答案
【发布时间】:2014-03-01 18:08:36
【问题描述】:

我编写了两个程序来检查 clflush 是否正在从缓存中清除我的数据。在我编写的两个程序中,只有一个给出了正确的结果(按照我的预期,在 clflush 之后,访问时间必须比刷新之前的时间长)。

这是我得到预期结果的 Program1。

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

inline void clflush(volatile void *p)
{
    asm volatile ("clflush (%0)" :: "r"(p));
}

inline uint64_t rdtsc()
{
    unsigned long a, d;
    asm volatile ("cpuid; rdtsc" : "=a" (a), "=d" (d) : : "ebx", "ecx"); 
    return a | ((uint64_t)d << 32);
}


static int i=10; // static variable 

inline void test()
{
    uint64_t start, end;    
    int j;    
    start = rdtsc();
    j = i;
    end = rdtsc();
    printf("took %lu ticks\n", end - start);
}

int main(int ac, char **av)
{
    test();
    test();
    printf("flush: ");
    clflush((void *)&i);
    test();
    test();
    return 0;
}

这是我的输出(如预期的那样)

took 314 ticks
took 282 ticks
flush: took 442 ticks
took 272 ticks

这是另一个程序,我没有得到预期的结果。

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

inline void clflush(volatile void *p)
{
    asm volatile ("clflush (%0)" :: "r"(p));
}

inline uint64_t rdtsc()
{
    unsigned long a, d;
    asm volatile ("cpuid; rdtsc" : "=a" (a), "=d" (d) : : "ebx", "ecx"); 
    return a | ((uint64_t)d << 32);
}


static const int i=10; // I make this as constant 

inline void test()
{
    uint64_t start, end;    
    int j;    
    start = rdtsc();
    j = i;
    end = rdtsc();
    printf("took %lu ticks\n", end - start);
}

int main(int ac, char **av)
{
    test();
    test();
    printf("flush: ");
    clflush((void *)&i);
    test();
    test();
    return 0;
}

这是我的输出(如预期的那样)

took 314 ticks
took 282 ticks
flush: took 282 ticks // same as previous
took 272 ticks


--------
took 314 ticks
took 282 ticks
flush: took 272 ticks // lower than previous
took 272 ticks

如果我让静态 int i=10;进入静态 const int i=10;那么结果并不符合我的预期。在 clflush 之后,我得到了更低的价值/平等的访问时间。

谁能解释为什么会这样?我怎样才能按照我的期望(在 clflush as program1 之后访问时间更长)来制作它(在 C 或 C++ 中)?

我在 Fedora19 linux 下使用 GCC。任何帮助将不胜感激。

【问题讨论】:

  • 你使用什么编译器选项?
  • 我正在使用这个选项编译,gcc -O0 test.c -o test
  • 完全删除j=i需要多长时间?我猜你只是测量cpuid的延迟
  • @ Leeor ,如果我完全删除 j=i 语句,我得到了 296 个滴答 272 个滴答 刷新:两种情况(非 const 和 const )都花费了 272 个滴答 272 个滴答。
  • (保留@ 附加到名称,否则 SO 不会发送有关您的评论的消息)。无论如何,这表明大部分时间度量确实是 cpuid(以及它的序列化引起的停顿)

标签: c++ c linux performance-testing cpu-architecture


【解决方案1】:

我很确定这里的问题是 CPUID + RDTSC 与“之间的指令”相比太长了。

我得到了非常不同的结果,大概取决于代码最终运行在哪个实际 CPU 上的“运气”、其他 CPU 正在做什么等等。

下面是第二个程序的三个连续运行:

took 92 ticks
took 75 ticks
flush: took 75 ticks
took 474 ticks

took 221 ticks
took 243 ticks
flush: took 221 ticks
took 242 ticks

took 221 ticks
took 221 ticks
flush: took 221 ticks
took 230 ticks

但是,我认为我们不能由此得出“clflush 不起作用”的结论。只是处理器中有足够的时钟周期和足够的乱序执行来克服缓存刷新并重新加载数据。

如果您有大量数据,比如几千字节,您可能会获得更明显的效果。一会儿我会做一些实验,但现在我需要一些食物……

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

inline void clflush(volatile void *p)
{
    __asm volatile ("clflush (%0)" :: "r"(p));
}

inline uint64_t rdtsc()
{
    unsigned long a, d;
    __asm volatile ("rdtsc" : "=a" (a), "=d" (d) : : "ebx", "ecx"); 
    return a | ((uint64_t)d << 32);
}


static int v[1024]; 
uint64_t   t[5];
int        r[5];
int        ti = 0;

static inline void test()
{
    uint64_t start, end;    
    int j;    
    start = rdtsc();
    for(int i = 0; i < 1024; i++)
    {
    j += v[i];
    }
    end = rdtsc();
    r[ti] = j;
    t[ti++] = end - start;
}

int main(int ac, char **av)
{
    for(int i = 0; i < 1024; i++)
    {
    v[i] = i;
    }
    test();
    test();
    t[ti++] = 0;
    for(int i = 0; i < 1024; i+=4)
    {
    clflush((void *)&v[i]);
    }
    test();
    test();
    for(int i = 0; i < ti; i++)
    {
    if (t[i] == 0)
    {
        printf("flush\n");
    }
    else
    {
        printf("Test %lu [res=%d]\n", t[i], r[i]);
    }
    }
    printf("\n");
    return 0;
}

我将 printf 移出测试路径,以减少在那里花费的时间,并使刷新区域更大。这提供了更长的运行时间,这肯定有助于测量。

Test 2538 [res=523776]
Test 2593 [res=523776]
flush
Test 4845 [res=523776]
Test 2592 [res=523776]

Test 2550 [res=523776]
Test 2771 [res=523776]
flush
Test 4782 [res=523776]
Test 2513 [res=523776]

Test 2550 [res=523776]
Test 2708 [res=523776]
flush
Test 4356 [res=523776]
Test 2593 [res=523776]

如您所见,在刷新之后,与第一次访问相比,获取数据的时间大约是两倍。

编辑:

使用 const,像这样

static const int v[1024] = 
{
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
    /* snip 62 lines equal to this */
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
};

给出这个结果:

Test 14139 [res=8704]
Test 2639 [res=8704]
flush
Test 5287 [res=8704]
Test 2597 [res=8704]

Test 12983 [res=8704]
Test 2652 [res=8704]
flush
Test 4859 [res=8704]
Test 2550 [res=8704]

Test 12911 [res=8704]
Test 2581 [res=8704]
flush
Test 4705 [res=8704]
Test 2649 [res=8704]

如您所见,第三次访问明显比第二次和第四次慢。第一次访问速度较慢,因为第一次访问时缓存中根本没有任何内容(包括页表等)。

【讨论】:

  • 感谢@Mats 的回答。为了防止乱序执行,我在这里使用 CPUID。还有一点,我也试过 static const int arr_i [256] = { 0,1,2,...255] ;然后使用 clflush(&arr_i[j]) 刷新缓存中的所有元素,但没有成功。
  • @ Mats ,我已经禁用了其他 CPU 核心以及 CPU 中的 ASLR 、 HT 。
  • CPUID 只会保证“之前的指令执行完毕”。它不保证将来的指令不会启动。您也许可以在 RDTSC 的后端添加另一个 CPUID,但我更喜欢执行循环操作。请参阅我的代码的编辑,虽然结果并不总是相同,但它表明 clflush 有所不同。
  • @ Mats,我得到了静态非常量数据类型的正确结果,(参见 program1 输出)。当我使用静态 const 数据类型时,我没有得到正确的结果。
  • @ Mats,只是检查我是否可以用 const 数据类型重现相同的数据。我会发布我的观察结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-28
  • 1970-01-01
  • 2015-06-23
  • 1970-01-01
  • 2021-10-25
  • 1970-01-01
相关资源
最近更新 更多