Apakah prefetcher L2 HW benar-benar bermanfaat?

10

Saya berada di Whiskey Lake i7-8565U dan menganalisis penghitung perf dan waktu untuk menyalin data 512 KiB (dua kali lebih banyak dari ukuran cache L2) dan menghadapi beberapa kesalahpahaman mengenai pekerjaan prefetcher L2 HW.

Dalam Intel Manual Vol.4 MSR terdapat MSR 0x1A4bit 0 of adalah untuk controlloing L2 HW prefetcher (1 untuk menonaktifkan).


Pertimbangkan tolok ukur berikut:

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++;
    }
}

Pertimbangkan 2 run yang dikompilasi main.c

Aku .

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

Saya perhatikan konter:

12 611 038 018 cycle_activity.stalls_l2_miss v / s
25 019 148 253 cycle_activity.stalls_l2_miss

menunjukkan bahwa MSR menonaktifkan prefetcher L2 HW sedang diterapkan. Juga hal-hal lain yang terkait dengan l2 / LLC berbeda secara signifikan. Perbedaannya dapat direproduksi di berbagai jalur . Masalahnya adalah hampir tidak ada perbedaan dalam total timedan siklus:

48 914 478 241 cycles v / s
49 877 044 086 cycles

12,155774555 seconds time elapsed v / s
12,231406658 seconds time elapsed

PERTANYAAN:
Apakah L2 hilang disembunyikan oleh pembatas kinerja lainnya?
Jika demikian, dapatkah Anda menyarankan penghitung apa yang harus dilihat untuk memahaminya?

St.Antario
sumber
4
Sebagai aturan praktis: Setiap salinan memori yang diimplementasikan secara tidak terbatas terikat dengan memori. Bahkan ketika itu hanya mencapai cache L1. Overhead dari akses memori apa pun jauh lebih tinggi daripada yang dibutuhkan CPU untuk menambahkan dua dan dua secara bersamaan. Dalam kasus Anda, Anda bahkan menggunakan instruksi AVX untuk mengurangi jumlah instruksi per byte yang disalin. Di mana pun data Anda ditemukan (L1, L2, LLC, memori), throughput dari komponen memori terkait akan menjadi hambatan Anda.
cmaster - mengembalikan monica

Jawaban:

5

Ya, L2 streamer sangat membantu banyak waktu.

memcpy tidak memiliki latensi komputasi untuk disembunyikan, jadi saya kira itu bisa membiarkan sumber daya OoO exec (ukuran ROB) menangani latensi beban tambahan yang Anda dapatkan dari lebih banyak kesalahan L2, setidaknya dalam kasus ini di mana Anda mendapatkan semua hit L3 dari menggunakan perangkat kerja ukuran sedang (1MiB) yang pas di L3, tidak perlu prefetching untuk membuat hit L3 terjadi.

Dan satu-satunya instruksi adalah load / store (dan loop overhead), sehingga jendela OoO termasuk beban permintaan untuk cukup jauh ke depan.

IDK jika prefetcher spasial L2 dan prefetcher L1 membantu di sini.


Prediksi untuk menguji hipotesis ini : buat array Anda lebih besar sehingga Anda kehilangan L3 dan Anda mungkin akan melihat perbedaan waktu keseluruhan begitu eksekutif OoO tidak cukup untuk menyembunyikan latensi beban hingga DRAM. Preferensi HW memicu lebih jauh ke depan dapat membantu beberapa.

Manfaat besar lainnya dari pengambilan gambar HW datang ketika dapat mengikuti perhitungan Anda, sehingga Anda mendapatkan hit L2. (Dalam satu loop yang memiliki komputasi dengan rantai ketergantungan yang diangkut dengan panjang sedang tetapi tidak.)

Permintaan banyak dan OoO exec dapat melakukan banyak hal sejauh menggunakan bandwidth memori yang tersedia (single threaded), ketika tidak ada tekanan lain pada kapasitas ROB.


