なぜ、MATLABは行列乗算でこれほど高速なのですか?


190

CUDA、C ++、C#、Javaでベンチマークを作成し、検証とマトリックス生成にMATLABを使用しています。MATLABで行列乗算を実行する2048x2048と、さらに大きな行列がほぼ瞬時に乗算されます。

             1024x1024   2048x2048   4096x4096
             ---------   ---------   ---------
CUDA C (ms)      43.11      391.05     3407.99
C++ (ms)       6137.10    64369.29   551390.93
C# (ms)       10509.00   300684.00  2527250.00
Java (ms)      9149.90    92562.28   838357.94
MATLAB (ms)      75.01      423.10     3133.90

CUDAだけが競争力がありますが、少なくともC ++はやや近づき、60倍遅くなるとは思いませんでした。また、C#の結果についてどう考えればよいかわかりません。アルゴリズムはC ++およびJavaとまったく同じですが、2048からの大きなジャンプがあり1024ます。

MATLABは行列乗算をどのように高速に実行しますか?

C ++コード:

float temp = 0;
timer.start();
for(int j = 0; j < rozmer; j++)
{
    for (int k = 0; k < rozmer; k++)
    {
        temp = 0;
        for (int m = 0; m < rozmer; m++)
        {
            temp = temp + matice1[j][m] * matice2[m][k];
        }
        matice3[j][k] = temp;
    }
}
timer.stop();

14
おそらく、どのアルゴリズムを使用するかという問題です。
ロバートJ.

24
Matlabが結果をキャッシュしていないことを確認してください。それはトリッキーなビーストです。まず、計算が実際に実行されていることを確認してから、比較します。
rubenvb '19年


10
この投稿は本当に興味深いと思いますが、もっと適切なベンチマークが欲しいです。たとえば、Matlab R2011aはマルチスレッドを自動的に使用し、行列の乗算はIntelのmkl / blasライブラリを使用して実装されていると思います。したがって、行列の乗算を行うためにmkl呼び出しを使用した場合、c ++の方が速いと思います。問題は、Matlabのオーバーヘッドがどうなるかということです。これは行列の乗算の詳細に依存することは知っていますが、上記の数値は現時点では意味がありません。
ルーカス

1
実行時間O(n ^ 2.81)の「Strassenアルゴリズム」を使用して、O(n ^ 3)で実行されるネイティブな乗算よりも約10倍速い大きな正方行列乗算を実行できます。また、SSE / AVXを使用すると、コード実行を約8〜20倍高速化できます。まとめると、MATLABの実装よりも高速にACを実装できます。
DU Jiaen

回答:


85

これは、Tesla C2070を搭載したマシンでMATLAB R2011a + Parallel Computing Toolboxを使用した私の結果です。

>> A = rand(1024); gA = gpuArray(A);
% warm up by executing the operations a couple of times, and then:
>> tic, C = A * A; toc
Elapsed time is 0.075396 seconds.
>> tic, gC = gA * gA; toc
Elapsed time is 0.008621 seconds.

MATLABは高度に最適化されたライブラリを行列の乗算に使用するため、プレーンなMATLAB行列の乗算は非常に高速です。gpuArrayバージョンは、使用していますMAGMAを

Tesla K20c を搭載したマシンでR2014a使用して更新し、新機能timeitgputimeit機能:

>> A = rand(1024); gA = gpuArray(A);
>> timeit(@()A*A)
ans =
    0.0324
>> gputimeit(@()gA*gA)
ans =
    0.0022

16個の物理コアとTesla V100を備えたWIN64マシンでR2018b を使用して更新します

>> timeit(@()A*A)
ans =
    0.0229
>> gputimeit(@()gA*gA)
ans =
   4.8019e-04

(注:ある時点で(正確gpuArrayにはいつかは忘れますが)MAGMAからcuBLASに切り替わりました-MAGMAはまだ一部のgpuArray操作に使用されています)


なぜこれが問題なのですか?
マッド物理学者

なぜ重要なのですか?私は、MATLABのパフォーマンスが優れている理由、つまり高度に最適化された数値ライブラリーを使用している理由を説明するために、MATLABがさまざまな状況で使用するライブラリーに洞察を与えようとしていました。
エドリック

