20

この単純なアルゴリズムで行列乗算を実行しています。より柔軟にするために、動的に作成された配列を含むマトリックスにオブジェクトを使用しました。

このソリューションを静的配列を使用した最初のソリューションと比較すると、4 倍遅くなります。データ アクセスを高速化するにはどうすればよいですか? アルゴリズムを変更したくありません。

 matrix mult_std(matrix a, matrix b) {
 matrix c(a.dim(), false, false);
 for (int i = 0; i < a.dim(); i++)
  for (int j = 0; j < a.dim(); j++) {
   int sum = 0;
   for (int k = 0; k < a.dim(); k++)
    sum += a(i,k) * b(k,j);
   c(i,j) = sum;
  }

 return c;
}


編集
上記の質問を修正しました!以下に完全なソースコードを追加し、あなたのアドバイスのいくつかを試しました:

  • スワップkjループの反復 -> パフォーマンスの向上
  • 宣言さdim()operator()()inline-> パフォーマンスの向上
  • const 参照による引数の受け渡し ->パフォーマンスの低下! なぜ?なので使いません。

パフォーマンスは、以前のプログラムとほぼ同じになりました。もう少し改善が必要かもしれません。

しかし、別の問題があります。関数でメモリ エラーが発生しますmult_strassen(...)。なんで?
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc


古いプログラム
main.c http://pastebin.com/qPgDWGpW

c99 main.c -o matrix -O3


新しいプログラム
matrix.h http://pastebin.com/TYFYCTY7
matrix.cpp http://pastebin.com/wYADLJ8Y
main.cpp http://pastebin.com/48BSqGJr

g++ main.cpp matrix.cpp -o matrix -O3.


編集
ここにいくつかの結果があります。標準アルゴリズム (std)、j と k ループの順序を入れ替えたもの (swap)、ブロック サイズ 13 のブロック アルゴリズム (block) の比較。 代替テキスト

4

7 に答える 7

35

スピードアップについて言えば、kjループの反復の順序を入れ替えると、関数はよりキャッシュ フレンドリーになります。

matrix mult_std(matrix a, matrix b) {
 matrix c(a.dim(), false, false);
 for (int i = 0; i < a.dim(); i++)
  for (int k = 0; k < a.dim(); k++)
   for (int j = 0; j < a.dim(); j++)  // swapped order
    c(i,j) += a(i,k) * b(k,j);

 return c;
}

これkは、最も内側のループにインデックスがあると、bすべての反復でキャッシュ ミスが発生するためです。j最も内側のインデックスとして、 と の両方が連続してアクセスされますがc、はそのままです。ba

于 2010-11-29T03:57:52.120 に答える
4

dim()メンバーとoperator()()がインラインで宣言されていること、およびコンパイラーの最適化がオンになっていることを確認してください。-funroll-loops次に、 (gccで)のようなオプションで遊んでください。

a.dim()とにかくどれくらいの大きさですか?マトリックスの行が2、3のキャッシュ行に収まらない場合は、一度に1行全体を使用するのではなく、ブロックアクセスパターンを使用することをお勧めします。

于 2010-11-29T03:47:59.627 に答える
4

アルゴリズムを変更したくないと言っていますが、それは正確にはどういう意味ですか?

ループの展開は「アルゴリズムの変更」と見なされますか? CPU で利用可能な SIMD 命令に関係なく、SSE/VMX を使用するのはどうですか? キャッシュの局所性を改善するために何らかの形のブロッキングを採用するのはどうですか?

コードをまったく再構築したくない場合は、既に行った変更以外にできることはないと思います。それ以外はすべて、アルゴリズムを少し変更してパフォーマンスを向上させるというトレードオフになります。

もちろん、コンパイラによって生成された asm を確認する必要があります。これにより、コードを高速化するために何ができるかについて多くのことがわかります。

于 2010-11-29T15:49:02.033 に答える
3
  • 可能であれば SIMD を使用してください。ベクトル計算が可能なプラットフォームを使用していると仮定して、大規模なベクトル計算を行う場合は、VMX レジスタのようなものを絶対に使用する必要があります。そうしないと、パフォーマンスが大幅に低下します。
  • 値渡しなどの複雑な型を渡さないでくださいmatrix。const 参照を使用してください。
  • 各反復で関数を呼び出さないdim()でください - ループの外にキャッシュしてください。
  • 通常、コンパイラはこれを効率的に最適化しますが、多くの場合、型ごとに行列を返すのではなく、呼び出し元に関数の行列参照を提供して入力させることをお勧めします。場合によっては、これによりコストのかかるコピー操作が発生する可能性があります。
于 2010-11-29T03:58:18.690 に答える
1

これは、正方フロート行列 (2D 配列) の高速単純乗算アルゴリズムの実装です。多少のインクリメントを省いているため、chrisaycock コードよりも少し高速になるはずです。

static void fastMatrixMultiply(const int dim, float* dest, const float* srcA, const float* srcB)
{
    memset( dest, 0x0, dim * dim * sizeof(float) );

    for( int i = 0; i < dim; i++ ) {
        for( int k = 0; k < dim; k++ ) 
        {
            const float* a = srcA + i * dim + k;
            const float* b = srcB + k * dim;
            float* c = dest + i * dim;

            float* cMax = c + dim;
            while( c < cMax ) 
            {   
                *c++ += (*a) * (*b++);
            }
        }
    }
}
于 2014-07-18T13:24:14.953 に答える
1

まず、const 参照でパラメーターを渡します。

