4

2 つの画像 (配列) の乗算に Intel IPP を使用しています。
Intel Composer 2015 Update 6 に付属する Intel IPP 8.2 を使用しています。

大きすぎる画像を乗算する簡単な関数を作成しました (プロジェクト全体が添付されています。以下を参照してください)。
インテル® IPP マルチスレッド・ライブラリーを使用した場合の利点を確認したかったのです。

簡単なプロジェクトを次に示します (Visual Studio から完全なプロジェクトも添付しました)。

#include "ippi.h"
#include "ippcore.h"
#include "ipps.h"
#include "ippcv.h"
#include "ippcc.h"
#include "ippvm.h"

#include <ctime>
#include <iostream>

using namespace std;

const int height = 6000;
const int width  = 6000;
Ipp32f mInput_image [1 * width * height];
Ipp32f mOutput_image[1 * width * height] = {0};

int main()
{
    IppiSize size = {width, height};

    double start = clock();

    for (int i = 0; i < 200; i++)
        ippiMul_32f_C1R(mInput_image, 6000 * 4, mInput_image, 6000 * 4, mOutput_image, 6000 * 4, size); 

    double end = clock();
    double douration = (end - start) / static_cast<double>(CLOCKS_PER_SEC);

    cout << douration << endl;
    cin.get();

    return 0;
}

このプロジェクトは、インテル IPP シングルスレッドを使用して 1 回、インテル IPP マルチスレッドを使用して 1 回コンパイルしました。

さまざまなサイズの配列を試してみましたが、そのすべてにおいて、マルチスレッド バージョンでは効果がありません (さらに遅い場合もあります)。

マルチスレッドでこのタスクに利益がないのはなぜですか?
Intel IPP が AVX を使用していることは知っていますが、タスクがメモリ制限になるのではないかと思いました。

OpenMP を手動で使用して別のアプローチを試み、Intel IPP シングル スレッド実装を使用したマルチスレッド アプローチを採用しました。
これはコードです:

#include "ippi.h"
#include "ippcore.h"
#include "ipps.h"
#include "ippcv.h"
#include "ippcc.h"
#include "ippvm.h"

#include <ctime>
#include <iostream>

using namespace std;

#include <omp.h>

const int height = 5000;
const int width  = 5000;
Ipp32f mInput_image [1 * width * height];
Ipp32f mOutput_image[1 * width * height] = {0};

int main()
{
    IppiSize size = {width, height};

    double start = clock();

    IppiSize blockSize = {width, height / 4};

    const int NUM_BLOCK = 4;
    omp_set_num_threads(NUM_BLOCK);

    Ipp32f*  in;
    Ipp32f*  out;

    //  ippiMul_32f_C1R(mInput_image, width * 4, mInput_image, width * 4, mOutput_image, width * 4, size);

    #pragma omp parallel            \
    shared(mInput_image, mOutput_image, blockSize) \
    private(in, out)
    {
        int id   = omp_get_thread_num();
        int step = blockSize.width * blockSize.height * id;
        in       = mInput_image  + step;
        out      = mOutput_image + step;
        ippiMul_32f_C1R(in, width * 4, in, width * 4, out, width * 4, blockSize);
    }

    double end = clock();
    double douration = (end - start) / static_cast<double>(CLOCKS_PER_SEC);

    cout << douration << endl;
    cin.get();

    return 0;
}

結果は同じで、パフォーマンスの向上はありませんでした。

この種のタスクでマルチスレッドを活用する方法はありますか?
タスクがメモリ制限になり、並列化してもメリットがないかどうかを検証するにはどうすればよいですか? CPU で 2 つの配列を乗算するタスクを AVX で並列化する利点はありますか?

私が試したコンピュータは、Core i7 4770k (Haswell) ベースです。

Visual Studio 2013のプロジェクトへのリンクは次のとおりです。

ありがとうございました。

4

3 に答える 3

3

画像は合計 200 MB (2 x 5000 x 5000 x 4 バイト) を占めます。したがって、各ブロックは 50 MB のデータで構成されます。これは、CPU の L3 キャッシュのサイズの 6 倍以上です (こちらを参照)。各 AVX ベクトル乗算は 256 ビットのデータで動作します。これはキャッシュ ラインの半分です。つまり、ベクトル命令ごとに 1 つのキャッシュ ラインを消費します (引数ごとにキャッシュ ラインの半分)。Haswell でのベクトル化された乗算のレイテンシは 5 サイクルで、FPU は 1 サイクルあたり 2 つの命令をリタイアできます (こちらを参照)。i7-4770K のメモリ バスの定格は 25.6 GB/s (理論値)最大!) または毎秒 4 億 3000 万以下のキャッシュ ライン。CPU の公称速度は 3.5 GHz です。AVX 部分のクロックは少し低く、3.1 GHz としましょう。その速度では、AVX エンジンに完全にデータを供給するには、毎秒桁違いに多くのキャッシュ ラインが必要です。

このような状況では、ベクトル化されたコードの 1 つのスレッドが CPU のメモリ バスをほぼ完全に飽和させます。2 番目のスレッドを追加すると、非常にわずかな改善が得られる場合があります。さらにスレッドを追加しても、競合が発生し、オーバーヘッドが増えるだけです。このような計算を高速化する唯一の方法は、メモリ帯域幅を増やすことです。

  • より多くのメモリ コントローラを備えた NUMA システム上で実行されるため、マルチソケット サーバー ボードなどの総メモリ帯域幅が高くなります。
  • Intel Xeon Phi や GPGPU など、メモリ帯域幅がはるかに高い別のアーキテクチャに切り替えます。
于 2016-05-02T13:56:48.843 に答える
1

私自身の調査によると、合計 CPU キャッシュは約 8MB のようです。6000*4/4 (6000 個の float を 4 つのブロックに分割) は 6MB です。これに 2 を掛けると (インとアウト)、キャッシュの外側になります。

私はこれをテストしていませんが、ブロック数を増やすとパフォーマンスが向上するはずです。最初は 8 から始めてみてください (お使いの CPU はハイパースレッディングを 8 つの仮想コアに移植します)。

現在、OpenMP で生成されたさまざまなプロセスのそれぞれにキャッシュの競合があり、メイン メモリから (再) ロードする必要があります。ブロックのサイズを小さくすると、これに役立ちます。個別のキャッシュを使用すると、キャッシュのサイズが効果的に増加しますが、それはオプションではないようです。

原理の証明としてこれを行っているだけの場合は、グラフィック カードで実行してテストすることをお勧めします。ただし、それを適切に実装するのはさらに難しい場合があります。

于 2016-05-01T18:10:02.903 に答える