175

この種の質問は繰り返し発生し、スタックオーバーフローで「MATLABは高度に最適化されたライブラリを使用する」や「MATLABはMKLを使用する」よりも明確に回答する必要があります。

歴史:

行列の乗算(行列-ベクトル、ベクトル-ベクトルの乗算、および行列分解の多くと合わせて)は、線形代数における(最も重要な)問題です。エンジニアは、初期の頃からコンピュータでこれらの問題を解決してきました。

私は歴史の専門家ではありませんが、当時はどうやら、誰もが単純なループでFORTRANバージョンを書き直しました。その後、いくつかの標準化が行われ、ほとんどの線形代数問題を解決するために必要な「カーネル」(基本ルーチン)が特定されました。次に、これらの基本操作は、Basic Linear Algebra Subprograms(BLAS)と呼ばれる仕様で標準化されました。次に、エンジニアはコード内でこれらの十分にテストされたBLASルーチンを呼び出し、作業をはるかに簡単にすることができます。

BLAS:

BLASはレベル1(スカラーベクトルとベクトルベクトルの演算を定義した最初のバージョン)からレベル2(ベクトル-行列演算)からレベル3(行列-行列演算)に進化し、より多くの「カーネル」を提供して標準化を進めました。その他の基本的な線形代数演算。元のFORTRAN 77の実装は、NetlibのWebサイトで引き続き利用できます。

より良いパフォーマンスに向けて:

そのため、長年(特にBLASレベル1とレベル2のリリース間:80年代前半)、ハードウェアは変化し、ベクター演算とキャッシュ階層が登場しました。これらの進化により、BLASサブルーチンのパフォーマンスを大幅に向上させることができました。その後、さまざまなベンダーがBLASルーチンの実装を導入し、その効果はますます高まりました。

すべての歴史的な実装を知っているわけではありませんが(当時、私は生まれも子供もいませんでした)、2000年代初頭に最も注目に値する2つの実装、Intel MKLとGotoBLASが登場しました。MatlabはIntel MKLを使用します。これは非常に優れた最適化されたBLASであり、これにより、優れたパフォーマンスが説明されます。

行列乗算の技術詳細:

では、なぜMatlab(MKL)はdgemm(倍精度の一般的な行列-行列乗算)でこれほど高速なのでしょうか。簡単に言うと、ベクトル化とデータの適切なキャッシュを使用しているためです。より複雑な言葉で:ジョナサンムーアによって提供される記事を参照してください

基本的に、提供したC ++コードで乗算を実行する場合、キャッシュは使いやすくありません。行配列へのポインターの配列を作成したのではないかと思うので、内部ループでの「matice2」のk番目の列へのアクセスmatice2[m][k]は非常に低速です。実際、にアクセスするときmatice2[0][k]は、行列の配列0のk番目の要素を取得する必要があります。次に、次の反復で、matice2[1][k]別の配列(配列1)のk番目の要素であるにアクセスする必要があります。次に、次の反復でさらに別の配列にアクセスします...行列全体matice2が最も高いキャッシュ(8*1024*1024バイト数が大きい)に収まらないため、プログラムはメインメモリから目的の要素をフェッチする必要があり、多くの時間。

マトリックスを転置しただけで、アクセスが連続したメモリアドレスで行われる場合、コンパイラは同時にキャッシュ内の行全体を同時にロードできるため、コードはすでにはるかに高速に実行されます。この修正バージョンを試してください:

timer.start();
float temp = 0;
//transpose matice2
for (int p = 0; p < rozmer; p++)
{
    for (int q = 0; q < rozmer; q++)
    {
        tempmat[p][q] = matice2[q][p];
    }
}
for(int j = 0; j < rozmer; j++)
{
    for (int k = 0; k < rozmer; k++)
    {
        temp = 0;
        for (int m = 0; m < rozmer; m++)
        {
            temp = temp + matice1[j][m] * tempmat[k][m];
        }
        matice3[j][k] = temp;
    }
}
timer.stop();

