Je suis sur Whisky Lake i7-8565U et les compteurs de et le temps nécessaire pour copier 512 Ko de données (deux fois plus que la taille du cache L2) et fait face à un malentendu concernant le travail du préfiltre L2 HW.
Dans le manuel Intel vol.4 MSR, il y a MSR dont 0x1A4
le bit 0 sert à contrôler le préfiltre L2 HW (1 à désactiver).
Considérez le repère suivant:
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++;
}
}
Considérez 2 exécutions du compilé main.c
Je .
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
J'ai remarqué le comptoir:
12 611 038 018 cycle_activity.stalls_l2_miss
contre
25 019 148 253 cycle_activity.stalls_l2_miss
suggérant que le MSR désactivant le préfiltre L2 HW est appliqué. Les autres éléments liés à l2 / LLC diffèrent également de manière significative. La différence est reproductible sur différentes séries . Le problème est qu'il n'y a presque pas de différence dans les total time
cycles:
48 914 478 241 cycles
contre
49 877 044 086 cycles
12,155774555 seconds time elapsed
contre
12,231406658 seconds time elapsed
QUESTION: Les
échecs L2 sont-ils cachés par d'autres limiteurs de performances?
Si oui, pouvez-vous suggérer quels compteurs regarder pour le comprendre?
Réponses:
Oui, le streamer L2 est vraiment utile la plupart du temps.
memcpy n'a pas de latence de calcul à masquer, donc je suppose qu'il peut se permettre de laisser les ressources d'exécution OoO (taille ROB) gérer la latence de charge supplémentaire que vous obtenez à partir de plus de missiles L2, au moins dans ce cas où vous obtenez tous les hits L3 de en utilisant un ensemble de travail de taille moyenne (1 Mo) compatible avec L3, aucune prélecture n'est nécessaire pour que les hits L3 se produisent.
Et les seules instructions sont load / store (et boucle overhead), donc la fenêtre OoO inclut les charges de demande pour assez loin.
IDK si le préfixeur spatial L2 et le préfiltre L1d sont utiles ici.
Prédiction pour tester cette hypothèse : agrandissez votre tableau pour que vous obteniez des échecs L3 et vous verrez probablement une différence dans le temps global une fois que OoO exec ne suffira pas à masquer la latence de charge d'aller jusqu'à la DRAM. HW prefetch se déclenchant plus loin peut aider certains.
Les autres grands avantages de la pré-lecture HW viennent quand cela peut suivre votre calcul, vous obtenez donc des hits L2. (Dans une boucle qui a un calcul avec une chaîne de dépendance de longueur moyenne mais non portée par la boucle.)
Les charges de demande et les exécutions OoO peuvent faire beaucoup en ce qui concerne l'utilisation de la bande passante mémoire disponible (à un seul thread), lorsqu'il n'y a pas d'autre pression sur la capacité ROB.
Notez également que sur les processeurs Intel, chaque échec de cache peut coûter une relecture back-end (à partir du RS / ordonnanceur) d' uops dépendants , un pour les échecs L1d et L2 lorsque les données devraient arriver. Et après cela, apparemment, le noyau spams optimiste uops en attendant que les données arrivent de L3.
(Voir https://chat.stackoverflow.com/rooms/206639/discussion-on-question-by-beeonrope-are-load-ops-deallocated-from-the-rs-when-th et Are load ops deallocated from the RS quand ils expédient, se terminent ou à un autre moment? )
Pas le cache-miss se charge lui-même; dans ce cas, ce serait les instructions du magasin. Plus précisément, l'uop store-data pour le port 4. Cela n'a pas d'importance ici; l'utilisation de magasins de 32 octets et d'un goulot d'étranglement sur la bande passante L3 signifie que nous ne sommes pas proches de 1 port 4 uop par horloge.
la source
16MiB
tampon et10
itérations et j'ai en fait obtenu14,186868883 seconds
vs43,731360909 seconds
et46,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
.Oui, le préfiltre L2 HW est très utile!
Par exemple, trouvez ci-dessous les résultats sur ma machine (i7-6700HQ) exécutant tinymembench . La première colonne des résultats est avec tous les prefetchers activés, la deuxième colonne des résultats est avec le streamer L2 désactivé (mais tous les autres prefetchers toujours activés).
Ce test utilise des tampons de source et de destination de 32 Mio, qui sont beaucoup plus grands que le L3 sur ma machine, il testera donc principalement les manquements à la DRAM.
Dans ces tests, le streamer L2 n'est jamais plus lent et est souvent presque deux fois plus rapide.
En général, vous pouvez remarquer les modèles suivants dans les résultats:
standard memset
etSTOSB fill
(ceux-ci se résument à la même chose sur cette plate-forme) sont les moins affectés, le résultat de la prélecture n'étant que de quelques% plus rapide que sans.memcpy
est probablement la seule copie ici qui utilise des instructions AVX de 32 octets, et elle est parmi les moins affectées des copies - mais la prélecture est toujours ~ 40% plus rapide que sans.J'ai également essayé d'activer et de désactiver les trois autres prefetchers, mais ils n'avaient généralement presque aucun effet mesurable pour cette référence.
la source
vmovdqa
AVX1 est-il un "entier"). Pensez-vous que la boucle de l'OP fournissait une bande passante inférieure à celle de la glibc memcpy? Et c'est pourquoi 12 LFB étaient suffisants pour suivre les charges de demande allant à L3, sans profiter du MLP supplémentaire de la superqueue L2 <-> L3 que le streamer L2 peut garder occupé? C'est probablement la différence dans votre test. L3 devrait fonctionner à la même vitesse que le noyau; vous avez tous les deux des micro-architectures quadricœurs équivalentes à Skylake, donc une latence L3 probablement similaire?uarch-bench
mentionné dans les commentaires).