Trình tải trước L2 HW có thực sự hữu ích không?


10

Tôi đang sử dụng Whiskey Lake i7-8565U và phân tích các bộ đếm hoàn hảo và thời gian để sao chép 512 KiB dữ liệu (gấp đôi so với kích thước bộ đệm L2) và gặp phải một số hiểu lầm về công việc của trình tải trước L2 HW.

Trong Intel Manual Vol.4 MSR có MSR 0x1A4, bit 0 của để kiểm soát trình tải trước L2 HW (1 để vô hiệu hóa).


Hãy xem xét các điểm chuẩn sau:

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

Xem xét 2 lần biên dịch main.c

Tôi .

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

Tôi nhận thấy quầy:

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

đề xuất rằng MSR vô hiệu hóa trình tải trước L2 HW đang được áp dụng. Ngoài ra các công cụ khác liên quan đến l2 / LLC khác nhau đáng kể. Sự khác biệt là tái sản xuất trên các lần chạy khác nhau . Vấn đề là hầu như không có sự khác biệt trong total timevà chu kỳ:

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

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

HỎI:
L2 có bị ẩn bởi các bộ hạn chế hiệu suất khác không?
Nếu vậy, bạn có thể đề nghị những gì quầy để nhìn vào để hiểu nó?


4
Như một quy tắc tự nhiên: Bất kỳ bản sao bộ nhớ được triển khai không sâu thẳm đều bị ràng buộc bộ nhớ. Ngay cả khi nó chỉ đạt L1 cache. Tổng chi phí của bất kỳ truy cập bộ nhớ nào chỉ đơn giản là cao hơn nhiều so với những gì CPU cần để thêm hai và hai lại với nhau. Trong trường hợp của bạn, bạn thậm chí đang sử dụng các hướng dẫn AVX để giảm lượng hướng dẫn trên mỗi byte được sao chép. Bất cứ nơi nào dữ liệu của bạn được tìm thấy (L1, L2, LLC, bộ nhớ), thông lượng của thành phần bộ nhớ liên quan sẽ là nút cổ chai của bạn.
cmaster - phục hồi monica

Câu trả lời:


5

Vâng, bộ truyền phát L2 thực sự hữu ích rất nhiều thời gian.

memcpy không có bất kỳ độ trễ tính toán nào để ẩn, vì vậy tôi đoán nó có thể đủ khả năng để cho OoO thực thi tài nguyên (kích thước ROB) xử lý độ trễ tải thêm mà bạn nhận được từ nhiều lỗi L2 hơn, ít nhất là trong trường hợp này bạn nhận được tất cả các lần truy cập L3 từ sử dụng bộ làm việc cỡ trung bình (1MiB) phù hợp với L3, không cần tìm nạp trước để thực hiện các lần truy cập L3.

Và các hướng dẫn duy nhất là tải / lưu trữ (và vòng lặp trên đầu), vì vậy cửa sổ OoO bao gồm tải nhu cầu cho khá xa về phía trước.

IDK nếu trình tải trước không gian L2 và trình tải trước L1d đang giúp bất kỳ ở đây.


Dự đoán để kiểm tra giả thuyết này : làm cho mảng của bạn lớn hơn để bạn bỏ lỡ L3 và có thể bạn sẽ thấy sự khác biệt về thời gian nói chung một khi OoO exec không đủ để che giấu độ trễ tải của DRAM. CTNH nạp trước kích hoạt xa hơn có thể giúp một số.

Những lợi ích lớn khác của việc tìm nạp trước CTNH đến khi nó có thể theo kịp tính toán của bạn, do đó bạn nhận được các lượt truy cập L2. (Trong một vòng lặp có tính toán với chuỗi phụ thuộc có độ dài trung bình nhưng không mang theo vòng lặp.)

Tải nhu cầu và bộ thực thi OoO có thể làm được rất nhiều khi sử dụng băng thông bộ nhớ có sẵn (một luồng), khi không có áp lực khác đối với dung lượng ROB.


Cũng lưu ý rằng trên CPU Intel, mỗi lần bỏ lỡ bộ đệm có thể tốn một lần phát lại phía sau (từ RS / bộ lập lịch) của các vòng lặp phụ thuộc , mỗi lần cho L1d và L2 bị mất khi dữ liệu dự kiến ​​sẽ đến. Và sau đó, rõ ràng phần lõi lạc quan sẽ spam uops trong khi chờ dữ liệu đến từ L3.

