Jestem na Whisky Lake i7-8565U i analizuję liczniki perf oraz czas na skopiowanie 512 KiB danych (dwa razy więcej niż rozmiar pamięci podręcznej L2) i napotkałem pewne nieporozumienia dotyczące pracy modułu pobierania wstępnego L2 HW.
W Intel Manual Vol.4 MSR jest MSR, 0x1A4
którego bit 0 służy do kontrolowania modułu wstępnego pobierania L2 HW (1, aby wyłączyć).
Rozważ następujący punkt odniesienia:
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++;
}
}
Rozważ 2 serie skompilowanych main.c
Ja .
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
Zauważyłem licznik:
12 611 038 018 cycle_activity.stalls_l2_miss
vs
25 019 148 253 cycle_activity.stalls_l2_miss
sugerując, że zastosowano MSR wyłączający moduł wstępnego pobierania L2 HW. Również inne rzeczy związane z l2 / LLC różnią się znacznie. Różnica jest odtwarzalna dla różnych przebiegów . Problem polega na tym, że nie ma prawie żadnej różnicy total time
i cykli:
48 914 478 241 cycles
vs
49 877 044 086 cycles
12,155774555 seconds time elapsed
vs
12,231406658 seconds time elapsed
PYTANIE:
Czy brak L2 jest ukryty przez inne ograniczniki wydajności?
Jeśli tak, czy możesz zasugerować, na jakie liczniki spojrzeć, aby to zrozumieć?
Odpowiedzi:
Tak, streamer L2 jest bardzo pomocny przez większość czasu.
memcpy nie ma żadnych ukrytych opóźnień obliczeniowych, więc sądzę, że stać go na to, aby pozwolić zasobom wykonawczym OoO (rozmiar ROB) obsłużyć dodatkowe opóźnienie obciążenia, które otrzymujesz z większej liczby braków L2, przynajmniej w tym przypadku, gdy otrzymujesz wszystkie trafienia L3 z przy użyciu średniej wielkości zestawu roboczego (1MiB), który pasuje do L3, nie jest wymagane wstępne pobieranie, aby nastąpiło trafienie w L3.
A jedynymi instrukcjami są ładowanie / przechowywanie (i narzut pętli), więc okno OoO zawiera obciążenia popytu na całkiem daleką przyszłość.
IDK, jeśli prefiks przestrzenny L2 i preetcher L1d tutaj pomagają.
Prognozy do przetestowania tej hipotezy : powiększ swoją tablicę, aby uzyskać brakujące L3 i prawdopodobnie zobaczysz różnicę w całkowitym czasie, gdy OoO exec nie wystarczy, aby ukryć opóźnienie ładowania aż do DRAM. Wywołanie poboru CWU uruchamiane dalej może pomóc niektórym.
Inne duże zalety pobierania wstępnego HW wynika z tego, że może nadążyć za obliczeniami, dzięki czemu otrzymujesz trafienia L2. (W pętli, która ma obliczenia z łańcuchem zależności średniej długości, ale nie z pętlą.)
Obciążenia na żądanie i OoO exec mogą wiele zrobić, jeśli chodzi o wykorzystanie dostępnej przepustowości pamięci (jednowątkowej), gdy nie ma innego nacisku na pojemność ROB.
Należy również pamiętać, że w przypadku procesorów Intel każda brakująca pamięć podręczna może kosztować powtórne odtwarzanie (z RS / harmonogramu) zależnych upops , po jednym dla L1d i L2 nieudanych, gdy oczekuje się, że dane dotrą. A potem, najwyraźniej rdzeń optymistycznie wysyła spam podczas oczekiwania na dane z L3.
(Zobacz https://chat.stackoverflow.com/rooms/206639/discussion-on-question-by-beeonrope-are-load-ops-deallocated-from-the-rs-when-th and Are load ops dealocated from the RS, gdy wysyłają, kompletują lub w innym terminie? )
Nie samo ładowanie pamięci podręcznej; w tym przypadku byłyby to instrukcje sklepu. Mówiąc dokładniej, kupa danych sklepu dla portu 4. To nie ma znaczenia tutaj; korzystanie z 32-bajtowych sklepów i wąskie gardło w przepustowości L3 oznacza, że nie jesteśmy blisko 1 portu 4 UOP na zegar.
źródło
16MiB
buforem i10
iteracjami i rzeczywiście dostałem14,186868883 seconds
vs43,731360909 seconds
i46,76% of all LL-cache hits
vs99,32% of all LL-cache hits
;1 028 664 372 LLC-loads
vs1 587 454 298 LLC-loads
.Tak, moduł wstępny L2 HW jest bardzo pomocny!
Na przykład znajdź poniżej wyniki na moim komputerze (i7-6700HQ) z uruchomionym programem tinymembench . Pierwsza kolumna wyników jest z włączonymi wszystkimi modułami pobierania wstępnego, druga kolumna wyników jest z wyłączonym streamerem L2 (ale wszystkie pozostałe moduły pobierania są nadal włączone).
Ten test wykorzystuje 32 źródłowe i docelowe bufory MiB, które są znacznie większe niż L3 na moim komputerze, więc będzie testować głównie brakujące dane do DRAM.
W tych testach posiadanie streamera L2 nigdy nie jest wolniejsze i często prawie dwa razy szybsze.
Zasadniczo w wynikach można zauważyć następujące wzorce:
standard memset
iSTOSB fill
(sprowadzają się do tej samej rzeczy na tej platformie) są najmniej dotknięte, a wstępnie wybrany wynik jest tylko o kilka% szybszy niż bez.memcpy
jest prawdopodobnie jedyną kopią, która używa 32-bajtowych instrukcji AVX, i jest jedną z najmniej dotkniętych kopii - ale pobieranie wstępne jest nadal ~ 40% szybsze niż bez.Próbowałem także włączać i wyłączać pozostałe trzy moduły pobierania wstępnego, ale generalnie nie miały one prawie żadnego wymiernego wpływu na ten test porównawczy.
źródło
vmovdqa
czy AVX1 jest „liczbą całkowitą”). Czy uważasz, że pętla OP zapewniała mniejszą przepustowość niż memcpy glibc? I dlatego 12 LFB wystarczyło, aby nadążyć za obciążeniem popytu idącym do L3, bez korzystania z dodatkowej MLP z kolejki L2 <-> L3, którą streamer L2 może utrzymać? To prawdopodobnie różnica w twoim teście. L3 powinien pracować z tą samą prędkością co rdzeń; oboje macie czterordzeniowe odpowiedniki mikroarchitektury klienta Skylake, więc prawdopodobnie podobne opóźnienie L3?uarch-bench
wspomniany w komentarzach).