0

私はメモリパフォーマンスパズルを持っています。メイン メモリから 1 バイトをフェッチするのにかかる時間と、さまざまな BIOS 設定とメモリ ハードウェア パラメータがそれにどのように影響するかをベンチマークしようとしています。Windows 用に次のコードを作成しました。このコードは、ループ内で別のバッファーを読み取ってキャッシュをフラッシュし、その後、さまざまなストライドで一度に 1 バイトずつターゲット バッファーを読み取ります。ストライドがキャッシュ ラインのサイズになると、各読み取りがメイン メモリに送られるため、それが測定しようとしている量になると思います。ベンチマーク コードは次のとおりです (バッファーのサイズはストライド x 1MB であり、スレッドをコア 1 に固定していることに注意してください)。

#include <stdio.h>
#include <memory.h>

#define NREAD       (1024*1024)
#define CACHE_SIZE  (50*1024*1024)

char readTest(int stride) {
    LARGE_INTEGER frequency;
    LARGE_INTEGER start;
    LARGE_INTEGER end;
    int rep, i,ofs;
    double time, min_time=1e100, max_time=0.0, mean_time=0.0;
    char *buf = (char *)malloc(NREAD*stride);
    char *flusher = (char *)malloc(CACHE_SIZE); 
    char jnk=0;
    for(rep=0; rep<255; rep++) {
        // read the flusher to flush the cache
        for(ofs = 0; ofs<CACHE_SIZE; ofs+=64) jnk+=flusher[ofs];
        if (QueryPerformanceFrequency(&frequency) == FALSE) exit(-1);
        if (QueryPerformanceCounter(&start) == FALSE) exit(-2);

        // here's the timed loop
        for(ofs=0; ofs<NREAD*stride; ofs+=stride) jnk += buf[ofs];

        if (QueryPerformanceCounter(&end) == FALSE) exit(-3);
        time = (double)(end.QuadPart - start.QuadPart) / (double)frequency.QuadPart*1e6;
        max_time = time > max_time ? time : max_time;
        min_time = time < min_time ? time : min_time;
        mean_time += time;
    }
    mean_time /= 255;
    printf("Stride = %4i, Max: %6.0f us, Min: %6.0f us, Mean: %6.0f us, B/W: %4.0f MB/s\n", stride, max_time, min_time, mean_time, NREAD/min_time);
    free(buf);
    free(flusher);
    return jnk;
}

int main(int argc, char* argv[]) {
    SetThreadAffinityMask(GetCurrentThread(), 1);  // pin to core 1 to avoid weirdness
    // run the tests
    readTest(1);    readTest(2);    readTest(4);    readTest(6);    readTest(8);
    readTest(12);   readTest(16);   readTest(24);   readTest(32);   readTest(48);
    readTest(64);   readTest(96);   readTest(128);  readTest(192);  readTest(256);
    readTest(384);  readTest(512);  readTest(768);  readTest(1024); readTest(1536);
    return 0;
}

タイミングが設定された内側のループは、次のように組み立てられます。

        // here's the timed loop
        for(ofs=0; ofs<NREAD*stride; ofs+=stride) jnk += buf[ofs];
00F410AF  xor         eax,eax  
00F410B1  test        edi,edi  
00F410B3  jle         readTest+0C2h (0F410C2h)  
00F410B5  mov         edx,dword ptr [buf]  
00F410B8  add         bl,byte ptr [eax+edx]  
00F410BB  add         eax,dword ptr [stride]  
00F410BE  cmp         eax,edi  
00F410C0  jl          readTest+0B5h (0F410B5h)  

これをデュアル プロセッサの E5-2609 マシンで実行した結果は次のとおりです。