(Xem https://chat.stackoverflow.com/rooms/206639/discussion-on-question-by-beeonrope-are-load-ops-deallocated-from-the-rs-when-thAre ops tải deallocated từ RS khi họ gửi đi, hoàn thành hoặc một thời gian khác? )

Không phải bộ nhớ cache-chính nó tải; trong trường hợp này nó sẽ là hướng dẫn cửa hàng. Cụ thể hơn, uop dữ liệu lưu trữ cho cổng 4. Điều đó không quan trọng ở đây; sử dụng các cửa hàng 32 byte và tắc nghẽn trên băng thông L3 có nghĩa là chúng ta không ở gần 1 cổng 4 uop mỗi đồng hồ.


2
@ St.Antario: hả? Điều đó không có ý nghĩa; bạn bị giới hạn bộ nhớ nên bạn không có nút cổ chai phía trước nên LSD không liên quan. (Nó tránh tải lại chúng từ bộ đệm uop, tiết kiệm năng lượng). Họ vẫn dành không gian trong ROB cho đến khi họ có thể nghỉ hưu. Chúng không phải quan trọng, nhưng không phải là không đáng kể trong hai.
Peter Cordes

2
làm cho mảng của bạn lớn hơn để bạn nhận được L3 bỏ lỡ và có thể bạn sẽ thấy một sự khác biệt Tôi đã chạy một số bài kiểm tra với 16MiBbộ đệm và các 10lần lặp và thực sự có 14,186868883 secondsvs 43,731360909 seconds46,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: bằng cách đăng ký đổi tên! Đây là một trong những phần quan trọng nhất của OoO exec, đặc biệt là trên một ISA nghèo đăng ký như x86. Xem Tại sao mulss chỉ mất 3 chu kỳ trên Haswell, khác với bảng hướng dẫn của Agner? (Bỏ các vòng lặp FP với nhiều bộ tích lũy) . Và BTW, thông thường bạn muốn thực hiện 2 tải sau đó 2 cửa hàng, không tải / lưu trữ tải / lưu trữ. Cơ hội tốt hơn để tránh hoặc giảm nhẹ các quầy hàng răng cưa 4k vì các lần tải sau (mà CTNH phải phát hiện có chồng lấp các cửa hàng trước đó hay không) ở xa hơn.
Peter Cordes

2
@ St.Antario: vâng, tất nhiên. Hướng dẫn tối ưu hóa của Agner Fog cũng giải thích điều hành OoO với việc đổi tên đăng ký, wikipedia cũng vậy. BTW, đăng ký đổi tên cũng tránh các mối nguy WAW, chỉ để lại các phụ thuộc thực sự (RAW). Vì vậy, tải thậm chí có thể hoàn thành ngoài trật tự, mà không cần chờ tải trước đó hoàn thành việc viết cùng một thanh ghi kiến ​​trúc. Và đúng vậy, chuỗi dep mang theo vòng lặp duy nhất là thông qua RCX, để chuỗi đó có thể chạy về phía trước. Đó là lý do tại sao các địa chỉ có thể sẵn sàng sớm, trong khi các tải / cửa hàng vẫn bị tắc nghẽn trên thông lượng cổng 2/3.
Peter Cordes

3
Tôi ngạc nhiên rằng việc tìm nạp trước không giúp ích gì cho memcpy trong L3. Tôi đoán 10/12 LFB là "đủ" trong trường hợp đó. Có vẻ kỳ lạ mặc dù: yếu tố hạn chế ở đó là gì? Lõi -> Thời gian L2 phải nhỏ hơn thời gian L2 -> L3, vì vậy trong mô hình tinh thần của tôi có nhiều bộ đệm hơn (tổng số chiếm nhiều hơn) cho chặng thứ hai sẽ giúp ích.
BeeOnRope

3

Vâng, trình tải trước L2 HW rất hữu ích!

Ví dụ: tìm kết quả bên dưới trên máy của tôi (i7-6700HQ) đang chạy tinymembench . Cột kết quả đầu tiên là với tất cả các trình tìm nạp trước, cột kết quả thứ hai sẽ tắt trình phát trực tuyến L2 (nhưng tất cả các trình tìm nạp trước khác vẫn bật).

Thử nghiệm này sử dụng 32 bộ đệm nguồn và đích MiB, lớn hơn nhiều so với L3 trên máy của tôi, vì vậy nó sẽ thử nghiệm chủ yếu là bỏ qua 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

Trong các thử nghiệm này, bộ truyền phát L2 không bao giờ chậm hơn và thường nhanh gấp gần hai lần.

Nói chung, bạn có thể nhận thấy các mẫu sau trong kết quả:

  • Các bản sao nói chung dường như bị ảnh hưởng nhiều hơn so với điền.
  • Các standard memsetSTOSB fill(những đun sôi xuống để điều tương tự trên nền tảng này) là những ảnh hưởng nhất, với kết quả tìm nạp trước chỉ là một vài% nhanh hơn so với bên ngoài.
  • Standard memcpycó lẽ là bản sao duy nhất ở đây sử dụng các hướng dẫn AVX 32 byte và nó là một trong những bản sao ít bị ảnh hưởng nhất - nhưng việc tìm nạp trước vẫn nhanh hơn ~ 40% so với không có.

Tôi cũng đã thử bật và tắt ba trình nạp trước khác, nhưng nhìn chung chúng hầu như không có tác dụng nào cho điểm chuẩn này.


(Sự thật thú vị: vmovdqaAVX1 mặc dù là "số nguyên".) Bạn có nghĩ rằng vòng lặp của OP đã cho băng thông thấp hơn so với memcpy glibc không? Và đó là lý do tại sao 12 LFB đủ để theo kịp nhu cầu đến L3, mà không tận dụng MLP bổ sung từ siêu sao L2 <-> L3 mà bộ truyền phát L2 có thể tiếp tục chiếm giữ? Đó có lẽ là sự khác biệt trong bài kiểm tra của bạn. L3 nên chạy ở cùng tốc độ với lõi; Cả hai bạn đều có cấu trúc vi mô tương đương Skylake-client bốn lõi nên có thể có độ trễ L3 tương tự?
Peter Cordes

@PeterCordes - xin lỗi tôi có lẽ nên rõ ràng: thử nghiệm này nằm trong khoảng 32 bộ đệm MiB, vì vậy nó đang thử nghiệm các lần truy cập DRAM chứ không phải các lần truy cập L3. Tôi mặc dù tmb xuất kích thước bộ đệm, nhưng tôi thấy nó không - rất tiếc! Đó là cố ý: Tôi đã không cố gắng giải thích chính xác kịch bản 512 KiB của OP, nhưng chỉ trả lời câu hỏi tiêu đề về việc bộ truyền phát L2 có hữu ích với kịch bản cho thấy nó không. Tôi đoán rằng tôi đã sử dụng kích thước bộ đệm nhỏ hơn, tôi có thể tái tạo ít nhiều kết quả (tôi đã thấy một kết quả tương tự uarch-benchđược đề cập trong các bình luận).
BeeOnRope

1
Tôi đã thêm kích thước bộ đệm cho câu trả lời.
BeeOnRope

1
@ St.Antario: Không, đó không phải là vấn đề. Không biết tại sao bạn nghĩ rằng nó có thể một vấn đề; nó không giống như bất kỳ hình phạt nào cho việc trộn các hướng dẫn AVX1 và AVX2. Quan điểm của nhận xét của tôi là vòng lặp này chỉ yêu cầu AVX1, tuy nhiên câu trả lời này đề cập đến việc sử dụng các hướng dẫn AVX2. Intel tình cờ mở rộng đường dẫn tải / lưu trữ dữ liệu L1d lên 32 byte cùng lúc với việc giới thiệu AVX2, vì vậy bạn có thể sử dụng AVX2 sẵn có như một phần trong cách bạn chọn triển khai memcpy nếu bạn đang thực hiện thời gian chạy ...
Peter Cordes

1
Làm thế nào bạn tắt trình nạp trước và cái nào? Có phải đó là phần mềm.intel.com / en-us / articles / trên ? Diễn đàn phần mềm.intel.com/en-us/forums/intel-isa-extensions/topic/ khuyên nói rằng một số bit có ý nghĩa khác nhau.
osgx
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.