この種の質問は繰り返し発生するため、StackOverflowで「MATLABは高度に最適化されたライブラリを使用する」または「MATLABはMKLを使用する」よりも明確に回答する必要があります。
歴史:
行列の乗算(行列-ベクトル、ベクトル-ベクトルの乗算、および行列の分解の多くと一緒に)は、線形代数で最も重要な問題です。エンジニアは、初期の頃からコンピューターでこれらの問題を解決してきました。
私は歴史の専門家ではありませんが、どうやら当時は、誰もが彼のFORTRANバージョンを単純なループで書き直しただけだったようです。その後、いくつかの標準化が行われ、ほとんどの線形代数の問題を解決するために必要な「カーネル」(基本ルーチン)が特定されました。これらの基本的な操作は、Basic Linear Algebra Subprograms(BLAS)と呼ばれる仕様で標準化されました。エンジニアは、コード内でこれらの標準の十分にテストされたBLASルーチンを呼び出すことができ、作業がはるかに簡単になります。
BLAS:
BLASは、レベル1(スカラーベクトルおよびベクトルベクトル演算を定義した最初のバージョン)からレベル2(ベクトル行列演算)、レベル3(行列行列演算)に進化し、より多くの「カーネル」を提供するため、より標準化されました。そして、より多くの基本的な線形代数演算。元のFORTRAN77の実装は、NetlibのWebサイトで引き続き入手できます。
より良いパフォーマンスに向けて:
そのため、何年にもわたって(特に、BLASレベル1とレベル2のリリース間:80年代初頭)、ベクトル操作とキャッシュ階層の出現により、ハードウェアが変更されました。これらの進化により、BLASサブルーチンのパフォーマンスを大幅に向上させることができました。その後、さまざまなベンダーが、ますます効率的なBLASルーチンの実装を実現しました。
歴史的な実装のすべてを知っているわけではありませんが(当時、私は生まれも子供もいませんでした)、2000年代初頭に最も注目に値する2つの実装であるIntelMKLとGotoBLASが登場しました。MatlabはIntelMKLを使用しています。これは、非常に優れた最適化された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のサイズ(トランスレーションルックアサイドバッファ、簡単に言うと、効果的にキャッシュできるもの)で定義されたマトリックスのブロックで乗算を実行し、プロセッサにストリーミングします。正確に処理できるデータの量。もう1つの側面はベクトル化です。これらは、プロセッサのベクトル化された命令を使用して、最適な命令スループットを実現します。これは、クロスプラットフォームのC++コードからは実際には実行できません。
最後に、StrassenまたはCoppersmith–Winogradアルゴリズムが原因であると主張する人々は間違っています。上記のハードウェアの考慮事項のため、これらのアルゴリズムはどちらも実際には実装できません。