75

編集: 参照目的で (誰かがこの質問に出くわした場合)、Igor Ostrovsky がキャッシュ ミスに関する素晴らしい投稿を書きました。いくつかの異なる問題について説明し、例の数値を示します。 編集を終了

いくつかのテスト<long story goes here>を行いましたが、パフォーマンスの違いがメモリ キャッシュ ミスによるものかどうか疑問に思っています。次のコードは、問題を示し、重要なタイミング部分に要約します。次のコードには、メモリをランダムな順序でアクセスした後、アドレスの昇順でアクセスするループがいくつか含まれています。

XP マシン (VS2005 でコンパイル: cl /O2) と Linux ボックス (gcc –Os) で実行しました。どちらも同様の時間を生み出しました。これらの時間はミリ秒単位です。すべてのループが実行されており、最適化されていないと思います (そうでなければ、「即座に」実行されます)。

*** 20000 ノードをテスト中
合計注文時間: 888.822899
合計ランダム時間: 2155.846268

これらの数字は理にかなっていますか? 違いは主に L1 キャッシュ ミスによるものですか、それとも他の何かが起こっているのでしょうか? 20,000^2 のメモリ アクセスがあり、すべてがキャッシュ ミスである場合、ミスあたり約 3.2 ナノ秒になります。私がテストした XP (P4) マシンは 3.2GHz で、32KB の L1 キャッシュと 512KB の L2 を持っていると思われます (しかしわかりません)。20,000 エントリ (80KB) があるので、L2 ミスはそれほど多くないと思います。したがって、これは になります(3.2*10^9 cycles/second) * 3.2*10^-9 seconds/miss) = 10.1 cycles/miss。それは私には高く思えます。そうではないかもしれませんし、私の数学が悪いのかもしれません。VTune でキャッシュ ミスを測定しようとしましたが、BSOD が発生しました。そして今、ライセンス サーバー (grrrr) に接続できません。

typedef struct stItem
{
   long     lData;
   //char     acPad[20];
} LIST_NODE;



#if defined( WIN32 )
void StartTimer( LONGLONG *pt1 )
{
   QueryPerformanceCounter( (LARGE_INTEGER*)pt1 );
}

void StopTimer( LONGLONG t1, double *pdMS )
{
   LONGLONG t2, llFreq;

   QueryPerformanceCounter( (LARGE_INTEGER*)&t2 );
   QueryPerformanceFrequency( (LARGE_INTEGER*)&llFreq );
   *pdMS = ((double)( t2 - t1 ) / (double)llFreq) * 1000.0;
}
#else
// doesn't need 64-bit integer in this case
void StartTimer( LONGLONG *pt1 )
{
   // Just use clock(), this test doesn't need higher resolution
   *pt1 = clock();
}

void StopTimer( LONGLONG t1, double *pdMS )
{
   LONGLONG t2 = clock();
   *pdMS = (double)( t2 - t1 ) / ( CLOCKS_PER_SEC / 1000 );
}
#endif



long longrand()
{
   #if defined( WIN32 )
   // Stupid cheesy way to make sure it is not just a 16-bit rand value
   return ( rand() << 16 ) | rand();
   #else
   return rand();
   #endif
}

// get random value in the given range
int randint( int m, int n )
{
   int ret = longrand() % ( n - m + 1 );
   return ret + m;
}

// I think I got this out of Programming Pearls (Bentley).
void ShuffleArray
(
   long *plShuffle,  // (O) return array of "randomly" ordered integers
   long lNumItems    // (I) length of array
)
{
   long i;
   long j;
   long t;

   for ( i = 0; i < lNumItems; i++ )
      plShuffle[i] = i;

   for ( i = 0; i < lNumItems; i++ )
      {
      j = randint( i, lNumItems - 1 );

      t = plShuffle[i];
      plShuffle[i] = plShuffle[j];
      plShuffle[j] = t;
      }
}



int main( int argc, char* argv[] )
{
   long          *plDataValues;
   LIST_NODE     *pstNodes;
   long          lNumItems = 20000;
   long          i, j;
   LONGLONG      t1;  // for timing
   double dms;

   if ( argc > 1 && atoi(argv[1]) > 0 )
      lNumItems = atoi( argv[1] );

   printf( "\n\n*** Testing %u nodes\n", lNumItems );

   srand( (unsigned int)time( 0 ));

   // allocate the nodes as one single chunk of memory
   pstNodes = (LIST_NODE*)malloc( lNumItems * sizeof( LIST_NODE ));
   assert( pstNodes != NULL );

   // Create an array that gives the access order for the nodes
   plDataValues = (long*)malloc( lNumItems * sizeof( long ));
   assert( plDataValues != NULL );

   // Access the data in order
   for ( i = 0; i < lNumItems; i++ )
      plDataValues[i] = i;

   StartTimer( &t1 );

   // Loop through and access the memory a bunch of times
   for ( j = 0; j < lNumItems; j++ )
      {
      for ( i = 0; i < lNumItems; i++ )
         {
         pstNodes[plDataValues[i]].lData = i * j;
         }
      }

   StopTimer( t1, &dms );
   printf( "Total Ordered Time: %f\n", dms );

   // now access the array positions in a "random" order
   ShuffleArray( plDataValues, lNumItems );

   StartTimer( &t1 );

   for ( j = 0; j < lNumItems; j++ )
      {
      for ( i = 0; i < lNumItems; i++ )
         {
         pstNodes[plDataValues[i]].lData = i * j;
         }
      }

   StopTimer( t1, &dms );
   printf( "Total Random Time: %f\n", dms );

}
4

8 に答える 8

69

