2017-03-02 6 views
12

次のコード例は、サイズがNの行列を生成し、SAMPLES回転置します。 N = 512の場合、転置操作の平均実行時間は2144 μscoliru link)です。 一見何も特別なことは正しい、ありません?...なぜこれらのマトリックス転移時間は逆らって直感的ですか?

まあ、ここ

  • N = 5131451 μs
  • N = 519の結果である→600 μs
  • N = 530486 μs
  • N = 540492 μs(最終的に理論は働き始めます:)。

なぜ実際にこれらの単純な計算が理論と異なっているのですか?この動作は、CPUキャッシュの一貫性またはキャッシュミスに関連していますか?もしそうなら、説明してください。

#include <algorithm> 
#include <iostream> 
#include <chrono> 

constexpr int N  = 512; // Why is 512 specifically slower (as of 2016) 
constexpr int SAMPLES = 1000; 
using us = std::chrono::microseconds; 

int A[N][N]; 

void transpose() 
{ 
    for (int i = 0 ; i < N ; i++) 
    for (int j = 0 ; j < i ; j++) 
     std::swap(A[i][j], A[j][i]); 
} 

int main() 
{ 
    // initialize matrix 
    for (int i = 0 ; i < N ; i++) 
    for (int j = 0 ; j < N ; j++) 
     A[i][j] = i+j; 

    auto t1 = std::chrono::system_clock::now(); 
    for (int i = 0 ; i < SAMPLES ; i++) 
     transpose(); 
    auto t2 = std::chrono::system_clock::now(); 

    std::cout << "Average for size " << N << ": " << std::chrono::duration_cast<us>(t2 - t1).count()/SAMPLES << " (us)"; 
} 
+0

スニペットを何回実行しましたか?ランタイムは、実行中にシステムによって何が行われているかによって大きく異なることがあります。これらの平均時間は約10回または20回、または1回の実行からのタイミングだけですか? – JGroven

+1

おそらく512はキャッシュにぴったり合っているので、キャッシュミスが多く発生する魔法のサイズです。他のサイズはフィットしているので、ミスが少なくなります。 – NathanOliver

+0

間違った方法@NathanOliver - 512は513よりもずっと遅い* –

答えて

6

これはキャッシュミスによるものです。 valgrind --tool=cachegrindを使用して、ミスの量を確認できます。

Average for size 512: 13052 (us)==21803== 
==21803== I refs:  1,054,721,935 
==21803== I1 misses:   1,640 
==21803== LLi misses:   1,550 
==21803== I1 miss rate:   0.00% 
==21803== LLi miss rate:   0.00% 
==21803== 
==21803== D refs:  524,278,606 (262,185,156 rd + 262,093,450 wr) 
==21803== D1 misses:  139,388,226 (139,369,492 rd +  18,734 wr) 
==21803== LLd misses:   25,828 (  7,959 rd +  17,869 wr) 
==21803== D1 miss rate:   26.6% (  53.2%  +   0.0% ) 
==21803== LLd miss rate:   0.0% (  0.0%  +   0.0% ) 
==21803== 
==21803== LL refs:   139,389,866 (139,371,132 rd +  18,734 wr) 
==21803== LL misses:   27,378 (  9,509 rd +  17,869 wr) 
==21803== LL miss rate:   0.0% (  0.0%  +   0.0% ) 

ながら、N=530を使用して次のような出力ました:あなたが見ることができるように、D1は512でミス

Average for size 530: 13264 (us)==22783== 
==22783== I refs:  1,129,929,859 
==22783== I1 misses:   1,640 
==22783== LLi misses:   1,550 
==22783== I1 miss rate:   0.00% 
==22783== LLi miss rate:   0.00% 
==22783== 
==22783== D refs:  561,773,362 (280,923,156 rd + 280,850,206 wr) 
==22783== D1 misses:  32,899,398 (32,879,492 rd +  19,906 wr) 
==22783== LLd misses:   26,999 (  7,958 rd +  19,041 wr) 
==22783== D1 miss rate:   5.9% (  11.7%  +   0.0% ) 
==22783== LLd miss rate:   0.0% (  0.0%  +   0.0% ) 
==22783== 
==22783== LL refs:   32,901,038 (32,881,132 rd +  19,906 wr) 
==22783== LL misses:   28,549 (  9,508 rd +  19,041 wr) 
==22783== LL miss rate:   0.0% (  0.0%  +   0.0% ) 

530に比べて約3.5倍大きいのN = 512を使用して次のような出力を得ました

+0

そうです解決策は、 "キャッシュにやさしい"マトリックスに次のより大きなサイズのマトリックスを使用し、使用されていないカラム(場合によっては行)を残すことです。 – rcgldr

+0

Yupであり、高い割合のミスは、ある特定のメモリアクセスパターンの下で、連想性が使用される合計キャッシュのほんの一部しか使用できないためです。 –

+1

@rcgldr:よりよい解決策は、メモリアクセス順序を変更することです。単一要素をスワップする代わりに、4x4ブロックをスワップすると、スワップの両端の同じキャッシュ行にあるすべての要素にアクセスできます。 –

関連する問題