Чи справді корисний префетер L2 HW?


10

Я перебуваю на Whiskey Lake i7-8565U і аналізую лічильники перф і час для копіювання 512 Кб даних (вдвічі більше, ніж розмір кешу L2) і зіткнувся з деяким непорозумінням щодо роботи префедера L2 HW.

У посібнику з Intel Vol.4 MSR є MSR, 0x1A4біт 0 є для керування префедером L2 HW (1 для відключення).


Розглянемо наступний орієнтир:

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

Розглянемо 2 запуски складеного main.c

Я .

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

Я помітив лічильник:

12 611 038 018 cycle_activity.stalls_l2_miss в / с
25 019 148 253 cycle_activity.stalls_l2_miss

припускаючи, що застосовується MSR, що вимикає префетер L2 HW HW. Також інші матеріали, пов'язані з l2 / LLC, значно відрізняються. Різницю можна відтворити на різних етапах . Проблема в тому, що майже немає різниці в total timeциклах:

48 914 478 241 cycles в / с
49 877 044 086 cycles

12,155774555 seconds time elapsed в / с
12,231406658 seconds time elapsed

ПИТАННЯ:
Чи приховані пропуски L2 іншими обмежувачами продуктивності?
Якщо так, чи можете ви підказати, які лічильники подивитися, щоб зрозуміти це?


4
Як правило, будь-яка копія пам’яті, що не реалізована без змін, пов'язана з пам'яттю. Навіть коли він потрапляє лише в кеш L1. Накладні витрати будь-якого доступу до пам'яті просто набагато вищі за те, що потрібно процесору, щоб додати два та два разом. У вашому випадку ви навіть використовуєте інструкції AVX, щоб зменшити кількість інструкцій на скопійований байт. Де б ви не знайшли ваші дані (L1, L2, LLC, пам'ять), пропускна здатність пов'язаного компонента пам'яті буде вашим вузьким місцем.
— cmaster - відновити моніку

Відповіді:


5

Так, стрімер L2 дуже багато часу допомагає.

memcpy не має жодних обчислювальних затримок, які можна приховати, тому я думаю, він може дозволити собі дозволити ресурси OoO exec (розмір ROB) обробляти додаткові затримки навантаження, які ви отримуєте від більшої кількості пропусків L2, принаймні в цьому випадку, коли ви отримуєте всі звернення L3 від використовуючи робочий набір середнього розміру (1 Мбіт), який вписується в L3, не потрібно попереднього вибору, щоб здійснити хіти L3.

І єдині вказівки - це завантаження / зберігання (і петля накладних), тому вікно OoO включає навантаження попиту на досить далеко вперед.

IDK, якщо тут просто допомагають просторовий префетер L2 та префетер L1d.


Прогнозування для перевірки цієї гіпотези : зробіть свій масив більшим, щоб ви отримали пропуски L3, і, ймовірно, ви побачите різницю в загальному часі, як тільки OoO exec не буде достатньо, щоб приховати затримку завантаження для переходу до DRAM. Попередній вибір HW, що спрацьовує далі, може допомогти деяким.

Інші великі переваги попереднього завантаження HW приходять тоді, коли він може йти в ногу з вашими обчисленнями, тому ви отримуєте L2 звернень. (У циклі, який має обчислення із середньою довжиною, але не переноситься циклом залежності ланцюга.)

Навантаження на вимогу та OoO exec можуть зробити багато, що стосується використання наявної (однопоточної) смуги пропускання пам'яті, коли іншого тиску на робочу потужність ROB немає.


Також зауважте, що на процесорах Intel кожен промах кеш-пам'яті може коштувати зворотного відтворення (від RS / планувальника) залежних Uops , по одному для L1d та L2, якщо очікується, що дані надійдуть. І після цього, мабуть, ядро ​​оптимістично спамує uops, очікуючи приходу даних з L3.