ここでは、チョコレート チップ クッキーを焼くこととの類推によって、キャッシュ ミスの相対的なコストについての洞察を提供しようとしています ...

あなたの手はあなたのレジスターです。チョコチップを生地に落とすのに1秒かかります。

キッチン カウンターは L1 キャッシュであり、レジスターよりも 12 倍低速です。12 x 1 = 12 秒かかり、カウンターに足を踏み入れ、クルミの袋を手に取り、手に空にします。

冷蔵庫は L2 キャッシュであり、L1 よりも 4 倍遅くなります。4 x 12 = 48 秒かかり、冷蔵庫まで歩いて冷蔵庫を開き、昨夜の残り物を邪魔にならないように移動し、卵のカートンを取り出し、カートンを開け、カウンターに 3 つの卵を置き、カートンを元に戻します。冷蔵庫。

食器棚は L3 キャッシュであり、L2 よりも 3 倍遅くなります。3 x 48 = 2 分 24 秒かかり、食器棚まで 3 歩かかります。腰をかがめ、ドアを開け、周りを探し回ってベーキング用品の缶を見つけ、食器棚から取り出し、開け、掘ってベーキング パウダーを見つけます。 、カウンターの上に置いて、床にこぼれた汚れを一掃します。

そしてメインメモリ?それはコーナーストアで、L3 より 5 倍遅いです。5 x 2:24 = 12 分で、財布を見つけ、靴とジャケットを着て通りをダッシュ​​し、牛乳を 1 リットル手に取り、ダッシュして家に帰り、靴とジャケットを脱いでキッチンに戻ります。

これらのアクセスはすべて一定の複雑さ (O(1)) ですが、それらの違いがパフォーマンスに大きな影響を与える可能性があることに注意してください。複雑さだけを考慮して最適化することは、チョコレート チップを生地に 1 つずつ追加するか、一度に 10 ずつ追加するかを決定しながら、食料品のリストに追加するのを忘れているようなものです。

話の教訓:メモリ アクセスを整理して、CPU ができるだけ頻繁に食料品を買いに行かないようにします。

数値は、CPU キャッシュ フラッシュの誤謬のブログ投稿から取得したもので、特定の 2012 年代の Intel プロセッサの場合、次のことが当てはまることを示しています。

  • レジスタ アクセス = 1 サイクルあたり 4 命令
  • L1 レイテンシ = 3 サイクル (12 x レジスタ)
  • L2 レイテンシ = 12 サイクル (4 x L1、48 x レジスタ)
  • L3 レイテンシ = 38 サイクル (3 x L2、12 x L1、144 x レジスタ)
  • DRAM レイテンシ = 65 ns = 3 GHz CPU で 195 サイクル (5 x L3、15 x L2、60 x L1、720 x レジスタ)

このトピックについては、Gallery of Processor Cache Effectsも参考になります。

うーん、クッキー...

于 2015-03-21T21:56:28.813 に答える
26

数字が理にかなっているのかどうかについては答えられませんが (私はキャッシュのレイテンシーについては詳しくありませんが、記録としては 10 サイクルまでの L1 キャッシュ ミスはほぼ正しいように聞こえます)、Cachegrindを提案することはできます。ツールを使用すると、2 つのテスト間のキャッシュ パフォーマンスの違いを実際に確認できます。

Cachegrind は、キャッシュとブランチのヒット/ミスをプロファイリングする Valgrind ツール (常に素敵な memcheck を強化するフレームワーク) です。これにより、プログラムで実際にキャッシュ ヒット/ミスが何回発生しているかがわかります。

于 2009-07-15T08:28:53.880 に答える
18

L1 キャッシュ ミスの 3.2ns は完全に妥当です。比較のために、特定の最新のマルチコア PowerPC CPU では、L1 ミスは約40サイクルです。コアによっては、L2 キャッシュからの距離に応じて、他のコアよりも少し長くなります (そうです)。L2 ミスは少なくとも 600サイクルです。

キャッシュはパフォーマンスのすべてです。現在、CPU はメモリよりもはるかに高速であるため、コアではなくメモリ バスを最適化しているところです。

于 2009-07-15T08:27:51.247 に答える
3

cachegrind を使用する予定がある場合は、これはキャッシュ ヒット/ミス シミュレーターのみであることに注意してください。常に正確であるとは限りません。例: ループ内の 0x1234 などのメモリ位置に 1000 回アクセスすると、次のような場合でも、キャッシュ ミス (最初のアクセス) が 1 回だけであることが cachegrind によって常に表示されます。

ループで clflush 0x1234 。

x86 では、これにより 1000 回すべてのキャッシュ ミスが発生します。

于 2011-11-14T23:18:15.447 に答える
-3

最も簡単な方法は、ターゲット CPU の拡大写真を撮り、コアとレベル 1 キャッシュの間の距離を物理的に測定することです。その距離に、電子が銅の中を 1 秒間に移動できる距離を掛けます。次に、同じ時間にいくつのクロック サイクルを使用できるかを計算します。これは、L1 キャッシュ ミスで浪費する CPU サイクルの最小数です。

同様に、浪費された CPU サイクル数に基づいて、RAM からデータをフェッチする最小コストを計算することもできます。あなたは驚くかもしれません。

ここに表示されているのは、明らかにキャッシュ ミス (L1 または L1 と L2 の両方) と関係があることに注意してください。これは、通常、キャッシュ ライン上の何かにアクセスすると、同じキャッシュ ライン上のデータが取り出されるためです。 RAMへのトリップが少なくなります。

ただし、おそらく RAM は (ランダム アクセス メモリと呼ばれていますが) リニア メモリ アクセスを優先するという事実も見られます。

于 2009-07-15T08:16:17.853 に答える