在检查 False Sharing 时,为什么在使用兄弟线程运行时比使用独立线程运行时有更多的 L1d 缓存未命中

问题描述

(我知道过去有人问过一些相关的问题,但我找不到关于 L1d 缓存未命中和超线程/SMT 的问题。)

在阅读了几天关于虚假共享、MESI/MOESI 缓存一致性协议等超级有趣的内容后,我决定用 C 编写一个小的“基准”(见下文),以测试虚假共享的实际效果

我基本上有一个包含 8 个双精度值的数组,因此它适合一个缓存行和两个递增相邻数组位置的线程。

此时我应该声明我使用的是 Ryzen 5 3600 ,其拓扑结构可以在 here 中看到。

我创建了两个线程,然后将它们固定在两个不同的逻辑核心上,每个线程访问并更新自己的数组位置,即线程 A 更新数组[2],线程 B 更新数组[3]。

当我使用属于同一核心的硬件线程 #0#6 运行代码时,(如拓扑图所示)共享 L1d 缓存,执行时间约为 5 秒。

当我使用没有任何共同缓存的线程 #0#11 时,完成大约需要 9.5 秒。这个时间差是意料之中的,因为在这种情况下,“缓存线乒乓”正在​​进行。

然而,这正是我的问题所在,当我使用线程 #0#11 时,L1d 缓存未命中少于使用线程运行 #0#6

我的猜测是,当使用没有公共缓存的线程 #0#11 时,当一个线程更新共享缓存行的内容时,根据到 MESI/MOESI 协议,另一个内核中的缓存行无效。因此,即使正在进行乒乓,也不会发生太多缓存未命中(与使用线程 #0#6 运行时相比),只是一堆无效和缓存行块在内核之间传输。

那么,当使用具有公共 L1d 缓存的线程 #0 和 #6 时,为什么会有更多缓存未命中?