(Див. Https://chat.stackoverflow.com/rooms/206639/discussion-on-question-by-beeonrope-are-load-ops-deallocated-from-the-rs-when-th і чи є завантаження операторів, розміщених з RS, коли вони відправляють, завершують чи інший час? )

Не завантаження кеш-пропуску; у цьому випадку це будуть інструкції щодо магазину. Більш конкретно, дані зберігання взагалі для порту 4. Це не має значення; використання 32-байтових сховищ та обмеження вузької смуги на пропускній здатності L3 означає, що ми не наближаємось до 1 порту 4 у цілому за годинник.


2
@ St.Antario: так? Це безглуздя; ви обмежені пам’яттю, щоб у вас не було вузького місця, тому LSD не має значення. (Це дозволяє уникнути повторного вилучення їх із загального кешу, економлячи деяку кількість енергії). Вони все ще займають місце в РОБ, поки не зможуть вийти на пенсію. Вони не такі вже й значні, але теж не незначні.
— Пітер Кордес

2
зробіть свій масив більшим, щоб ви отримали пропуски L3, і ви, мабуть, побачите різницю, я провів ряд тестів з 16MiBбуфером та 10ітераціями, і справді отримав 14,186868883 secondsvs 43,731360909 secondsі 46,76% of all LL-cache hitsvs 99,32% of all LL-cache hits; 1 028 664 372 LLC-loadsпроти 1 587 454 298 LLC-loads .
— St.Antario

4
@ St.Antario: перейменуючи реєстр! Це одна з найважливіших деталей OoO exec, особливо на ISA-програмі, незадовільній реєстру, як x86. Див. Чому мульс займає лише 3 цикли на Haswell, відмінні від таблиць інструкцій Agner? (Розгортання циклів FP з декількома акумуляторами) . І BTW, як правило, ви хочете зробити 2 вантажі, а потім 2 магазини, не завантажувати / зберігати вантаж / магазин. Більше шансів уникнути або пом'якшити 4-кількове випромінювання кіосків, оскільки пізніші навантаження (які HW повинен виявити як перекриття попередніх магазинів чи ні) віддалені.
— Пітер Кордес

2
@ St.Antario: так, звичайно. Посібник з оптимізації Agner Fog також пояснює OoO exec з перейменуванням реєстру, як і wikipedia. BTW, перейменування регістрів також дозволяє уникнути небезпек WAW, залишаючи лише справжні залежності (RAW). Таким чином, вантажі можуть навіть закінчуватися поза чергою, не чекаючи, коли попереднє завантаження закінчить писати той же архітектурний реєстр. І так, єдиний ланцюг, який переносять петлю, проходить через RCX, так що ланцюг може рухатися вперед. Ось чому адреси можуть бути готові достроково, тоді як Uops для завантаження / зберігання все ще є вузьким місцем на пропускній здатності порту 2/3.
— Пітер Кордес

3
Я здивований, що попереднє завантаження не допомогло для memcpy в L3. Я здогадуюсь, що 10/12 LFB в цьому випадку "достатньо". Здається, що це дивно: який там обмежувальний фактор? Ядро -> час L2 має бути меншим, ніж час L2 -> L3, тому в моїй ментальній моделі наявність більшої кількості буферів (більше загальної зайнятості) для другої ноги має допомогти.
— BeeOnRope

3

Так, префетер L2 HW дуже корисний!

Наприклад, знайдіть нижче результати на моїй машині (i7-6700HQ) під керуванням tinymembench . Перший стовпець результатів увімкнено з усіма попередньою програмою, другий стовпчик результатів із вимкненим потоком L2 (але всі інші попередньо завантажувачі ще увімкнено).

Цей тест використовує 32 MiB буфери джерела та призначення, які значно перевищують L3 на моїй машині, тому він буде тестувати здебільшого пропуски 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

У цих тестах випромінювання L2 ніколи не повільніше і часто майже вдвічі швидше.

Загалом, ви можете помітити такі результати в результатах:

  • На копії, як правило, більше впливає, ніж на заливки.
  • standard memsetІ STOSB fill(вони зводяться до того ж на цій платформі) є найменш постраждали, завантажується попередньо результатом є лише кілька% швидше , ніж без нього .
  • Стандартна memcpy- це, мабуть, єдина копія, яка використовує 32-байтові інструкції AVX, і вона належить до числа найменш постраждалих від копій - але попереднє завантаження все ще на 40% швидше, ніж без.

Я також спробував увімкнути та вимкнути інші три попередні програми, але вони, як правило, майже не мали вимірюваного ефекту для цього показника.


(Факт забави: vmovdqaAVX1 незважаючи на те, що він "цілий".) Ви вважаєте, що цикл ОП давав меншу пропускну здатність, ніж memcpy glibc? І тому 12 LFB було достатньо, щоб не відставати від навантажень попиту, що йдуть до L3, не скориставшись додатковим MLP з надбудови L2 <-> L3, який стример L2 може залишати зайнятим? Це, мабуть, різниця у вашому тесті. L3 повинен працювати з тією ж швидкістю, що і серцевина; у вас обох є чотириядерні мікроархітектури Skylake-клієнта, напевно, подібні затримки L3?
— Пітер Кордес

@PeterCordes - вибачте, я, мабуть, мав би бути зрозумілим: цей тест був між 32 буферами MiB, тому він тестує хіти DRAM, не L3. Я хоча tmb виводить розмір буфера, але я бачу, що це не так - ой! Це було навмисно: я не намагався пояснити точно сценарій 512 KiB ОП, а просто відповів на заголовок питання про те, чи корисний стример L2 за сценарієм, який показує, що він є. Я думаю, що я використовував менший розмір буфера, я міг більш-менш відтворити результати (я вже бачив подібний результат, про який uarch-benchговорилося в коментарях).
— BeeOnRope

1
Я додав розмір буфера до відповіді.
— BeeOnRope

1
@ St.Antario: Ні, це не проблема. Не маю уявлення, чому ви думаєте, що це може бути проблемою; це не так, як за змішування інструкцій AVX1 та AVX2 існує штраф. Суть мого коментаря полягала в тому, що для цього циклу потрібен лише AVX1, але ця відповідь згадує, використовуючи інструкції AVX2. Intel випадково розширила шляхи завантаження / зберігання даних L1d на 32 байти одночасно з введенням AVX2, тому ви можете використовувати доступність AVX2 як частину способу вибору реалізації memcpy, якщо ви робите відправлення по роботі ...
— Петро Корди

1
Як ви вимкнули префетер і який? Це було software.intel.com/en-us/articles/… ? Форум software.intel.com/en-us/forums/intel-isa-extensions/topic/… говорить, що деякі біти мають різний зміст.
— osgx
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.