0

(過去に多少関連する質問がいくつかあったことは知っていますが、L1d キャッシュ ミスとハイパースレッディング/SMT に関する質問は見つかりませんでした。)

False Sharing、MESI/MOESI Cache Coherence Protocols などの非常に興味深いものについて数日間読んだ後、C で小さな「ベンチマーク」を作成して (以下を参照)、False Sharing の動作をテストすることにしました。

私は基本的に 8 つの double の配列を持っているので、1 つのキャッシュ ラインと隣接する配列位置をインクリメントする 2 つのスレッドに収まります。

この時点で、私は Ryzen 5 3600 を使用していることを述べる必要があります。そのトポロジーはここで見ることができます。

2 つのスレッドを作成し、それらを 2 つの異なる論理コアに固定し、それぞれが独自の配列位置にアクセスして更新します。つまり、スレッド A は array[2] を更新し、スレッド B は array[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にも共通の L2 キャッシュがありますが、ここでは重要ではないと思います。キャッシュ ラインが無効になると、メイン メモリ (MESI) または別のコアのいずれかからフェッチする必要があるためです。キャッシュ (MOESI) であるため、L2 が必要なデータを保持することさえ不可能であるように思われますが、それを要求することもできません) .

もちろん、1 つのスレッドが L1d キャッシュ ラインに書き込むと、キャッシュ ラインは「ダーティ」になりますが、なぜそれが問題になるのでしょうか? 同じ物理コアに存在する他のスレッドは、新しい「ダーティ」値を問題なく読み取れるはずではありませんか?

TLDR : False Sharing をテストすると、2 つの兄弟スレッド (同じ物理コアに属するスレッド) を使用すると、2 つの異なる物理コアに属するスレッドを使用する場合よりも、L1d キャッシュ ミスが約3 倍多くなります。(2.34% 対 0.75% のミス率、3 億 9600 万対 1 億 1800 万のミスの絶対数)。なぜそれが起こっているのですか?

(L1d キャッシュ ミスなどのすべての統計は、Linux の perf ツールを使用して測定されます。)

また、マイナーな二次的な質問ですが、兄弟スレッドが 6 桁離れた ID でペアになっているのはなぜですか? つまり、スレッド 0 の兄弟はスレッド 6 です。スレッド i の兄弟はスレッド i+6 です。それは何らかの形で役立ちますか?Intel と AMD の両方の CPU でこれに気付きました。

私はコンピュータ アーキテクチャに非常に興味があり、まだ学習中なので、上記のいくつかは間違っている可能性があります。申し訳ありません。

だから、これは私のコードです。2 つのスレッドを作成し、それらを特定の論理コアにバインドしてから、隣接するキャッシュ ラインの場所にアクセスするだけです。

#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], NULL, work, (void*)arg1);

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


    clock_gettime(CLOCK_REALTIME, &tend);
 }

私はGCC 10.2.0 Compilingを次のように使用していますgcc -pthread p.c -o p

次に、perf record ./p --cpu=0,6スレッド 0,6 と 0,11 をそれぞれ使用する場合、 --cpu=0,11 で実行するか、同じことを実行します。

perf stat -d ./p --cpu=0,6他の場合は --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
4

0 に答える 0