(线程 #0#6 ,也有共同的二级缓存,但我认为它在这里没有任何重要性,因为当缓存行失效时,它必须从主内存 (MESI) 或另一个内核的缓存 (MOESI) 中获取,因此 L2 似乎不可能拥有所需的数据,但也被要求提供)。

当然,当一个线程写入 L1d 缓存行时,缓存行会变得“脏”,但这有什么关系呢?位于同一个物理核心上的另一个线程读取新的“脏”值应该没有问题吗?

TLDR:在测试 False Sharing 时,与使用两个兄弟线程(属于同一物理核心的线程)相比,L1d 缓存未命中率大约 3 倍使用属于两个不同物理内核的线程。 (2.34% 与 0.75% 的未命中率,3.96 亿与 1.18 亿的绝对未命中数)。为什么会这样?

(L1d 缓存未命中等所有统计数据均使用 Linux 中的 perf 工具进行测量。)

另外,次要问题,为什么兄弟线程在 IDs 6 中成对分开?即线程 0 的兄弟是线程 6.. 线程 i 的兄弟是线程 i+6。这有什么帮助吗?我在 Intel 和 AMD cpu 中都注意到了这一点。

我对计算机架构非常感兴趣,我还在学习,所以上面的一些可能是错误的,很抱歉。

所以,这是我的代码。只需创建两个线程,将它们绑定到特定的逻辑内核,然后访问相邻的缓存行位置。

#define _GNU_SOURCE

#include <stdio.h>
#include <sched.h>
#include <stdlib.h>
#include <sys/random.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

struct timespec tstart,tend;
static cpu_set_t cpuset;


typedef struct arg_s
{
       int index;
       double *array_ptr;
} arg_t;

void *work(void *arg)
{
    pthread_setaffinity_np(pthread_self(),sizeof(cpu_set_t),&cpuset);
    int array_index = ((arg_t*)arg)->index;
    double *ptr = ((arg_t*)arg)->array_ptr;

    for(unsigned long i=0; i<1000000000; i++)
    {
            //it doesn't matter which of these is used
            // as long we are hitting adjacent positions
            ptr[array_index] ++;
            // ptr[array_index] += 1.0e5 * 4;
    }
    return NULL;
}

int main()
{
    pthread_t tid[2];

    srand(time(NULL));
    
    static int cpu0 = 0;
    static int cpu6 = 6; //change this to say 11 to run with threads 0 and 11

    cpu_ZERO(&cpuset);
    cpu_SET(cpu0,&cpuset);
    cpu_SET(cpu6,&cpuset);

    double array[8];

    for(int i=0; i<8; i++)
            array[i] = drand48();

    arg_t *arg0 = malloc(sizeof(arg_t));
    arg_t *arg1 = malloc(sizeof(arg_t));

    arg0->index = 0; arg0->array_ptr = array;       
    arg1->index = 1; arg1->array_ptr = array;


    clock_gettime(CLOCK_REALTIME,&tstart);

    pthread_create(&tid[0],NULL,work,(void*)arg0);
    pthread_create(&tid[1],(void*)arg1);

    pthread_join(tid[0],NULL);
    pthread_join(tid[1],NULL);


    clock_gettime(CLOCK_REALTIME,&tend);
 }

我使用的是 GCC 10.2.0 编译为 gcc -pthread p.c -o p

然后在分别使用线程 0,6 和 0,11 时运行 perf record ./p --cpu=0,6 或与 --cpu=0,11 相同的东西。

然后在其他情况下运行 perf stat -d ./p --cpu=0,11 相同

使用线程 06 运行:

Performance counter stats for './p --cpu=0,6':

           9437,29 msec task-clock                #    1,997 cpus utilized          
                64      context-switches          #    0,007 K/sec                  
                 2      cpu-migrations            #    0,000 K/sec                  
               912      page-faults               #    0,097 K/sec                  
       39569031046      cycles                    #    4,193 GHz                      (75,00%)
        5925158870      stalled-cycles-frontend   #   14,97% frontend cycles idle     (75,00%)
        2300826705      stalled-cycles-backend    #    5,81% backend cycles idle      (75,00%)
       24052237511      instructions              #    0,61  insn per cycle         
                                                  #    0,25  stalled cycles per insn  (75,00%)
        2010923861      branches                  #  213,083 M/sec                    (75,00%)
            357725      branch-misses             #    0,02% of all branches          (75,03%)
       16930828846      L1-dcache-loads           # 1794,034 M/sec                    (74,99%)
         396121055      L1-dcache-load-misses     #    2,34% of all L1-dcache accesses  (74,96%)
   <not supported>     LLC-loads                                                   
   <not supported>     LLC-load-misses                                             

       4,725786281 seconds time elapsed

       9,429749000 seconds user
       0,000000000 seconds sys 

使用线程 011 运行:

 Performance counter stats for './p --cpu=0,11':

          18693,31 msec task-clock                #    1,982 cpus utilized          
               114      context-switches          #    0,006 K/sec                  
                 1      cpu-migrations            #    0,000 K/sec                  
               903      page-faults               #    0,048 K/sec                  
       78404951347      cycles                    #    4,194 GHz                      (74,97%)
        1763001213      stalled-cycles-frontend   #    2,25% frontend cycles idle     (74,98%)
       71054052070      stalled-cycles-backend    #   90,62% backend cycles idle      (74,98%)
       24055983565      instructions              #    0,31  insn per cycle         
                                                  #    2,95  stalled cycles per insn  (74,97%)
        2012326306      branches                  #  107,650 M/sec                    (74,96%)
            553278      branch-misses             #    0,03% of all branches          (75,07%)
       15715489973      L1-dcache-loads           #  840,701 M/sec                    (75,09%)
         118455010      L1-dcache-load-misses     #    0,75% of all L1-dcache accesses  (74,98%)
   <not supported>      LLC-loads                                                   
   <not supported>      LLC-load-misses                                             

       9,430223356 seconds time elapsed

      18,675328000 seconds user
       0,000000000 seconds sys

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)