実験的なキャッシュの特徴付け:ワークショップ

キャッシュメモリの実験的特性に関する最初の記事は、やや珍しい方法で生まれました。 lmbench



のユーティリティをlmbench



、同じ3つのグラフを取得しましたが、調査中のシステムに関する情報をどれだけ引き出すことができるのか疑問に思いました。 キャッシュとTLBのいくつかの特性を定義した後、これらのチャートを宿題として生徒に設定します。生徒が見落としているものを発見できると期待しています。 一般に、学生は私を失望させ、グラフ上のステップの勾配との関連性の関係すら気付かなかった。 学期の終わりに、私は彼らに私の決定を伝えます。 それまでに忘れられないように、私はその記事をまとめました。



次に、 Yekverは、グラフを手動で分析することなく、キャッシュの特性を自動的に決定するWindows用の単純なプログラムのアイデアを提案しました。 (さらに、Windows用のlmbench



バージョンはありません。)時間を測定するために、 __rdtsc



関数を使用します。これは、最後のプロセッサリセット以降の64ビット数のクロックサイクルを返します。 まず、実行時間と任意の負荷で必要なクロックサイクル数を測定して、プロセッサのクロック速度を決定します。 次に、メモリアクセス時間を計算するために、使用したクロックサイクル数をプロセッサのクロック速度で除算します。



前の実験と同様に、4KBから512MBまでのさまざまなサイズのデータ​​を取得し、何百万回も配列を調べ、結果を平均します。 著者lat_mem_rd



例に従って、ロードサイクルでの追加操作の影響を最小限に抑えるために、操作p=(void**)*p;



を使用しp=(void**)*p;



、これは1つのマシンコマンドにコンパイルされ、256倍に拡張されるため、サイクルの最初に戻ることは比較的まれです。



実験中に、取得した最後の3つの結果の履歴を保存し、それらのジャンプを10%以上検出します-これらはステップのエッジです。 テストされたすべてのステップサイズについて検出されたすべてのステップを保存し、時間測定の終了後、取得したデータを分析します。 グラフ上のステップの位置によってスタックの特性を決定する方法論は、以前の記事で詳細に説明されています。 ステップをより正確に検出するには、固定しきい値の代わりに標準偏差を使用できます。



このプログラムは、構成とキャッシュサイズが異なる3台のコンピューターでテストされました。 ご覧のとおり、大きなアレイは物理メモリに完全には収まらず、したがってアレイへのアクセス時間は完全には予測できません。ディスクからデータをダウンロードする際の遅延によって決まります。 したがって、前の実験とは異なり、512 MBを超えるアレイでは測定が行われませんでした。

この表は、さまざまなコンピューターでプログラムを実行した結果を示しています。



Pentium4 630、1GB RAM
クロックレート:2993 MHz

L1サイズ:16 KB、レイテンシ:4.10サイクル(1.37 ns)

ウェイサイズ:分。 2 KB、最大 2 KB

TLBサイズ:64、レイテンシ:18.37サイクル(6.14 ns)

ウェイサイズ:分。 1、最大 8

L2サイズ:1920 KB、レイテンシ:28.98サイクル(9.68 ns)

ウェイサイズ:分。 384 KB、最大 384 KB

Core2 T7200、1GB RAM
クロックレート:1995 MHz

L1サイズ:32 KB、レイテンシ:3.02サイクル(1.51 ns)

ウェイサイズ:分。 8 KB、最大 8 KB

L2サイズ:1024 KB、レイテンシ:14.87サイクル(7.45 ns)

ウェイサイズ:分。 256 KB、最大 256 KB

L3サイズ:3840 KB、レイテンシ:14.62サイクル(7.33 ns)

ウェイサイズ:分。 2816 KB、最大 2816 KB

Core2 Duo T5250、1GB RAM
クロックレート:1496 MHz

L1サイズ:32 KB、レイテンシ:3.04サイクル(2.03 ns)

ウェイサイズ:分。 8 KB、最大 8 KB

L2サイズ:1280 KB、レイテンシ:14.82サイクル(9.91 ns)

ウェイサイズ:分。 4 KB、最大 0 KB