したがって、キャッシュの局所性だけでコードのパフォーマンスが大幅に向上したことがわかります。現在、実際のdgemm実装では、これを非常に広範囲に活用しています。TLB(Translation lookaside buffer、長いストーリー:効果的にキャッシュできるもの)のサイズによって定義された行列のブロックで乗算を実行し、プロセッサにストリーミングします。処理できるデータの量。もう1つの側面はベクトル化です。プロセッサのベクトル化された命令を使用して、最適な命令スループットを実現します。これは、クロスプラットフォームのC ++コードでは実際には実行できません。

最後に、それはStrassenのアルゴリズムまたはCoppersmith–Winogradアルゴリズムが間違っているためであると主張する人々は、上記のハードウェアの考慮事項のために、これらのアルゴリズムはどちらも実際には実装できません。


2
キャッシュサイズとキャッシュラインサイズへのデータの適合の重要性、およびソースに共有データを持たないがハードウェアで共有されるデータになるマルチスレッドソリューションで起こり得る問題について、スコットマイヤーズのビデオを見たところです。 / core-thread level: youtu.be/WDIkqP4JbkE
WillC

40

これが理由です。MATLABは、C ++コードで行ったようにすべての要素をループすることにより、単純な行列の乗算を実行しません。

もちろん、私はC=A*Bあなたが自分で乗算関数を書くのではなく単に使用したと仮定しています。


19

MatlabはLAPACKを少し前に組み込んだので、彼らの行列の乗算は少なくともそれほど高速なものを使用すると思います。LAPACKのソースコードとドキュメントはすぐに利用できます。

GotoおよびVan De Geijnの論文「Anatomy of High-Performance Matrix Multiplication」もご覧ください。http: //citeseerx.ist.psu.edu/viewdoc/download?doi = 10.1.1.140.1785&rep = rep1&type = pdf


7
MATLABは、BLAS / LAPACKルーチンの最適化された実装を提供するインテルMKLライブラリーを使用します:stackoverflow.com/a/16723946/97160
Amro

11

答えはLAPACKBLASライブラリーにより、MATLABの独自のコードではなく、MATLABがマトリックス操作で盲目的に高速化されます。

C ++コードでLAPACKおよび/またはBLASライブラリを使用して行列演算を行うと、MATLABと同様のパフォーマンスが得られます。これらのライブラリは、現代のシステムで自由に利用できるはずであり、部品は学界で数十年にわたって開発されました。インテルMKLなどのクローズドソースを含む複数の実装があることに注意してください。

BLASがどのようにして高パフォーマンスを実現するかについての議論は、ここにあります。


ところで、LAPACKライ​​ブラリをcから直接呼び出すのは私の経験では深刻な痛みです(しかし、それだけの価値があります)。ドキュメントを非常に正確に読む必要があります。


8

行列の乗算を行う場合、の時間がかかる単純な乗算方法を使用しますO(n^3)

をとる行列乗算アルゴリズムが存在しますO(n^2.4)。つまりn=2000、アルゴリズムでは、最高のアルゴリズムの約100倍の計算が必要になります。
それを実装する効率的な方法の詳細については、マトリックスの乗算についてウィキペディアのページを実際に確認する必要があります。


そして、MATLABはおそらく1024 * 1024行列乗算の時間が2048 * 2048行列乗算の時間の8倍よりも小さいため、このようなアルゴリズムを使用します。よくやったMATLABの人。
Renaud 2013年

4
理論的な利点はあるものの、「効率的な」乗算アルゴリズムを使用しているのではないかと思います。Strassenのアルゴリズムでさえ、実装が困難であり、おそらく単純なものについてのみ読んだと思われるCoppersmith–Winogradアルゴリズム実用的ではありません(現在)。また、関連のSOスレッド:stackoverflow.com/questions/17716565/...
Ernir

そのアルゴリズムは、非常に大きな行列に対してのみです。

@Renaud。それは比較的一定のオーバーヘッドの定義です
マッド物理学者

6

Matlabのバージョンによっては、GPUをすでに使用している可能性があります。

別物; Matlabはマトリックスの多くのプロパティを追跡します。その対角線、ヘルメティアンなどに加えて、それに基づくアルゴリズムを専門化しています。たぶん、あなたがそれを渡すゼロ行列に基づいたその特殊化、またはそのような何か?多分それはあなたのタイミングを台無しにする繰り返し関数呼び出しをキャッシュしていますか?多分それは繰り返される未使用のマトリックス製品を最適化しますか?

