Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/linux/27.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Linux 嗯,clflush真的会刷新缓存吗?_Linux_X86 64_Cpu Architecture_Cpu Cache_Cache Locality - Fatal编程技术网

Linux 嗯,clflush真的会刷新缓存吗?

Linux 嗯,clflush真的会刷新缓存吗?,linux,x86-64,cpu-architecture,cpu-cache,cache-locality,Linux,X86 64,Cpu Architecture,Cpu Cache,Cache Locality,我试图通过编写和运行测试程序来了解硬件缓存的工作原理: #include <stdio.h> #include <stdint.h> #include <x86intrin.h> #define LINE_SIZE 64 #define L1_WAYS 8 #define L1_SETS 64 #define L1_LINES 512 // 32K memory for filling in L1 cache uint8_t d

我试图通过编写和运行测试程序来了解硬件缓存的工作原理:

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

#define LINE_SIZE   64

#define L1_WAYS     8
#define L1_SETS     64
#define L1_LINES    512

// 32K memory for filling in L1 cache
uint8_t data[L1_LINES*LINE_SIZE];

int main()
{
    volatile uint8_t *addr;
    register uint64_t i;
    int junk = 0;
    register uint64_t t1, t2;

    printf("data: %p\n", data);

    //_mm_clflush(data);
    printf("accessing 16 bytes in a cache line:\n");
    for (i = 0; i < 16; i++) {
        t1 = __rdtscp(&junk);
        addr = &data[i];
        junk = *addr;
        t2 = __rdtscp(&junk) - t1;
        printf("i = %2d, cycles: %ld\n", i, t2);
    }
}
不带
\u mm\u clflush

$ ./l1
data: 0x700c00
accessing 16 bytes in a cache line:
i =  0, cycles: 280
i =  1, cycles: 84
i =  2, cycles: 91
i =  3, cycles: 77
i =  4, cycles: 91
$ ./l1
data: 0x700c00
accessing 16 bytes in a cache line:
i =  0, cycles: 3899
i =  1, cycles: 91
i =  2, cycles: 105
i =  3, cycles: 77
i =  4, cycles: 84
刷新缓存线是没有意义的,但实际上会更快吗?有人能解释为什么会发生这种情况吗?谢谢

----------------进一步试验-------------------

假设3899次循环是由TLB未命中引起的。为了证明我对缓存命中/未命中的了解,我稍微修改了这段代码,以比较
L1缓存命中
L1缓存未命中
情况下的内存访问时间

这一次,代码跳过缓存线大小(64字节)并访问下一个内存地址

*data = 1;
_mm_clflush(data);
printf("accessing 16 bytes in a cache line:\n");
for (i = 0; i < 16; i++) {
    t1 = __rdtscp(&junk);
    addr = &data[i];
    junk = *addr;
    t2 = __rdtscp(&junk) - t1;
    printf("i = %2d, cycles: %ld\n", i, t2);
}

// Invalidate and flush the cache line that contains p from all levels of the cache hierarchy.
_mm_clflush(data);
printf("accessing 16 bytes in different cache lines:\n");
for (i = 0; i < 16; i++) {
    t1 = __rdtscp(&junk);
    addr = &data[i*LINE_SIZE];
    junk = *addr;
    t2 = __rdtscp(&junk) - t1;
    printf("i = %2d, cycles: %ld\n", i, t2);
}

这是由预取引起的吗?还是我的理解有什么问题?谢谢

我想这可能是因为一开始TLB未命中造成的?而且_mm_clflush实际上将这个虚拟地址缓存到TLB中,我可能是对的吗?如何证明它?

我修改了代码,在
\u mm\u clflush(data)
之前添加了一个write,它显示clflush确实刷新了缓存线。修改后的代码:

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

#define LINE_SIZE   64
#define L1_LINES    512

// 32K memory for filling in L1 cache
uint8_t data[L1_LINES*LINE_SIZE];

