2

私は C++ に比較的慣れておらず (科学アプリのパフォーマンスのために Java から移動しました)、SSE については何も知りません。それでも、次の非常に単純なコードを改善する必要があります。

    int myMax=INT_MAX;
    int size=18000003;
    vector<int> nodeCost(size);

    /* init part */
    for (int k=0;k<size;k++){
     nodeCost[k]=myMax;
    }

初期化部分の時間を測定したところ、13ms かかりました。これは私の科学アプリには大きすぎます (アルゴリズム全体が 22ms で実行されるため、初期化には合計時間の 1/2 がかかります)。初期化部分は、同じベクトルに対して複数回繰り返されることに注意してください。

ご覧のとおり、ベクトルのサイズは 4 で割られていません。SSE で初期化を高速化する方法はありますか? どのように提案できますか?配列を使用する必要がありますか、それとも SSE をベクトルでも使用できますか?

どうか、あなたの助けが必要なので、a) 「どのように時間を測定しましたか」または b) 「時期尚早の最適化は諸悪の根源です」という質問はどちらも合理的ですが、a) 測定された時間は正しいです b ) 同意しますが、他に選択肢はありません。コードを OpenMP で並列化したくないので、SSE が唯一の代替手段です。

ご協力いただきありがとうございます

4

3 に答える 3

8

ベクトルのコンストラクターを使用します。

std::vector<int> nodeCost(size, myMax);

これは、ベクトルを埋めるために最適化された「memset」タイプの実装を使用する可能性が最も高いです。

また、コンパイラにアーキテクチャ固有のコードを生成するように指示します ( -march=native -O3GCC など)。私の x86_64 マシンでは、これにより、ベクトルを埋めるための次のコードが生成されます。

L5:
    add     r8, 1                    ;; increment counter
    vmovdqa YMMWORD PTR [rax], ymm0  ;; magic, ymm contains the data, and eax...
    add     rax, 32                  ;; ... the "end" pointer for the vector
    cmp     r8, rdi                  ;; loop condition, rdi holds the total size
    jb      .L5

movdqa256 ビット操作用にサイズがプレフィックスされた命令は、一度に 32 バイトをメモリにコピーします。これは AVX 命令セットの一部です。

于 2013-07-17T21:45:55.227 に答える
5

すでに提案されているように最初に試してみてくださいstd::fill。それでもまだ十分に速くない場合は、本当に必要な場合は SIMD に行くことができます。CPU とメモリ サブシステムによっては、このような大きなベクトルの場合、DRAM の最大帯域幅に達する可能性があり、それが制限要因になる可能性があることに注意してください。とにかく、これはかなり単純な SSE 実装です。

#include <emmintrin.h>

const __m128i vMyMax = _mm_set1_epi32(myMax);
int * const pNodeCost = &nodeCost[0];
for (k = 0; k < size - 3; k += 4)
{
    _mm_storeu_si128((__m128i *)&pNodeCost[k], vMyMax);
}
for ( ; k < size; ++k)
{
    pNodeCost[k] = myMax;
}

これは、最新の CPU ではうまく機能するはず_mm_store_si128です_mm_storeu_si128。例えば

#include <emmintrin.h>

const __m128i vMyMax = _mm_set1_epi32(myMax);
int * const pNodeCost = &nodeCost[0];
for (k = 0; k < size && (((intptr_t)&pNodeCost[k] & 15ULL) != 0); ++k)
{                                              // initial scalar loop until we
    pNodeCost[k] = myMax;                      // hit 16 byte alignment
}
for ( ; k < size - 3; k += 4)                  // 16 byte aligned SIMD loop
{
    _mm_store_si128((__m128i *)&pNodeCost[k], vMyMax);
}
for ( ; k < size; ++k)                         // scalar loop to take care of any
{                                              // remaining elements at end of vector
    pNodeCost[k] = myMax;
}
于 2013-07-17T21:53:45.767 に答える
2

これは、Mats Petersson のコメントのアイデアを拡張したものです。

これを本当に気にするなら、参照局所性を改善する必要があります。72 メガバイトの初期化を実行し、後でそれを上書きするためだけに戻ってくるのは、メモリ階層にとって非常に不親切です。

std::vector常に自分自身を初期化するため、ストレート C++ でこれを行う方法がわかりません。ただし、(1) を使用callocfreeてメモリを割り当てることもできます。(2) 配列の要素を「0 の意味myMaxn意味n-1」として解釈します。(「コスト」は非負であると想定しています。それ以外の場合は、このスキームを少し調整する必要があります。ポイントは、明示的な初期化を避けることです。)

callocLinux システムでは、カーネルから直接取得されたページは既にゼロに設定されているため、十分に大きなブロックがメモリを明示的にゼロにする必要がないため、これが役立ちます。さらに良いことに、それらは最初に触れたときにのみマップされ、ゼロにされます。これは非常にキャッシュフレンドリーです.

(私のUbuntu 13.04システムでは、Linuxcallocは明示的に初期化しないほどスマートです。そうでない場合は、このアプローチを使用するためmmapに ofを実行する必要があるかもしれません...)/dev/zero

はい、これは、配列へのすべてのアクセスに 1 の加算/減算が含まれることを意味します (ただし、「最小」または「最大」などの操作ではありません)。他の作業と並行して行うため、これによりパフォーマンスが大幅に向上する可能性が十分にあります。

もちろん、これが役立つかどうかはプラットフォームに依存します。

于 2013-07-17T22:07:04.840 に答える