L2 HWプリフェッチャーは本当に役に立ちますか?


10

私はWhisky Lake i7-8565Uで、512 KiBのデータ(L2キャッシュサイズの2倍)をコピーするためのパフォーマンスカウンターと時間を分析しており、L2 HWプリフェッチャーの作業に関して誤解に直面しています。

でインテル・マニュアル第4巻MSR MSRがある0x1A4ビット0(無効にする1)L2 HWプリフェッチャをcontrolloingするためのものであるの。


次のベンチマークを検討してください。

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 v / s
25 019 148 253 cycle_activity.stalls_l2_miss

MSR無効化L2 HWプリフェッチャーが適用されていることを示唆しています。また、他のl2 / LLC関連のものは大幅に異なります。違いは、実行ごとに再現可能です。問題はtotal timeandサイクルにほとんど違いがないことです:

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

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

質問:
L2ミスは他のパフォーマンスリミッターによって隠されていますか?
もしそうなら、それを理解するためにどのカウンターを見るべきかを提案できますか?


4
経験則として、abysmally以外で実装されたメモリコピーはすべてメモリバウンドです。L1キャッシュのみにヒットする場合でも。メモリアクセスのオーバーヘッドは、CPUが2と2を加算するのにかかるオーバーヘッドよりもはるかに高くなります。あなたのケースでは、AVX命令を使用して、コピーされたバイトあたりの命令の量を減らしています。データが見つかった場所(L1、L2、LLC、メモリ)はどこでも、関連するメモリコンポーネントのスループットがボトルネックになります。
— cmaster-

回答:


5

はい、L2ストリーマーは多くの場合、本当に役に立ちます。

memcpyには非表示にする計算レイテンシがないため、少なくともL3ヒットをすべて取得するこの場合は、OoO execリソース(ROBサイズ)がより多くのL2ミスから得られる追加のロードレイテンシを処理できる余裕があると思いますL3に適合する中規模のワーキングセット(1MiB)を使用しているため、L3ヒットを発生させるためにプリフェッチする必要はありません。

そして、唯一の命令はロード/ストア(およびループオーバーヘッド)であるため、OoOウィンドウにはかなり先の需要ロードが含まれます。

L2空間プリフェッチャーとL1dプリフェッチャーがここで役立つ場合はIDK。


この仮説をテストするための予測:アレイを大きくしてL3ミスを取得すると、OoO execがDRAMに到達するまでのロードレイテンシを隠すのに十分ではない場合、全体の時間に違いが見られるでしょう。さらに先にHWプリフェッチトリガーが役立つ場合があります。

HWプリフェッチのその他の大きなメリットは、計算に追いつくことができるため、L2ヒットが得られることです。(中程度の長さの計算でループが運ぶ依存関係チェーンを持たないループ内。)

ROB容量に他のプレッシャーがない場合、デマンドロードとOoO execは、利用可能な(シングルスレッド)メモリ帯域幅を使用する限り、多くのことを実行できます。


また、Intel CPUでは、すべてのキャッシュミスにより、依存する uopsの(RS /スケジューラーからの)バックエンドの再生にコストがかかる可能性があることに注意してください。そしてその後、明らかにコアはL3からデータが到着するのを待つ間、楽観的にuopsをスパムします。