Juga catat bahwa pada Intel CPU, setiap cache miss dapat dikenai biaya ulangan back-end (dari RS / scheduler) dari uops dependen , masing-masing untuk L1d dan L2 meleset saat data diharapkan tiba. Dan setelah itu, ternyata core secara optimis mengirim spam saat menunggu data tiba dari L3.

(Lihat https://chat.stackoverflow.com/rooms/206639/discussion-on-question-by-beeonrope-are-load-ops-deallocated-from-the-rs-when-th dan Apakah ops beban deallocated dari RS ketika mereka mengirim, menyelesaikan atau lain waktu? )

Bukan beban cache-miss itu sendiri; dalam hal ini akan menjadi instruksi toko. Lebih khusus lagi, uop data toko untuk port 4. Itu tidak masalah di sini; menggunakan toko 32-byte dan bottlenecking pada bandwidth L3 berarti kita tidak dekat dengan 1 port 4 uop per jam.

Peter Cordes
sumber
2
@ St.Antario: ya? Itu tidak masuk akal; Anda terikat memori sehingga Anda tidak memiliki hambatan front-end sehingga LSD tidak relevan. (Ini menghindari mengambil kembali mereka dari cache uop, menghemat daya). Mereka masih mengambil ruang di ROB sampai mereka bisa pensiun. Mereka tidak begitu signifikan, tetapi juga tidak dapat diabaikan.
Peter Cordes
2
membuat array Anda lebih besar sehingga Anda mendapatkan L3 meleset dan Anda mungkin akan melihat perbedaan saya menjalankan sejumlah tes dengan 16MiBbuffer dan 10iterasi dan memang mendapat 14,186868883 secondsvs 43,731360909 secondsdan 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: dengan mendaftar penggantian nama! Ini adalah salah satu bagian terpenting dari OoO exec, terutama pada ISA yang miskin seperti x86. Lihat Mengapa mulsa hanya mengambil 3 siklus di Haswell, berbeda dari tabel instruksi Agner? (Buka gulungan FP dengan beberapa akumulator) . Dan BTW, biasanya Anda ingin melakukan 2 beban kemudian 2 toko, bukan memuat / menyimpan beban / toko. Peluang yang lebih baik untuk menghindari atau mengurangi warung aliasing 4k karena muatan yang lebih baru (yang harus dideteksi HW sebagai tumpang tindih dengan toko sebelumnya atau tidak) lebih jauh.
Peter Cordes
2
@ St.Antario: ya, tentu saja. Panduan pengoptimalan Agner Fog juga menjelaskan OoO exec dengan penggantian nama register, demikian juga wikipedia. BTW, register renaming juga menghindari bahaya WAW, hanya menyisakan dependensi sejati (RAW). Sehingga banyak yang dapat menyelesaikan pesanan, tanpa menunggu muatan sebelumnya selesai menulis register arsitektur yang sama. Dan ya, satu-satunya rantai dep-loop dilakukan melalui RCX, sehingga rantai dapat berjalan di depan. Itu sebabnya alamat bisa siap lebih awal, sementara load / store uops masih mengalami hambatan pada throughput port 2/3.
Peter Cordes
3
Saya terkejut bahwa prefetching tidak membantu memcpy di L3. Saya kira 10/12 LFBs "cukup" dalam kasus itu. Tampaknya aneh: apa faktor pembatas di sana? Inti -> waktu L2 harus kurang dari waktu L2 -> L3, jadi dalam model mental saya memiliki lebih banyak buffer (lebih banyak hunian total) untuk leg kedua harus membantu.
BeeOnRope
3

Ya, prefetcher L2 HW sangat membantu!

Misalnya, temukan hasil di bawah ini di mesin saya (i7-6700HQ) menjalankan tinymembench . Kolom pertama hasil dengan semua prefetcher aktif, kolom hasil kedua adalah dengan L2 streamer dimatikan (tetapi semua prefetcher lainnya masih aktif).

Tes ini menggunakan 32 sumber MIB dan buffer tujuan, yang jauh lebih besar dari L3 pada mesin saya, jadi itu akan menguji sebagian besar meleset ke DRAM.

==========================================================================
== 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

Dalam tes ini memiliki streamer L2 tidak pernah lebih lambat dan seringkali hampir dua kali lebih cepat.

Secara umum, Anda mungkin memperhatikan pola berikut dalam hasil:

  • Salinan pada umumnya tampaknya lebih terpengaruh daripada mengisi.
  • The standard memsetdan STOSB fill(ini mendidih ke hal yang sama pada platform ini) adalah yang paling tidak terpengaruh, dengan hasil pengambilan awal hanya beberapa% lebih cepat daripada tanpa.
  • Standar memcpymungkin adalah satu-satunya salinan di sini yang menggunakan instruksi AVX 32-byte, dan itu adalah salah satu yang paling terpengaruh dari salinan - tetapi prefetching masih ~ 40% lebih cepat daripada tanpa.

Saya juga mencoba menghidupkan dan mematikan tiga prefetcher lainnya, tetapi mereka umumnya hampir tidak memiliki efek yang dapat diukur untuk tolok ukur ini.

BeeOnRope
sumber
(Fakta menyenangkan: vmovdqaapakah AVX1 meskipun "integer".) Apakah menurut Anda loop OP memberikan bandwidth yang lebih rendah daripada glibc memcpy? Dan itu sebabnya 12 LFB cukup untuk mengimbangi beban permintaan pergi ke L3, tanpa mengambil keuntungan dari MLP tambahan dari L2 <-> L3 superqueue dimana streamer L2 dapat terus ditempati? Itu mungkin perbedaan dalam tes Anda. L3 harus berjalan pada kecepatan yang sama dengan inti; Anda berdua memiliki quad-core Skylake-client microarchitectures jadi mungkin mirip L3 latency?
Peter Cordes
@PeterCordes - maaf saya mungkin seharusnya sudah jelas: tes ini adalah antara 32 MiB buffer, jadi ini menguji DRAM hits bukan L3 hits. Saya pikir tmb menampilkan ukuran buffer, tapi saya lihat tidak - oops! Itu disengaja: Saya tidak mencoba menjelaskan persis skenario 512 KiB OP, tetapi hanya menjawab pertanyaan utama apakah L2 streamer berguna dengan skenario yang menunjukkan itu. Saya kira saya menggunakan ukuran buffer yang lebih kecil saya bisa lebih atau kurang mereproduksi hasilnya (saya sudah melihat hasil yang serupa dalam yang uarch-benchdisebutkan dalam komentar).
BeeOnRope
1
Saya menambahkan ukuran buffer ke jawabannya.
BeeOnRope
1
@ St.Antario: Tidak, tidak masalah. Tidak tahu mengapa Anda berpikir itu mungkin menjadi masalah; tidak seperti ada penalti untuk mencampur instruksi AVX1 dan AVX2. Inti dari komentar saya adalah bahwa loop ini hanya membutuhkan AVX1, namun jawaban ini menyebutkan menggunakan instruksi AVX2. Intel kebetulan memperluas L1d memuat / menyimpan jalur data ke 32 byte pada saat yang sama dengan memperkenalkan AVX2, sehingga Anda dapat menggunakan ketersediaan AVX2 sebagai bagian dari cara Anda memilih implementasi memcpy jika Anda melakukan pengiriman runtime ...
Peter Cordes
1
Bagaimana Anda mematikan prefetcher dan yang mana? Apakah itu software.intel.com/en-us/articles/… ? Forum software.intel.com/en-us/forums/intel-isa-extensions/topic/… mengatakan bahwa beberapa bit memiliki arti yang berbeda.
osgx