L2 HW prefetcher gerçekten yardımcı mı?


10

Hattayım Viski Gölü i7-8565U ve (iki kez daha L2 önbellek boyutundan daha) veri 512 KiB kopyalamak için sayaçlar ve zaman perf analiz ve L2 HW Önceden çalışmaları ile ilgili olarak karşılaşılan bazı yanlış anlama.

Olarak Intel Kılavuzu Vol.4 MSR MAB orada 0x1A4biraz 0 (devre dışı 1) L2 HW Önceden controlloing için değildir.


Aşağıdaki karşılaştırmayı düşünün:

memcopy.h:

void *avx_memcpy_forward_lsls(void *restrict, const void *restrict, size_t);

memcopy.S:

avx_memcpy_forward_lsls:
    shr rdx, 0x3
    xor rcx, rcx
avx_memcpy_forward_loop_lsls:
    vmovdqa ymm0, [rsi + 8*rcx]
    vmovdqa [rdi + rcx*8], ymm0
    vmovdqa ymm1, [rsi + 8*rcx + 0x20]
    vmovdqa [rdi + rcx*8 + 0x20], ymm1
    add rcx, 0x08
    cmp rdx, rcx
    ja avx_memcpy_forward_loop_lsls
    ret

main.c:

#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <x86intrin.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include "memcopy.h"

#define ITERATIONS 1000
#define BUF_SIZE 512 * 1024

_Alignas(64) char src[BUF_SIZE];
_Alignas(64) char dest[BUF_SIZE];

static void __run_benchmark(unsigned runs, unsigned run_iterations,
                    void *(*fn)(void *, const void*, size_t), void *dest, const void* src, size_t sz);