(参照https://chat.stackoverflow.com/rooms/206639/discussion-on-question-by-beeonrope-are-load-ops-deallocated-from-the-rs-when-thをしてから割り当て解除負荷OPSていますRSは、派遣、完了、またはその他のときに?)

キャッシュミスロード自体ではありません。この場合は、ストアの指示になります。具体的には、ポート4のstore-data uopです。ここでは問題ありません。L3帯域幅で32バイトのストアとボトルネックを使用することは、クロックあたり1ポート4 uopに近くないことを意味します。


2
@ St.Antario:ハァッ?それは意味がありません。あなたはメモリに縛られているので、フロントエンドのボトルネックがないので、LSDは無関係です。(uopキャッシュからの再フェッチを回避し、電力を節約します)。彼らは引退することができるまでROBのスペースを取ります。彼らはじゃないことを重要な、しかしどちらか無視できません。
— Peter Cordes

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 。
— セントアンタリオ

4
@ St.Antario:レジスタの名前変更により!これはOoO execの最も重要な部分の1つであり、特にx86のようなレジスターの少ないISAで特に重要です。参照してくださいmulssがAgnerの命令テーブルとは異なる、ハスウェルにのみ3サイクルを要しないのはなぜ?(複数のアキュムレータを使用したFPループの展開)。ところで、通常は、ロード/ストアロード/ストアではなく、2つのロードを実行してから2つのストアを実行する必要があります。後のロード(HWが前のストアとオーバーラップしているかどうかを検出する必要がある)が遠く離れているため、4kエイリアシングストールを回避または軽減する可能性が高くなります。
— Peter Cordes

2
@ St.Antario:はい、もちろん。Agner Fogの最適化ガイドは、OoO execとレジスタ名の変更についても説明し、Wikipediaについても説明しています。ところで、レジスタの名前変更はWAWの危険を回避し、真の依存関係(RAW)のみを残します。したがって、前のロードが同じアーキテクチャレジスタへの書き込みを完了するのを待たずに、ロードが順不同で完了することさえあります。そして、はい、ループを運ぶdepチェーンはRCXを介したものだけなので、そのチェーンは先に実行できます。ロード/ストアのuopsがポート2/3のスループットでまだボトルネックになっているのに、アドレスが早く準備できるのはそのためです。
— Peter Cordes

3
プリフェッチがL3のmemcpyに役に立たなかったことに驚いています。その場合、10/12 LFBは「十分」だと思います。奇妙に思われる:そこの制限要因は何ですか?コア-> L2の時間はL2-> L3の時間よりも短いはずなので、私のメンタルモデルでは、2番目のレッグ用のバッファーを増やす(合計占有率を増やす)ことが役立つはずです。
— BeeOnRope

3

はい、L2 HWプリフェッチャーは非常に役立ちます。

たとえば、実行している私のマシン上の結果(i7-6700HQ)の下に見つけるtinymembenchを。結果の最初の列はすべてのプリフェッチャーがオンで、2番目の結果列は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ストリーマーを使用しても速度が低下することはなく、多くの場合2倍近く高速です。

一般に、結果には次のパターンが見られます。

  • 一般に、コピーは塗りつぶしよりも影響を受けるようです。
  • (このプラットフォーム上で同じ事までこれら沸騰)プリフェッチ結果はわずか数%速くない場合よりもあることで、影響を受けた以上です。standard memsetSTOSB fill
  • 標準は、memcpyおそらく唯一の用途32バイトAVX命令することをここにコピーされ、それは少なくともコピーの影響を受けるの中で-しかしにプリフェッチすることは〜40%速くない場合よりもまだあります。

他の3つのプリフェッチャーのオンとオフも試してみましたが、これらのベンチマークでは通常、測定可能な影響はほとんどありませんでした。


(vmovdqa面白い事実:「整数」であるにもかかわらずAVX1です。)OPのループがglibc memcpyよりも低い帯域幅を提供していたと思いますか?そして、それがL2ストリーマーが占有し続けることができるL2 <-> L3スーパーキューからの追加のMLPを利用せずに、L3への需要負荷に対応するのに12 LFBで十分である理由ですか?それはおそらくあなたのテストの違いです。L3はコアと同じ速度で実行する必要があります。あなたはどちらもクアッドコアのSkylakeクライアントと同等のマイクロアーキテクチャーを持っているので、おそらく同じようなL3レイテンシーですか?
— Peter Cordes

@PeterCordes-申し訳ありませんが、おそらく明確でした。このテストは32 MiBバッファの間で行われたため、L3ヒットではなくDRAMヒットをテストしています。tmbはバッファサイズを出力しましたが、そうではありません-おっと!これは意図的なものでした。OPの512 KiBシナリオを正確に説明するのではなく、L2ストリーマーが有用かどうかを示すシナリオで見出しの質問に答えるだけです。バッファサイズを小さくしたので、結果を再現することができたと思います(uarch-benchコメントで言及されているのと同じような結果が既に見られました)。
— BeeOnRope

1
バッファサイズを回答に追加しました。
— BeeOnRope

1
@ St.Antario:いいえ、問題ありません。それが問題であると考える理由がわかりません。AVX1とAVX2の命令を混在させることによるペナルティはないようです。私のコメントの要点は、このループはAVX1のみを必要とするということでしたが、この回答はAVX2命令の使用について言及しています。IntelはAVX2の導入と同時にL1dロード/ストアデータパスを32バイトに拡大したため、ランタイムディスパッチを実行している場合、memcpy実装を選択する方法の一部としてAVX2の可用性を使用する可能性があります...
— Peterコルド

1
どのようにしてプリフェッチャーをオフにしましたか?それはsoftware.intel.com/en-us/articles/…でしたか?フォーラムsoftware.intel.com/en-us/forums/intel-isa-extensions/topic/…は、一部のビットは異なる意味を持つと述べています。
— osgx
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.