2048x2048 配列の乗算と 2047x2047 配列の乗算でパフォーマンスが大幅に低下するのはなぜですか? 質問する

2048x2048 配列の乗算と 2047x2047 配列の乗算でパフォーマンスが大幅に低下するのはなぜですか? 質問する

以前述べたように、私は行列乗算のベンチマークを行っています。MATLAB の行列乗算がなぜこんなに速いのでしょうか?

今、別の問題が発生しています。2 つの 2048x2048 行列を乗算する場合、C# と他のものとの間に大きな違いがあります。2047x2047 行列のみを乗算しようとすると、正常に見えます。比較のために他のものもいくつか追加しました。

1024x1024 - 10秒。

1027x1027 - 10秒。

2047x2047 - 90秒。

2048x2048 - 300秒。

2049x2049 - 91秒。(更新)

2500x2500 - 166秒

2k x 2k の場合、3 分半の差になります。

2次元配列を使用する

//Array init like this
int rozmer = 2048;
float[,] matice = new float[rozmer, rozmer];

//Main multiply code
for(int j = 0; j < rozmer; j++)
{
   for (int k = 0; k < rozmer; k++)
   {
     float temp = 0;
     for (int m = 0; m < rozmer; m++)
     {
       temp = temp + matice1[j,m] * matice2[m,k];
     }
     matice3[j, k] = temp;
   }
 }

ベストアンサー1

これはおそらく L2 キャッシュの競合に関係しています。

matice1 のキャッシュ ミスは、順番にアクセスされるため問題にはなりません。ただし、matice2 の場合、列全体が L2 に収まる場合 (つまり、matice2[0, 0]、matice2[1, 0]、matice2[2, 0] などにアクセスすると、何も削除されない)、matice2 のキャッシュ ミスも問題になりません。

キャッシュの仕組みを詳しく説明すると、変数のバイト アドレスが X の場合、そのキャッシュ ラインは (X >> 6) & (L - 1) になります。ここで、L はキャッシュ内のキャッシュ ラインの合計数です。L は常に 2 の累乗です。6 は、2^6 == 64 バイトがキャッシュ ラインの標準サイズであるという事実に由来します。

これは何を意味するのでしょうか? つまり、アドレス X とアドレス Y があり、(X >> 6) - (Y >> 6) が L (つまり、2 の大きな累乗) で割り切れる場合、それらは同じキャッシュラインに格納されるということです。

さて、あなたの問題に戻りましょう。2048年と2049年の違いは何でしょうか?

2048があなたのサイズの場合:

&matice2[x, k] と &matice2[y, k] を取ると、その差 (&matice2[x, k] >> 6) - (&matice2[y,k] >> 6) は 2048 * 4 (float のサイズ) で割り切れます。つまり、2 の大きな累乗です。

したがって、L2 のサイズによっては、キャッシュ ラインの競合が多く発生し、列を格納するために L2 のごく一部しか使用されないことになります。そのため、実際にはキャッシュに列全体を格納できず、パフォーマンスが低下します。

サイズが 2049 の場合、差は 2049 * 4 となり、2 の累乗ではないため、競合が少なくなり、列がキャッシュに安全に収まります。

この理論をテストするには、次の 2 つの方法があります。

配列 matice2 を matice2 [razmor, 4096] のように割り当て、razmor = 1024、1025 または任意のサイズで実行すると、以前と比べてパフォーマンスが非常に悪くなることがわかります。これは、すべての列が互いに競合するように強制的に配置しているためです。

次に、matice2 [razmor、4097] を試して、任意のサイズで実行すると、パフォーマンスが大幅に向上するはずです。

おすすめ記事