Stride =    1, Max:   2362 us, Min:    937 us, Mean:    950 us, B/W: 1119 MB/s
Stride =    2, Max:   1389 us, Min:    968 us, Mean:    978 us, B/W: 1083 MB/s
Stride =    4, Max:   1694 us, Min:   1026 us, Mean:   1037 us, B/W: 1022 MB/s
Stride =    6, Max:   2418 us, Min:   1098 us, Mean:   1124 us, B/W:  955 MB/s
Stride =    8, Max:   2835 us, Min:   1234 us, Mean:   1252 us, B/W:  850 MB/s
Stride =   12, Max:   4203 us, Min:   1527 us, Mean:   1559 us, B/W:  687 MB/s
Stride =   16, Max:   5130 us, Min:   1816 us, Mean:   1849 us, B/W:  577 MB/s
Stride =   24, Max:   7370 us, Min:   2408 us, Mean:   2449 us, B/W:  435 MB/s
Stride =   32, Max:  10039 us, Min:   2901 us, Mean:   3014 us, B/W:  361 MB/s
Stride =   48, Max:  14248 us, Min:   4652 us, Mean:   4731 us, B/W:  225 MB/s
Stride =   64, Max:  19149 us, Min:   6340 us, Mean:   6447 us, B/W:  165 MB/s
Stride =   96, Max:  28848 us, Min:   8475 us, Mean:   8615 us, B/W:  124 MB/s
Stride =  128, Max:  37449 us, Min:   9900 us, Mean:  10160 us, B/W:  106 MB/s
Stride =  192, Max:  51718 us, Min:  11282 us, Mean:  11563 us, B/W:   93 MB/s
Stride =  256, Max:  62193 us, Min:  11558 us, Mean:  11924 us, B/W:   91 MB/s
Stride =  384, Max:  86943 us, Min:  11829 us, Mean:  12260 us, B/W:   89 MB/s
Stride =  512, Max: 108661 us, Min:  11847 us, Mean:  12401 us, B/W:   89 MB/s
Stride =  768, Max: 167951 us, Min:  11797 us, Mean:  12946 us, B/W:   89 MB/s
Stride = 1024, Max: 211700 us, Min:  12893 us, Mean:  13979 us, B/W:   81 MB/s
Stride = 1536, Max: 332214 us, Min:  12967 us, Mean:  15077 us, B/W:   81 MB/s

ここに私の質問があります:

  • ストライドがキャッシュラインのサイズ (Sandy Bridge の場合は 64 バイト) を超えた後もパフォーマンスが低下し続けるのはなぜですか? 読み取りごとにキャッシュライン転送が必要なほどストライドが大きくなると、最悪のパフォーマンスが発生すると思いますが、その後でも時間が 2 倍に増加します...何が欠けていますか?
  • 最大時間 (ループの最初の繰り返しで発生) が最小時間の 2 ~ 4 倍長いのはなぜですか? 繰り返しごとにキャッシュをフラッシュしています...
4

2 に答える 2

2

メモリが追跡される粒度は、キャッシュラインだけではありません。仮想アドレスから物理アドレスへの変換は、ページの粒度で行われます。お使いのシステムはほぼ確実に 4k ページを使用しています。

ストライド 64 では、1 ページあたり 64 エントリを取得するため、16384 ページになります。L2 TLB はこれらのページのうち 512 ページしか追跡できないため、新しいページごとに (64 回目のアクセスごとに) L2 TLB ミスが発生します。

1024 のストライドで、1 ページあたり 4 つのエントリを取得するため、262144 ページになります。これで、4 回目のアクセスごとに L2 TLB ミスが発生します。

tl;dr: TLB ミスが命取りです。スタック オーバーフローに茶葉を読み取らせる代わりに、パフォーマンス カウンターを使用してこれを直接観察できます。また、1 つ以上の「スーパーページ」を使用してシステムにバッファーを割り当てさせ、TLB 範囲を拡張することもできます (ただし、システムによってこの機能のサポートの程度は異なります)。

于 2013-11-05T18:48:09.840 に答える
1
  1. プリフェッチによるライン サイズの後も劣化は続きます。安定したストリームでキャッシュ ラインをまたぐ限り (またはストライドを使用しても)、次の数ラインを引き継ぐ HW プリフェッチャーの利点を享受できます。L2 ストリーマーは、アクセスのストリームよりも高速に実行されるため、特に便利です。
    ただし、ストライドが 128 バイトを超えると、ストリーム プリフェッチャーよりも先に実行を開始する必要があり、すべてのアクセスで完全な遅延が発生します。
    これが実際に当てはまることを確認するには-プリフェッチを無効にします(システムがBIOSでこれを許可することを願っています)編集:Stephenは、TLBルックアップへのアクセスの比率についても非常に良い点を挙げています-これは大きな進歩を説明します. ストライドあたりの時間をプロットすると、TLB ミス率による強い傾向が見られ、さらに 64 バイトから 128 バイトのストライドにジャンプすることに賭けても構わないと思います。

  2. コールド TLB が原因で、最初のイテレーションが長くなると思います。これをテストするには、それもフラッシュするか (ハード..)、またはウォームアップ反復を実行して 2 番目のものからのみ測定します。

于 2013-11-05T18:46:07.400 に答える