#define run_benchmark(runs, run_iterations, fn, dest, src, sz) \
    do{\
        printf("Benchmarking " #fn "\n");\
        __run_benchmark(runs, run_iterations, fn, dest, src, sz);\
    }while(0)

int main(void){
    int fd = open("/dev/urandom", O_RDONLY);
    read(fd, src, sizeof src);
    run_benchmark(20, ITERATIONS, avx_memcpy_forward_lsls, dest, src, BUF_SIZE);
}

static inline void benchmark_copy_function(unsigned iterations, void *(*fn)(void *, const void *, size_t),
                                               void *restrict dest, const void *restrict src, size_t sz){
    while(iterations --> 0){
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
        fn(dest, src, sz);
    }
}

static void __run_benchmark(unsigned runs, unsigned run_iterations,
                    void *(*fn)(void *, const void*, size_t), void *dest, const void* src, size_t sz){
    unsigned current_run = 1;
    while(current_run <= runs){
        benchmark_copy_function(run_iterations, fn, dest, src, sz);
        printf("Run %d finished\n", current_run);
        current_run++;
    }
}

Derlenen 2 çalışmayı düşünün main.c

Ben .

MSR:

$ sudo rdmsr -p 0 0x1A4
0

Run:

$ taskset -c 0 sudo ../profile.sh ./bin 

 Performance counter stats for './bin':

    10 486 164 071      L1-dcache-loads                                               (12,13%)
    10 461 354 384      L1-dcache-load-misses     #   99,76% of all L1-dcache hits    (12,05%)
    10 481 930 413      L1-dcache-stores                                              (12,05%)
    10 461 136 686      l1d.replacement                                               (12,12%)
    31 466 394 422      l1d_pend_miss.fb_full                                         (12,11%)
   211 853 643 294      l1d_pend_miss.pending                                         (12,09%)
     1 759 204 317      LLC-loads                                                     (12,16%)
            31 007      LLC-load-misses           #    0,00% of all LL-cache hits     (12,16%)
     3 154 901 630      LLC-stores                                                    (6,19%)
    15 867 315 545      l2_rqsts.all_pf                                               (9,22%)
                 0      sw_prefetch_access.t1_t2                                      (12,22%)
         1 393 306      l2_lines_out.useless_hwpf                                     (12,16%)
     3 549 170 919      l2_rqsts.pf_hit                                               (12,09%)
    12 356 247 643      l2_rqsts.pf_miss                                              (12,06%)
                 0      load_hit_pre.sw_pf                                            (12,09%)
     3 159 712 695      l2_rqsts.rfo_hit                                              (12,06%)
     1 207 642 335      l2_rqsts.rfo_miss                                             (12,02%)
     4 366 526 618      l2_rqsts.all_rfo                                              (12,06%)
     5 240 013 774      offcore_requests.all_data_rd                                     (12,06%)
    19 936 657 118      offcore_requests.all_requests                                     (12,09%)
     1 761 660 763      offcore_response.demand_data_rd.any_response                                     (12,12%)
       287 044 397      bus-cycles                                                    (12,15%)
    36 816 767 779      resource_stalls.any                                           (12,15%)
    36 553 997 653      resource_stalls.sb                                            (12,15%)
    38 035 066 210      uops_retired.stall_cycles                                     (12,12%)
    24 766 225 119      uops_executed.stall_cycles                                     (12,09%)
    40 478 455 041      uops_issued.stall_cycles                                      (12,05%)
    24 497 256 548      cycle_activity.stalls_l1d_miss                                     (12,02%)
    12 611 038 018      cycle_activity.stalls_l2_miss                                     (12,09%)
        10 228 869      cycle_activity.stalls_l3_miss                                     (12,12%)
    24 707 614 483      cycle_activity.stalls_mem_any                                     (12,22%)
    24 776 110 104      cycle_activity.stalls_total                                     (12,22%)
    48 914 478 241      cycles                                                        (12,19%)

      12,155774555 seconds time elapsed

      11,984577000 seconds user
       0,015984000 seconds sys

II.

MSR:

$ sudo rdmsr -p 0 0x1A4
1

Run:

$ taskset -c 0 sudo ../profile.sh ./bin

 Performance counter stats for './bin':

    10 508 027 832      L1-dcache-loads                                               (12,05%)
    10 463 643 206      L1-dcache-load-misses     #   99,58% of all L1-dcache hits    (12,09%)
    10 481 296 605      L1-dcache-stores                                              (12,12%)
    10 444 854 468      l1d.replacement                                               (12,15%)
    29 287 445 744      l1d_pend_miss.fb_full                                         (12,17%)
   205 569 630 707      l1d_pend_miss.pending                                         (12,17%)
     5 103 444 329      LLC-loads                                                     (12,17%)
            33 406      LLC-load-misses           #    0,00% of all LL-cache hits     (12,17%)
     9 567 917 742      LLC-stores                                                    (6,08%)
     1 157 237 980      l2_rqsts.all_pf                                               (9,12%)
                 0      sw_prefetch_access.t1_t2                                      (12,17%)
           301 471      l2_lines_out.useless_hwpf                                     (12,17%)
       218 528 985      l2_rqsts.pf_hit                                               (12,17%)
       938 735 722      l2_rqsts.pf_miss                                              (12,17%)
                 0      load_hit_pre.sw_pf                                            (12,17%)
         4 096 281      l2_rqsts.rfo_hit                                              (12,17%)
     4 972 640 931      l2_rqsts.rfo_miss                                             (12,17%)
     4 976 006 805      l2_rqsts.all_rfo                                              (12,17%)
     5 175 544 191      offcore_requests.all_data_rd                                     (12,17%)
    15 772 124 082      offcore_requests.all_requests                                     (12,17%)
     5 120 635 892      offcore_response.demand_data_rd.any_response                                     (12,17%)
       292 980 395      bus-cycles                                                    (12,17%)
    37 592 020 151      resource_stalls.any                                           (12,14%)
    37 317 091 982      resource_stalls.sb                                            (12,11%)
    38 121 826 730      uops_retired.stall_cycles                                     (12,08%)
    25 430 699 605      uops_executed.stall_cycles                                     (12,04%)
    41 416 190 037      uops_issued.stall_cycles                                      (12,04%)
    25 326 579 070      cycle_activity.stalls_l1d_miss                                     (12,04%)
    25 019 148 253      cycle_activity.stalls_l2_miss                                     (12,03%)
         7 384 770      cycle_activity.stalls_l3_miss                                     (12,03%)
    25 442 709 033      cycle_activity.stalls_mem_any                                     (12,03%)
    25 406 897 956      cycle_activity.stalls_total                                     (12,03%)
    49 877 044 086      cycles                                                        (12,03%)

      12,231406658 seconds time elapsed

      12,226386000 seconds user
       0,004000000 seconds sys

Sayacı fark ettim:

12 611 038 018 cycle_activity.stalls_l2_miss vs
25 019 148 253 cycle_activity.stalls_l2_miss

MSR devre dışı bırakan L2 HW ön getiricinin uygulandığını gösterir. Ayrıca l2 / LLC ile ilgili diğer şeyler de önemli ölçüde farklıdır. Fark, farklı çalışmalarda tekrarlanabilir . Sorun şu ki, total timedöngülerde ve döngülerde neredeyse hiçbir fark yok :

48 914 478 241 cycles vs
49 877 044 086 cycles

12,155774555 seconds time elapsed vs
12,231406658 seconds time elapsed

SORU:
L2 kaçırmaları diğer performans sınırlayıcıları tarafından gizlenmiş mi?
Eğer öyleyse, anlamak için hangi sayaçlara bakılması gerektiğini önerebilir misiniz?


4
Genel bir kural olarak: Hatalı olarak uygulanmayan tüm bellek kopyaları belleğe bağlıdır. Sadece L1 önbelleğine çarptığında bile. Herhangi bir bellek erişiminin genel giderleri, iki ve ikisini birlikte eklemek için gereken CPU'dan çok daha yüksektir. Sizin durumunuzda, kopyalanan bayt başına talimat miktarını azaltmak için AVX talimatlarını bile kullanıyorsunuz. Verileriniz nerede bulunursa bulunsun (L1, L2, LLC, bellek), ilişkili bellek bileşeninin verimi darboğazınız olacaktır.
— cmaster - reinstate monica

Yanıtlar:


5

Evet, L2 flama çoğu zaman çok yardımcı oluyor.

memcpy gizlemek için herhangi bir hesaplama gecikme süresi yoktur, bu yüzden OoO yürütme kaynaklarının (ROB boyutu) daha fazla L2 özleminden aldığınız ekstra yük gecikmesini ele almasına izin verebilir, en azından tüm L3 isabetlerini aldığınız yerde L3'e uyan orta büyüklükte bir çalışma seti (1MiB) kullanarak, L3 isabetlerini gerçekleştirmek için önceden getirmeye gerek yoktur.

Ve sadece talimatlar yükleme / depolama (ve döngü yükü), bu yüzden OoO penceresi çok ileri için talep yüklerini içeriyor.

LK uzamsal prefetcher ve L1d prefetcher burada herhangi bir yardımcı oluyorsa IDK.


Bu hipotezi test etmek için tahmin : dizinizi daha büyük hale getirin, böylece L3 özledim elde edersiniz ve OoO exec DRAM'e kadar gitmenin yük gecikmesini gizlemek için yeterli olmadığında muhtemelen toplamda bir fark görürsünüz. HW önceden getirme tetikleme bazı yardımcı olabilir.

HW diğer büyük faydalar o zaman gelip ön yükleme yapabilirsiniz sizin hesaplama ayak uydurmak size L2 hit almak, böylece. (Orta uzunlukta, ancak döngü taşınan bağımlılık zinciri ile hesaplanan bir döngüde.)

Talep yükleri ve OoO yürütme, ROB kapasitesi üzerinde başka bir baskı olmadığında mevcut (tek iş parçacıklı) bellek bant genişliğini kullanmak kadar çok şey yapabilir.


Ayrıca, Intel CPU'larda, her önbellek kaybının bağımlı uopsların arka uç oynatmasına (RS / zamanlayıcıdan) mal olabileceğini , verilerin gelmesi beklendiğinde L1d ve L2 için her birinin kaçırıldığını unutmayın. Ve bundan sonra, görünüşte çekirdek, L3'ten veri gelmesini beklerken iyimser bir şekilde uops spam yapıyor.

(Bkz. Https://chat.stackoverflow.com/rooms/206639/discussion-on-question-by-beeonrope-are-load-ops-deallocated-from-the-rs-when-th ve Yük opsları Gönderdiklerinde RS, tamamlandı mı yoksa başka bir zaman mı? )

Önbellek kaçırma yükünün kendisi değil; bu durumda mağaza talimatları olacaktır. Daha spesifik olarak, bağlantı noktası 4 için store-data uop. Burada önemli değil; 32 baytlık mağazalar kullanmak ve L3 bant genişliğinde darboğaz oluşturmak, saatte 1 port 4 uop'a yakın olmadığımız anlamına gelir.


2
@ St.Antario: ha? Bu hiç mantıklı değil; hafızaya bağlısınız, bu yüzden LSD'nin alakasız olması için ön uç darboğazınız yok. (Onları uop önbelleğinden geri almayı önler, biraz güç tasarrufu sağlar). Emekli oluncaya kadar ROB'da yer kaplıyorlar. Onlar da o kadar önemli değil , ama ihmal edilemez.
— Peter Cordes

2
Eğer L3 özlüyor olsun böylece dizi büyük yapmak ve muhtemelen bir fark göreceksiniz Birlikte bir dizi test koştu 16MiBtampon ve 10got gerçekten tekrarlamalar ve 14,186868883 secondsvs 43,731360909 secondsve 46,76% of all LL-cache hitsvs 99,32% of all LL-cache hits; 1 028 664 372 LLC-loadsvs 1 587 454 298 LLC-loads .
— St.Antario

4
@ St.Antario: kayıt yeniden adlandırma! Bu, özellikle x86 gibi bir kayıttan fakir ISA'da, OoO exec'in en önemli parçalarından biridir. Bkz mulss Agner talimat tablolardan farklı, Haswell üzerinde sadece 3 döngüleri sürer Neden? (FP döngülerinin çoklu akümülatörlerle açılması) . Ve BTW, normalde 2 yük yapmak, sonra 2 mağaza yapmak, yük / depo yüklemek istemezsiniz. 4k takma duraklardan kaçınma veya hafifletme şansı daha yüksektir, çünkü daha sonraki yükler (HW'nin önceki mağazalarla çakıştığını tespit etmesi gerekir) daha uzaktadır.
— Peter Cordes

2
@ St.Antario: evet, elbette. Agner Fog'un optimizasyon kılavuzu, kayıt yeniden adlandırma ile OoO exec'i de açıklıyor. BTW, kayıt yeniden adlandırma ayrıca WAW tehlikelerini önler ve yalnızca gerçek bağımlılıkları (RAW) bırakır. Böylece , aynı mimari kayıt defterini yazmayı tamamlamak için önceki bir yükü beklemeden yükler arızalı olarak tamamlanabilir . Ve evet, döngüde taşınan tek zincir RCX'ten geçiyor, böylece zincir ilerleyebiliyor. Bu nedenle adresler erken hazır olabilirken, yükleme / depolama uopsları 2/3 bağlantı noktasında hala tıkanmış durumdadır.
— Peter Cordes

3
Önceden getirmenin L3'teki memcpy için yardımcı olmadığına şaşırdım. Sanırım 10/12 LFB'ler bu durumda "yeterli". Yine de garip görünüyor: orada sınırlayıcı faktör nedir? Çekirdek -> L2 süresi L2 -> L3 süresinden daha az olmalıdır, bu yüzden zihinsel modelimde ikinci bacak için daha fazla tampon (daha fazla doluluk) olması yardımcı olacaktır.
— BeeOnRope

3

Evet, L2 HW prefetcher çok yardımcı oluyor!

Örneğin, tinymembench çalıştıran makinemde (i7-6700HQ) aşağıdaki sonuçları bulun . İlk sonuç sütunu tüm ön getiriciler açıkken, ikinci sonuç sütunu L2 flama kapalı (ancak diğer tüm ön getiriciler hala açık).

Bu test, makinemdeki L3'ten çok daha büyük olan 32 MiB kaynak ve hedef arabellekleri kullanıyor, bu yüzden test etmek çoğunlukla DRAM'ı özlüyor.

==========================================================================
== Memory bandwidth tests                                               ==
==                                                                      ==
== Note 1: 1MB = 1000000 bytes                                          ==
== Note 2: Results for 'copy' tests show how many bytes can be          ==
==         copied per second (adding together read and writen           ==
==         bytes would have provided twice higher numbers)              ==
== Note 3: 2-pass copy means that we are using a small temporary buffer ==
==         to first fetch data into it, and only then write it to the   ==
==         destination (source -> L1 cache, L1 cache -> destination)    ==
== Note 4: If sample standard deviation exceeds 0.1%, it is shown in    ==
==         brackets                                                     ==
==========================================================================

                                                       L2 streamer ON            OFF
 C copy backwards                                     :   7962.4 MB/s    4430.5 MB/s
 C copy backwards (32 byte blocks)                    :   7993.5 MB/s    4467.0 MB/s
 C copy backwards (64 byte blocks)                    :   7989.9 MB/s    4438.0 MB/s
 C copy                                               :   8503.1 MB/s    4466.6 MB/s
 C copy prefetched (32 bytes step)                    :   8729.2 MB/s    4958.4 MB/s
 C copy prefetched (64 bytes step)                    :   8730.7 MB/s    4958.4 MB/s
 C 2-pass copy                                        :   6171.2 MB/s    3368.7 MB/s
 C 2-pass copy prefetched (32 bytes step)             :   6193.1 MB/s    4104.2 MB/s
 C 2-pass copy prefetched (64 bytes step)             :   6198.8 MB/s    4101.6 MB/s
 C fill                                               :  13372.4 MB/s   10610.5 MB/s
 C fill (shuffle within 16 byte blocks)               :  13379.4 MB/s   10547.5 MB/s
 C fill (shuffle within 32 byte blocks)               :  13365.8 MB/s   10636.9 MB/s
 C fill (shuffle within 64 byte blocks)               :  13588.7 MB/s   10588.3 MB/s
 -
 standard memcpy                                      :  11550.7 MB/s    8216.3 MB/s
 standard memset                                      :  23188.7 MB/s   22686.8 MB/s
 -
 MOVSB copy                                           :   9458.4 MB/s    6523.7 MB/s
 MOVSD copy                                           :   9474.5 MB/s    6510.7 MB/s
 STOSB fill                                           :  23329.0 MB/s   22901.5 MB/s
 SSE2 copy                                            :   9073.1 MB/s    4970.3 MB/s
 SSE2 nontemporal copy                                :  12647.1 MB/s    7492.5 MB/s
 SSE2 copy prefetched (32 bytes step)                 :   9106.0 MB/s    5069.8 MB/s
 SSE2 copy prefetched (64 bytes step)                 :   9113.5 MB/s    5063.1 MB/s
 SSE2 nontemporal copy prefetched (32 bytes step)     :  11770.8 MB/s    7453.4 MB/s
 SSE2 nontemporal copy prefetched (64 bytes step)     :  11937.1 MB/s    7712.1 MB/s
 SSE2 2-pass copy                                     :   7092.8 MB/s    4355.2 MB/s
 SSE2 2-pass copy prefetched (32 bytes step)          :   7001.4 MB/s    4585.1 MB/s
 SSE2 2-pass copy prefetched (64 bytes step)          :   7055.1 MB/s    4557.9 MB/s
 SSE2 2-pass nontemporal copy                         :   5043.2 MB/s    3263.3 MB/s
 SSE2 fill                                            :  14087.3 MB/s   10947.1 MB/s
 SSE2 nontemporal fill                                :  33134.5 MB/s   32774.3 MB/s

Bu testlerde L2 flama bulundurmak asla daha yavaş değildir ve genellikle neredeyse iki kat daha hızlıdır.

Genel olarak, sonuçlarda aşağıdaki kalıpları fark edebilirsiniz:

  • Kopyalar genellikle dolgulardan daha fazla etkilenir gibi görünüyor.
  • standard memsetVe STOSB fill(bu platformda aynı kapıya bu kaynatma aşağı) en ön getirme işlemi sonucu birkaç% daha hızlı olmadan daha yalnızca olmak üzere etkilenir.
  • Standart memcpymuhtemelen 32 bayt AVX komutlarını kullanan tek kopyadır ve kopyalardan en az etkilenenlerden biridir - ancak önceden getirme yine de ~% 40 daha hızlıdır.

Diğer üç ön getiriciyi de açıp kapatmayı denedim, ancak genellikle bu kriter için neredeyse ölçülebilir bir etkisi olmadı.


(Eğlenceli gerçek: vmovdqaAVX1 "tamsayı" olmasına rağmen.) OP'nin döngüsünün glibc memcpy'den daha düşük bant genişliği verdiğini düşünüyor musunuz? Ve bu yüzden 12 LFB L2 akışını meşgul edebileceği L2 <-> L3 süperqueue'sinden ekstra MLP'den yararlanmadan L3'e giden talep yüklerine ayak uyduracak kadar yeterli miydi? Muhtemelen testinizdeki fark budur. L3 çekirdek ile aynı hızda çalışmalıdır; ikinizin de dört çekirdekli Skylake-istemci eşdeğeri mikro mimarileri var, bu yüzden muhtemelen benzer L3 gecikmesi var mı?
— Peter Cordes

@PeterCordes - üzgünüm muhtemelen açık olmalıydı: Bu test 32 MiB tamponları arasındaydı, bu yüzden DR3 isabetlerini L3 isabetlerini test ediyor. Ben tmb arabellek boyutu çıktı, ama görmüyorum - oops! Bu kasıtlıydı: OP'nin 512 KiB senaryosunu tam olarak açıklamaya çalışmıyordum, ancak sadece L2 akışının yararlı olup olmadığını gösteren başlık sorusunu cevaplıyor. Sanırım ben daha az veya daha az sonuç üretebilir tampon boyutu kullandım (Ben zaten uarch-benchyorumlarda benzer bir sonuç gördüm ).
— BeeOnRope

1
Cevaba tampon boyutunu ekledim.
— BeeOnRope

1
@ St.Antario: Hayır, sorun değil. Bunu düşünüyoruz Hiçbir fikrim neden olmak bir sorun; AVX1 ve AVX2 talimatlarını karıştırmanın herhangi bir cezası yok gibi. Benim yorumum bu döngü sadece AVX1 gerektirir, ancak bu cevap AVX2 talimatları kullanarak bahsediyordu. Intel, L1d yükleme / depolama veri yollarını AVX2 ile aynı anda 32 bayta genişletti, bu nedenle çalışma zamanı gönderimi yapıyorsanız bir memcpy uygulamasını nasıl seçtiğinizin bir parçası olarak AVX2'nin kullanılabilirliğini kullanabilirsiniz ...
— Peter Kordonlar

1
Prefetcher'ı hangisini nasıl kapattınız? O oldu software.intel.com/en-us/articles/... ? Forum yazılımı.intel.com/ tr-tr/forums/intel-isa-extensions/topic/… bazı bitlerin farklı anlamları olduğunu söylüyor.
— osgx
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.