matrix mult_std(matrix const& a, matrix const& b) {

詳細を説明するには、使用されている他の方法の詳細を知る必要があります。
元の方法が 4 倍高速である理由に答えるには、元の方法を確認する必要があります。

この問題は過去に何百万回も解決されているため、間違いなくあなたのものです。

また、この種の質問をするときは、常にコンパイル可能なソースに適切な入力を提供して、実際にコードをビルドして実行し、何が起こっているかを確認できるようにします。

コードがなければ、私たちはただ推測しています。

編集

元の C コードの主なバグ (バッファ オーバーラン) を修正した後

公正な比較でテストを並べて実行するようにコードを更新しました。

 // INCLUDES -------------------------------------------------------------------
 #include <stdlib.h>
 #include <stdio.h>
 #include <sys/time.h>
 #include <time.h>

 // DEFINES -------------------------------------------------------------------
 // The original problem was here. The MAXDIM was 500. But we were using arrays
 // that had a size of 512 in each dimension. This caused a buffer overrun that
 // the dim variable and caused it to be reset to 0. The result of this was causing
 // the multiplication loop to fall out before it had finished (as the loop was
 // controlled by this global variable.
 //
 // Everything now uses the MAXDIM variable directly.
 // This of course gives the C code an advantage as the compiler can optimize the
 // loop explicitly for the fixed size arrays and thus unroll loops more efficiently.
 #define MAXDIM 512
 #define RUNS 10

 // MATRIX FUNCTIONS ----------------------------------------------------------
 class matrix
 {
 public:
 matrix(int dim)
       : dim_(dim)
 {
         data_ = new int[dim_ * dim_];

 }

     inline int dim() const {
                         return dim_;
                 }
                 inline int& operator()(unsigned row, unsigned col) {
                         return data_[dim_*row + col];
                 }

                 inline int operator()(unsigned row, unsigned col) const {
                         return data_[dim_*row + col];
                 }

 private:
     int dim_;
     int* data_;
 };

// ---------------------------------------------------
 void random_matrix(int (&matrix)[MAXDIM][MAXDIM]) {
         for (int r = 0; r < MAXDIM; r++)
                 for (int c = 0; c < MAXDIM; c++)
                         matrix[r][c] = rand() % 100;
 }
 void random_matrix_class(matrix& matrix) {
         for (int r = 0; r < matrix.dim(); r++)
                 for (int c = 0; c < matrix.dim(); c++)
                         matrix(r, c) = rand() % 100;
 }

 template<typename T, typename M>
 float run(T f, M const& a, M const& b, M& c)
 {
         float time = 0;

         for (int i = 0; i < RUNS; i++) {
                 struct timeval start, end;
                 gettimeofday(&start, NULL);
                 f(a,b,c);
                 gettimeofday(&end, NULL);

                 long s = start.tv_sec * 1000 + start.tv_usec / 1000;
                 long e = end.tv_sec * 1000 + end.tv_usec / 1000;

                 time += e - s;
         }
         return time / RUNS;
 }
 // SEQ MULTIPLICATION ----------------------------------------------------------
  int* mult_seq(int const(&a)[MAXDIM][MAXDIM], int const(&b)[MAXDIM][MAXDIM], int (&z)[MAXDIM][MAXDIM]) {
          for (int r = 0; r < MAXDIM; r++) {
                  for (int c = 0; c < MAXDIM; c++) {
                          z[r][c] = 0;
                          for (int i = 0; i < MAXDIM; i++)
                                  z[r][c] += a[r][i] * b[i][c];
                  }
          }
  }
  void mult_std(matrix const& a, matrix const& b, matrix& z) {
          for (int r = 0; r < a.dim(); r++) {
                  for (int c = 0; c < a.dim(); c++) {
                          z(r,c) = 0;
                          for (int i = 0; i < a.dim(); i++)
                                  z(r,c) += a(r,i) * b(i,c);
                  }
          }
  }

  // MAIN ------------------------------------------------------------------------
  using namespace std;
  int main(int argc, char* argv[]) {
          srand(time(NULL));

          int matrix_a[MAXDIM][MAXDIM];
          int matrix_b[MAXDIM][MAXDIM];
          int matrix_c[MAXDIM][MAXDIM];
          random_matrix(matrix_a);
          random_matrix(matrix_b);
          printf("%d ", MAXDIM);
          printf("%f \n", run(mult_seq, matrix_a, matrix_b, matrix_c));

          matrix a(MAXDIM);
          matrix b(MAXDIM);
          matrix c(MAXDIM);
          random_matrix_class(a);
          random_matrix_class(b);
          printf("%d ", MAXDIM);
          printf("%f \n", run(mult_std, a, b, c));

          return 0;
  }

結果は次のとおりです。

$ g++ t1.cpp
$ ./a.exe
512 1270.900000
512 3308.800000

$ g++ -O3 t1.cpp
$ ./a.exe
512 284.900000
512 622.000000

このことから、完全に最適化された場合、C コードは C++ コードの約 2 倍高速であることがわかります。コードに理由がわかりません。

于 2010-11-29T04:19:42.493 に答える
0

私はここで大げさな推測をしていますが、行列を動的に割り当てると非常に大きな違いが生じる場合、問題は断片化である可能性があります。繰り返しになりますが、基礎となるマトリックスがどのように実装されているのかわかりません。

行列にメモリを手動で割り当てて、連続していることを確認し、ポインタ構造を自分で構築してみませんか?

また、dim()メソッドには特別な複雑さがありますか?私もインラインで宣言します。

于 2010-11-29T03:48:32.557 に答える