int main()
{
    volatile uint8_t *addr;
    register uint64_t i;
    unsigned int junk = 0;
    register uint64_t t1, t2;

    data[0] = 1; //write before cflush
    //_mm_clflush(data);

    printf("accessing 16 bytes in a cache line:\n");
    for (i = 0; i < 16; i++) {
        t1 = __rdtscp(&junk);
        addr = &data[i];
        junk = *addr;
        t2 = __rdtscp(&junk) - t1;
        printf("i = %2d, cycles: %ld\n", i, t2);
    }
}
使用clflush:

data: 0000000000407980
accessing 16 bytes in a cache line:
i =  0, cycles: 214
i =  1, cycles: 41
i =  2, cycles: 40
i =  3, cycles: 42
i =  4, cycles: 40

如果没有
clflush
,第一次加载大约需要3899个周期,这大约是处理小页面错误所需的时间
rdtscp
序列化加载操作,从而确保所有稍后加载到一级缓存中的同一行。现在,当您在循环之前添加
clflush
时,页面错误将在循环外部触发和处理。当页面错误处理程序返回并且重新执行
clflush
时,目标缓存线将被刷新。在英特尔处理器上,
rdtscp
确保在发出循环中的第一次加载之前刷新该行。因此,现金层次结构中的第一次加载未命中,其延迟大约是内存访问的延迟。与前一种情况一样,后面的加载由
rdtscp
序列化,因此它们都在L1D中命中

测量的L1D命中潜伏期太高,即使我们考虑了<代码> RDTSCP < /代码>的开销。你是用

-O3
编译的吗

当静态分配缓存线时,我无法在Linux 4.4.0-154上使用gcc 5.5.0再现您的结果(即,小页面错误),但仅当我使用
mmap
时。如果你告诉我你的编译器版本和内核版本,也许我可以进一步调查


关于第二个问题,您测量负载延迟的方式无法区分L1D命中和L2命中,因为测量中的错误可能与延迟的差异一样大。您可以使用
MEM\u LOAD\u UOPS\u RETIRED.L1\u HIT
MEM\u LOAD\u UOPS\u RETIRED.L2\u HIT
性能计数器进行检查。一级和二级硬件预取器很容易检测到顺序访问模式,因此,如果不关闭预取器,则获得命中率并不奇怪。

使用rdtsc(甚至rdtscp风格)无法测量几个周期的粒度,您可能对性能的影响大于负载本身。您需要在多行上摊销。是的,
clflush
会在任何缓存中刷新缓存线。有关测量缓存命中与L3未命中延迟的工作程序,请参阅。或者,您的意思是,由于rdtscp函数调用使用的周期,此测量不准确?实际上,我正在研究缓存侧通道。我想这可能是区分一级命中、二级命中和缓存未命中的明显结果。谢谢,@PeterCordes。我更新了我的问题,以了解缓存线命中和缓存线未命中之间的区别。只是有点难以解释结果。您能回答这个问题吗?有关缓存读取端通道,请参阅:。如果我稍后再讨论,我会更详细地阅读整个问题。我现在没有时间。是的,如果需要找到正确的物理地址,clflush将导致页面漫游。它不像预取那样是一种暗示;CPU不允许像某些用于软件预取的旧CPU那样在TLB未命中时将其丢弃。您可以使用
alignas(64)volatile uint8\t data[64]
或其他什么;您不需要一个单独的指向volatile的指针。此外,缓存命中的~47和~41个周期(加上rdtscp开销)可能是由于没有将CPU预热到max turbo。无论初始访问是否丢失,缓存命中率应该大致相同。而你
printf
在循环之前的存储之后,这样存储转发就不会发生。
data: 0000000000407980
accessing 16 bytes in a cache line:
i =  0, cycles: 64
i =  1, cycles: 46
i =  2, cycles: 49
i =  3, cycles: 48
i =  4, cycles: 46
data: 0000000000407980
accessing 16 bytes in a cache line:
i =  0, cycles: 214
i =  1, cycles: 41
i =  2, cycles: 40
i =  3, cycles: 42
i =  4, cycles: 40