このようなことが起こらないようにするには、乱数の行列を使用し、結果をスクリーンやディスクなどに出力して、強制的に実行するようにします。


4
MLのヘビーユーザーとして、GPGPUをまだ使用していないことがわかります。新しいバージョンのmatlab DOは(最終的に)SSE1 / 2を使用します。しかし、私はテストを行いました。要素ごとの乗算を実行するMexFunctionは、2倍の速度で実行されA.*Bます。そのため、OPはほぼ間違いなく何かを誤解しています。
KitsuneYMG

6
Parallel Computing Toolboxを備えたMatlab はCUDA GPUを使用できますが、それは明白です。データをGPUにプッシュする必要があります。
エドリック'19年

私はM1 = single(rand(1024,1024)* 255);を使用します。M2 = single(rand(1024,1024)* 255); およびM3 = M1 * M2; ...次に、フロートのバイナリファイルに書き込みます。そのすべてが非常に迅速に行われます。
ウルフ

3

MATLABは、Intel Math Kernel Library(Intel MKL)として知られるIntelの LAPACKの高度に最適化された実装、具体的にはdgemm関数を使用します。速度このライブラリは、SIMD命令やマルチコアプロセッサなどのプロセッサ機能を利用しています。彼らは彼らが使用する特定のアルゴリズムを文書化していません。C ++からインテル®MKLを呼び出すと、同様のパフォーマンスが得られます。

MATLABがGPU乗算に使用するライブラリはわかりませんが、おそらく次のようなものです わかり nVidia CUBLASのです


1
あなたは正しいですが、この答えを見ましたか?ただし、IPPはMKLではなく、MKLはIPPと比較してはるかに優れた線形代数性能を備えています。また、IPPは最近のバージョンでマトリックス演算モジュールを廃止しました。
chappjc

申し訳ありませんが、IPPではなくMKLを意味しました
gregswiss

あなたは正しいです、他の答えはそれをカバーします。それは私がそれを逃したのでとても冗長です。
gregswiss 2015

2

「なぜ他のプログラムよりもxxxを実行するほうがmatlabの方が速いのか」に対する一般的な答えは、matlabには組み込みの最適化された関数がたくさんあるということです。

よく使用される他のプログラムにはこれらの機能がないため、人々は独自のクリエイティブソリューションを適用します。これは、専門的に最適化されたコードよりも驚くほど低速です。

これは2つの方法で解釈できます。

1)一般的/理論的な方法:Matlabはそれほど高速ではなく、ベンチマークを間違っているだけです

2)現実的な方法:C ++としての言語は効果的でない方法で簡単に使用されるため、Matlabは実際にはより高速です。


7
彼は、MATLABの速度と2分間で書いた関数の速度を比較しています。10分でより高速な関数を記述したり、2時間ではるかに高速な関数を記述したりできます。MATLABの担当者は、行列の乗算を高速化するために2時間以上費やしています。
gnasher729 2014

2

シャープなコントラストは、Matlabの驚くべき最適化(他の多くの回答で既に説明されている)だけでなく、マトリックスをオブジェクトとして定式化した方法によるものです。

マトリックスをリストのリストにしたようですか?リストのリストには、マトリックス要素を含むリストへのポインタが含まれています。含まれるリストの場所は任意に割り当てられます。最初のインデックス(行番号?)をループしているので、メモリアクセスの時間は非常に重要です。それに比べて、次の方法で行列を単一のリスト/ベクトルとして実装してみませんか?

#include <vector>

struct matrix {
    matrix(int x, int y) : n_row(x), n_col(y), M(x * y) {}
    int n_row;
    int n_col;
    std::vector<double> M;
    double &operator()(int i, int j);
};

そして

double &matrix::operator()(int i, int j) {
    return M[n_col * i + j];
}

同じ乗算アルゴリズムを使用して、フロップの数を同じにする必要があります。(サイズnの正方行列の場合はn ^ 3)

結果を以前と同じマシンで比較できるように、時間を計るようにお願いします。比較すると、メモリアクセス時間がどれほど重要であるかを正確に示します。


2

