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':

    10486164071      L1-dcache-loads                                               (12,13%)
    10461354384      L1-dcache-load-misses     #   99,76% of all L1-dcache hits    (12,05%)
    10481930413      L1-dcache-stores                                              (12,05%)
    10461136686      l1d.replacement                                               (12,12%)
    31466394422      l1d_pend_miss.fb_full                                         (12,11%)
   211853643294      l1d_pend_miss.pending                                         (12,09%)
     1759204317      LLC-loads                                                     (12,16%)
            31007      LLC-load-misses           #    0,00% of all LL-cache hits     (12,16%)
     3154901630      LLC-stores                                                    (6,19%)
    15867315545      l2_rqsts.all_pf                                               (9,22%)
                 0      sw_prefetch_access.t1_t2                                      (12,22%)
         1393306      l2_lines_out.useless_hwpf                                     (12,16%)
     3549170919      l2_rqsts.pf_hit                                               (12,09%)
    12356247643      l2_rqsts.pf_miss                                              (12,06%)
                 0      load_hit_pre.sw_pf                                            (12,09%)
     3159712695      l2_rqsts.rfo_hit                                              (12,06%)
     1207642335      l2_rqsts.rfo_miss                                             (12,02%)
     4366526618      l2_rqsts.all_rfo                                              (12,06%)
     5240013774      offcore_requests.all_data_rd                                     (12,06%)
    19936657118      offcore_requests.all_requests                                     (12,09%)
     1761660763      offcore_response.demand_data_rd.any_response                                     (12,12%)
       287044397      bus-cycles                                                    (12,15%)
    36816767779      resource_stalls.any                                           (12,15%)
    36553997653      resource_stalls.sb                                            (12,15%)
    38035066210      uops_retired.stall_cycles                                     (12,12%)
    24766225119      uops_executed.stall_cycles                                     (12,09%)
    40478455041      uops_issued.stall_cycles                                      (12,05%)
    24497256548      cycle_activity.stalls_l1d_miss                                     (12,02%)
    12611038018      cycle_activity.stalls_l2_miss                                     (12,09%)
        10228869      cycle_activity.stalls_l3_miss                                     (12,12%)
    24707614483      cycle_activity.stalls_mem_any                                     (12,22%)
    24776110104      cycle_activity.stalls_total                                     (12,22%)
    48914478241      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':

    10508027832      L1-dcache-loads                                               (12,05%)
    10463643206      L1-dcache-load-misses     #   99,58% of all L1-dcache hits    (12,09%)
    10481296605      L1-dcache-stores                                              (12,12%)
    10444854468      l1d.replacement                                               (12,15%)
    29287445744      l1d_pend_miss.fb_full                                         (12,17%)
   205569630707      l1d_pend_miss.pending                                         (12,17%)
     5103444329      LLC-loads                                                     (12,17%)
            33406      LLC-load-misses           #    0,00% of all LL-cache hits     (12,17%)
     9567917742      LLC-stores                                                    (6,08%)
     1157237980      l2_rqsts.all_pf                                               (9,12%)
                 0      sw_prefetch_access.t1_t2                                      (12,17%)
           301471      l2_lines_out.useless_hwpf                                     (12,17%)
       218528985      l2_rqsts.pf_hit                                               (12,17%)
       938735722      l2_rqsts.pf_miss                                              (12,17%)
                 0      load_hit_pre.sw_pf                                            (12,17%)
         4096281      l2_rqsts.rfo_hit                                              (12,17%)
     4972640931      l2_rqsts.rfo_miss                                             (12,17%)
     4976006805      l2_rqsts.all_rfo                                              (12,17%)
     5175544191      offcore_requests.all_data_rd                                     (12,17%)
    15772124082      offcore_requests.all_requests                                     (12,17%)
     5120635892      offcore_response.demand_data_rd.any_response                                     (12,17%)
       292980395      bus-cycles                                                    (12,17%)
    37592020151      resource_stalls.any                                           (12,14%)
    37317091982      resource_stalls.sb                                            (12,11%)
    38121826730      uops_retired.stall_cycles                                     (12,08%)
    25430699605      uops_executed.stall_cycles                                     (12,04%)
    41416190037      uops_issued.stall_cycles                                      (12,04%)
    25326579070      cycle_activity.stalls_l1d_miss                                     (12,04%)
    25019148253      cycle_activity.stalls_l2_miss                                     (12,03%)
         7384770      cycle_activity.stalls_l3_miss                                     (12,03%)
    25442709033      cycle_activity.stalls_mem_any                                     (12,03%)
    25406897956      cycle_activity.stalls_total                                     (12,03%)
    49877044086      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.