コアプロセッサ上のTLBへのアクセスに起因する追加の遅延は非常にわずかであったため、プログラムの1つが無視できると判断し、TLBにまったく気付かなかった。 別のケースでは、アクセス時間の変動は追加の遅延自体と同じ桁であることが判明したため、プログラムはTLBのアクセス時間特性の規則性に気づきませんでした。 つまり、プログラムに組み込まれたアクセスタイムスケジュールの分析メカニズムを改善する余地があります。



これはおそらく、キャッシュの特性を自動的に検出するWindows用の最初の無料プログラムです。 自分で実行する場合は、覚えておいてください。バックグラウンドロードによってグラフィックスに導入されるノイズに非常に敏感です。 より信頼性の高い結果を得るには、測定時に他のすべてのプログラムを閉じます。



 #include <math.h> #include <stdio.h> #include <intrin.h> #include <windows.h> //   :   lat_mem_rd int step(int k) { if(k < 1024) k = k * 2; else if(k < 4*1024) k += 1024; else { int s; for (s = 32*1024; s <= k; s *= 2) ; k += s / 16; } return k; } #define TWICE(x) xx #define MB (1024*1024) int main() { //    LARGE_INTEGER perfcnt1, perfcnt2; __int64 tsc1, tsc2; QueryPerformanceCounter(&perfcnt1); tsc1=__rdtsc(); //  (   ) Sleep(500); //  QueryPerformanceCounter(&perfcnt2); tsc2=__rdtsc(); perfcnt2.QuadPart -= perfcnt1.QuadPart; QueryPerformanceFrequency(&perfcnt1); //  const double MHz = (double)(tsc2-tsc1)*perfcnt1.QuadPart/perfcnt2.QuadPart/1e6; printf("Clock rate: %.0f MHz\n", MHz); //      //      typedef struct segment_t segment; struct segment_t { int size_l, size_r; //  ,   double level, total; //  ,   int width; // ,    segment* next; }; //      typedef struct { int step_size_bytes; segment data; } segments; //     segments allsegs[]={{128}, {256}, {512}, {1024}, {2048}, {4096}, {0}}; for(segments *cursegs = allsegs; int step_size = cursegs->step_size_bytes/sizeof(void*); cursegs++) { printf("\rTesting stride: %d \n", cursegs->step_size_bytes); int iters = 1<<28; //       int state = 0; //   -   segment* curseg = &(cursegs->data); //   //    ,     int a_size_bytes, b_size_bytes; double a, b; //          for(int arr_size_bytes = 1<<12; arr_size_bytes <= 1<<29; arr_size_bytes = step(arr_size_bytes)) { const int arr_size = arr_size_bytes / sizeof(void*); void **x = (void**)malloc(arr_size_bytes); //   //      step_size int k; for(k = 0; k < arr_size; k += step_size) { x[k] = x + k + step_size; } x[k-step_size] = x; const int arr_iters = k / step_size; //     //        if(iters < 4*arr_iters) iters = 4*arr_iters; //         void **p = x; //      const __int64 ticks_start = __rdtsc(); //   --      for(int j = 0; j < iters; j+=256) { TWICE(TWICE(TWICE(TWICE(TWICE(TWICE(TWICE(TWICE( p=(void**)*p; )))))))) } //      const __int64 ticks_end = __rdtsc(); //    ,      //   !!p (),        const double cycles = (double)!!p * (ticks_end-ticks_start) / iters; //   printf("\r%f MB - %.2f cycles ", (double)arr_size_bytes/MB, cycles); free(x); //   //   ,       while(cycles/MHz*iters > 1e6) iters >>= 1; //   ? if(!state && (curseg->width>2) && (fabs(a-curseg->level)<(a*.1)) && (b>(curseg->level*1.1)) && (cycles>(curseg->level*1.1))) { curseg->size_r = a_size_bytes; curseg = curseg->next = (segment*)calloc(1, sizeof(segment)); state = 1; b = 0; //        } //   ? if(state && (fabs(cycles-a)<(a*.1)) && (fabs(cycles-b)<(b*.1))) { curseg->size_l = a_size_bytes; state = 0; } //      if(!state && ((curseg->width<=2) || (fabs(cycles-curseg->level)<cycles*.1))) { curseg->total += cycles; curseg->width++; curseg->level = curseg->total / curseg->width; } //     ? if(!state && (cycles<curseg->level*.9) && (fabs(cycles-a)<(a*.1)) && (fabs(cycles-b)<(b*.1))) { curseg->total = a + b + cycles; curseg->width = 3; curseg->level = curseg->total / curseg->width; } //   a_size_bytes = b_size_bytes; b_size_bytes = arr_size_bytes; a = b; b = cycles; } } //    int TLB = 0; //    -- TLB? for(int cache_level = 1;;cache_level++) { //       int cache_size = allsegs[0].data.size_r, step_count = 0; if(!cache_size) break; //    ( ) double latency, total = 0; if(TLB) //         cache_level--; else latency = allsegs[0].data.level; int less=0, same=0, more=0; //    " " //      for(segments *cursegs = allsegs; cursegs->step_size_bytes; cursegs++) { segment* next = cursegs->data.next; //    if(!next) { //    ,     printf("Missing results for L%d! Step size %d, array size %f MB\n", cache_level, cursegs->step_size_bytes, (double)cursegs->data.size_l/MB); cache_size = 0; break; } //     ,  while(fabs(cursegs->data.level-next->level)<cursegs->data.level*.2) { cursegs->data.size_r = next->size_r; cursegs->data.total += next->total; cursegs->data.width += next->width; cursegs->data.level = cursegs->data.total / cursegs->data.width; cursegs->data.next = next->next; free(next); next = cursegs->data.next; //  if(cursegs==allsegs) { cache_size = cursegs->data.size_r; if(!TLB) latency = cursegs->data.level; } } //       if(cursegs->data.size_r == cache_size) same++; //        else if(cursegs->data.size_r == step(cache_size)) more++; else if(step(cursegs->data.size_r) == cache_size) less++; //     :  TLB else if(cursegs->data.size_r < cache_size/2) { //      --  cache_size = cursegs->data.size_r; more = less = 0; same = 1; //        for(segments *prevsegs = allsegs; prevsegs<cursegs; prevsegs++) { segment* second = (segment*)malloc(sizeof(segment)); *second = prevsegs->data; prevsegs->data.next = second; prevsegs->data.size_r = second->size_l = cache_size; } } //    ,   if(less>same) { cache_size = cursegs->data.size_r; more = same; same = less; less = 0; } else if (more>same) { cache_size = cursegs->data.size_r; less = same; same = more; more = 0; } if(!TLB && fabs(latency-cursegs->data.level)<latency*.1) { total += cursegs->data.level; latency = total / ++step_count; } } if(!cache_size) break; //      //     TLB int min_way_size = 0, max_way_size = 0, next_step_at = 2*cache_size; // ,     TLB double additional = (allsegs[0].data.next->level - latency) / 2; if(additional<0) additional=0; //    TLB = 1; //   TLB,      for(segments *cursegs = allsegs; cursegs->step_size_bytes; cursegs++) { segment* next = cursegs->data.next; //    //    ,     if(cursegs->data.size_r <= cache_size) { if(max_way_size && (max_way_size != next->size_l - cache_size)) { printf("Inconsistent results for L%d! Step size %d, array size %f MB\n", cache_level, cursegs->step_size_bytes, (double)next->size_l/MB); } min_way_size = cursegs->step_size_bytes; //     max_way_size = next->size_l - cache_size; //   --   //    ,      if(next->size_l > step(cache_size)) min_way_size = max_way_size; //   ,     } else if(cursegs->data.size_r > step(cache_size)) { if(cursegs->data.size_r != next_step_at) printf("Inconsistent results for L%d! Step size %d, array size %f MB\n", cache_level, cursegs->step_size_bytes, (double)cursegs->data.size_r/MB); if (!max_way_size) max_way_size = cursegs->step_size_bytes / 2; //       next_step_at *= 2; //        } //   TLB,        double new_additional = cursegs->data.next->level - latency; if((fabs(new_additional - additional*2) < new_additional*.1) || (additional<latency*.1)) additional = new_additional; else //    TLB TLB = 0; //     cursegs->data = *next; free(next); } if(TLB) printf("TLB size: %d, latency: %.2f cycles (%.2f ns)\n" " way size: min. %d, max. %d\n", cache_size/4096, additional/2, (additional/2)/MHz*1000, min_way_size/4096, max_way_size/4096); else printf("L%d size: %d KB, latency: %.2f cycles (%.2f ns)\n" " way size: min. %d KB, max. %d KB\n", cache_level, cache_size/1024, latency, latency/MHz*1000, min_way_size/1024, max_way_size/1024); } return 0; }
      
      






All Articles