マルチスレッドを使用していないため、C ++では遅くなります。基本的に、すべてが行列であるA = BCの場合、Aの最初の行は2番目の行から独立して計算できます。A、B、Cがすべてn行n列の行列である場合、乗算を高速化できます。 n ^ 2の因数

a_ {i、j} = sum_ {k} b_ {i、k} c_ {k、j}

たとえば、Eigen [ http://eigen.tuxfamily.org/dox/GettingStarted.html ] を使用すると、マルチスレッドが組み込まれ、スレッドの数を調整できます。


2

なぜなら、MATLABは最初は数値線形代数(行列操作)のために開発されたプログラミング言語であり、特に行列乗算用に開発されたライブラリーを持っているからです。そして MATLABはGPU(グラフィックスプロセッシングユニット)も使用できるようになりました。加えて、このため。

そして、あなたの計算結果を見ると:

             1024x1024   2048x2048   4096x4096
             ---------   ---------   ---------
CUDA C (ms)      43.11      391.05     3407.99
C++ (ms)       6137.10    64369.29   551390.93
C# (ms)       10509.00   300684.00  2527250.00
Java (ms)      9149.90    92562.28   838357.94
MATLAB (ms)      75.01      423.10     3133.90

次に、MATLABだけでなく行列の乗算が高速であることがわかります。CUDAC(NVIDIAのプログラミング言語)は、MATLABよりも優れた結果をもたらします。CUDA Cには、行列の乗算用に特別に開発されたライブラリもあり、GPUを使用します。

MATLABの短い歴史

ニューメキシコ大学のコンピューターサイエンス学部長であるクリーブモラーは、1970年代後半にMATLABの開発を始めました。彼は、学生がLINPACK(数値線形代数を実行するためのソフトウェアライブラリ)にアクセスできるように設計しました。 EISPACKに(線形代数の数値計算用のソフトウェアライブラリ)。Fortranを学習する必要はありません。それはすぐに他の大学にも広がり、応用数学のコミュニティーに強い聴衆を見つけました。エンジニアのジャック・リトルは、1983年にスタンダー大学を訪れたモーラーの訪問の間にそれにさらされました。その商業的可能性を認識して、彼はモーラーおよびスティーブ・バンガートに加わりました。彼らは、MATLABをCで書き直し、1984年にMathWorksを設立して開発を続けました。これらの書き換えられたライブラリは、JACKPACとして知られていました。2000年に、MATLABはマトリックス操作用の新しいライブラリセットLAPACK(数値線形代数用の標準ソフトウェアライブラリ)を使用するように書き直されました。

ソース

CUDA Cとは

CUDA Cは、OpenGL(Open Graphics Library)などの行列乗算用に特別に開発されたライブラリも使用します。また、GPUとDirect3D(MS Windows)を使用します。

CUDAプラットフォームは、このようなC、C ++、およびFortranなどのプログラミング言語で動作するように設計されています。このアクセシビリティにより、Direct3DOpenGLなどの以前のAPI ではグラフィックプログラミングの高度なスキルが必要でしたが、並列プログラミングの専門家はGPUリソ​​ースを簡単に使用できます。また、CUDAはOpenACCOpenCLなどのプログラミングフレームワークをサポートしています。

ここに画像の説明を入力してください

CUDA処理フローの例:

  1. メインメモリからGPUメモリにデータをコピーする
  2. CPUがGPU計算カーネルを開始します
  3. GPUのCUDAコアはカーネルを並列で実行します
  4. 結果のデータをGPUメモリからメインメモリにコピーする

CPUとGPUの実行速度の比較

インテルXeonプロセッサーX5650でグリッドサイズ64、128、512、1024、2048の50時間ステップを実行し、NVIDIA Tesla C2050 GPUを使用するのにかかる時間を測定するベンチマークを実行しました。

ここに画像の説明を入力してください

グリッドサイズが2048の場合、アルゴリズムは、CPUでの1分以上からGPUでの10秒未満への計算時間の7.5倍の減少を示しています。対数スケールのプロットは、グリッドサイズが小さい場合にCPUが実際に高速であることを示しています。ただし、テクノロジーが進化して成熟するにつれて、GPUソリューションはより小さな問題を処理できるようになり、この傾向は継続すると予想されます。

ソース

CUDA Cプログラミングガイドの紹介から:

示されるように、リアルタイム、高精細3Dグラフィックスのための飽くなき市場の需要によって駆動される、プログラム可能なグラフィックプロセッサユニット又はGPUは、多大な計算馬力と非常に高いメモリ帯域幅と高並列、マルチスレッド化、メニーコアプロセッサへと進化しているFigure 1Figure 2

図1. CPUおよびGPUの1秒あたりの浮動小数点演算

ここに画像の説明を入力してください

図2。CPUおよびGPUのメモリ帯域幅

ここに画像の説明を入力してください

CPUとGPUの間の浮動小数点機能の不一致の背後にある理由は、GPUが計算集中型の高度に並列化された計算に特化しているためです-まさにグラフィックスレンダリングとは何か-したがって、より多くのトランジスタがデータ処理に専念するように設計されていますで概略的に示されてFigure 3いるように、データキャッシングとフロー制御ではなく、

図3。GPUはより多くのトランジスタをデータ処理に捧げます

ここに画像の説明を入力してください

より具体的には、GPUは、データ並列計算として表現できる問題に対処するのに特に適しています。同じプログラムが多くのデータ要素で並列に実行されます-算術強度が高い-算術演算とメモリ演算の比率。各データ要素に対して同じプログラムが実行されるため、高度なフロー制御の必要性が低くなり、多くのデータ要素で実行され、計算の強度が高いため、ビッグデータキャッシュの代わりに計算でメモリアクセスの待ち時間を隠すことができます。 。

データ並列処理は、データ要素を並列処理スレッドにマップします。大きなデータセットを処理する多くのアプリケーションは、データ並列プログラミングモデルを使用して計算を高速化できます。3Dレンダリングでは、ピクセルと頂点の大きなセットが並列スレッドにマッピングされます。同様に、レンダリングされた画像の後処理、ビデオのエンコードとデコード、画像のスケーリング、ステレオビジョン、パターン認識などの画像とメディア処理アプリケーションは、画像ブロックとピクセルを並列処理スレッドにマッピングできます。実際、画像のレンダリングと処理の分野以外の多くのアルゴリズムは、一般的な信号処理や物理シミュレーションから計算ファイナンスや計算生物学まで、データ並列処理によって高速化されています。

ソース

高度な読書


いくつかの興味深いFAC

Matlabと同じくらい高速なC ++行列乗算を作成しましたが、注意が必要でした。(MatlabがこれにGPUを使用する前)。

この回答からの引用。


2
最後の引用は「事実」ではなく、自慢です。その人は彼がそれを投稿して以来、コードのいくつかのリクエストを受け取っています。しかし、コードは見えません。
Cris Luengo

1
GPUで計算をどれだけ迅速に実行できるかについての説明では、この問題にまったく対処していません。128の小さなコアが2つの大きなコアよりも多くの同じ単調な作業を実行できることは誰もが知っています。「そして、MATLABはこれにGPU(グラフィックスプロセッシングユニット)を追加で使用できるようになりました。」はい、ただしデフォルトではありません。通常の行列乗算では、依然としてBLASが使用されます。
クリスLuengo

@ CrisLuengo、OK、それは事実ではありません!たぶん、あなたは彼の "自慢"について正しいのではないでしょうか–私たちはそれについて知りませんし、なぜ彼が答えないのかもわかりません。2番目のコメントについて:GPUでの計算の説明は、線形代数の行列乗算では浮動小数点演算を使用するため、質問に答えます。多分それはすべての人々が理解できるわけではありませんが、私は彼らがこの基本を理解する必要があると思います。他のケースでは、マトリックスに関するいくつかの記事を読む前に、最初にこの基本を学ぶ必要があります。そして、他の誰かがそれについて書いてくれたら、この詳細を追加します。ありがとうございました!
Bharata

@CrisLuengo、私は単語を書いた"additionally"。つまり、使用できるということです。これはまた、通常の行列乗算でもソフトウェアライブラリが使用されることを意味します。もっとわかりやすくするために投稿を変更する必要があると思いますか?コメントしてくださってありがとうございます!
Bharata
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.