web-dev-qa-db-ja.com

C int配列をゼロにリセット:最速の方法?

T = int、unsigned int、long long int、またはunsigned long long intのT myarray[100]があると仮定すると、すべてのコンテンツをゼロにリセットする最も速い方法は(初期化だけでなく、私のプログラムでの時間)?多分memsetで?

T *myarray = new T[100]のような動的配列についても同じ質問です。

90
Vincent

memset<string.h>から)は、おそらくアセンブリで直接記述され、手動で最適化されるルーチンであるため、おそらく最速の標準的な方法です。

memset(myarray, 0, sizeof(myarray)); // for automatically-allocated arrays
memset(myarray, 0, N*sizeof(*myarray)); // for heap-allocated arrays, where N is the number of elements

ところで、C++での慣用的な方法は、std::fill<algorithm>から)を使用することです。

std::fill(myarray, myarray+N, 0);

which maymemsetに自動的に最適化されます。 memsetの場合はintと同じくらい高速に動作しますが、オプティマイザが十分に賢くない場合は、小さな型の場合は若干パフォーマンスが低下する可能性があります。それでも、疑わしい場合は、プロファイルします。

154
Matteo Italia

この質問は、かなり古いものですが、最も慣用的な方法ではなく、最も少ない行数で記述できる方法ではなく、最速方法を要求するため、いくつかのベンチマークが必要です。そして、実際のテストなしでその質問に答えるのはばかげています。そこで、memset vs. std :: fill、AnTの答えのゼロ、AVX組み込み関数を使用して作成したソリューションの4つのソリューションを比較しました。

このソリューションは一般的なものではなく、32ビットまたは64ビットのデータでのみ機能することに注意してください。このコードが何か間違っている場合はコメントしてください。

#include<immintrin.h>
#define intrin_ZERO(a,n){\
size_t x = 0;\
const size_t inc = 32 / sizeof(*(a));/*size of 256 bit register over size of variable*/\
for (;x < n-inc;x+=inc)\
    _mm256_storeu_ps((float *)((a)+x),_mm256_setzero_ps());\
if(4 == sizeof(*(a))){\
    switch(n-x){\
    case 3:\
        (a)[x] = 0;x++;\
    case 2:\
        _mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
    case 1:\
        (a)[x] = 0;\
        break;\
    case 0:\
        break;\
    };\
}\
else if(8 == sizeof(*(a))){\
switch(n-x){\
    case 7:\
        (a)[x] = 0;x++;\
    case 6:\
        (a)[x] = 0;x++;\
    case 5:\
        (a)[x] = 0;x++;\
    case 4:\
        _mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
    case 3:\
        (a)[x] = 0;x++;\
    case 2:\
        ((long long *)(a))[x] = 0;break;\
    case 1:\
        (a)[x] = 0;\
        break;\
    case 0:\
        break;\
};\
}\
}

低レベルの最適化の専門家ではないため、これが最速の方法であると主張することはありません。むしろ、それはmemsetよりも高速な正しいアーキテクチャ依存の実装の例です。

今、結果に。静的および動的に割り当てられたサイズ100 intおよびlong long配列のパフォーマンスを計算しましたが、静的配列でデッドコードを除去したmsvcを除き、結果は非常に類似していたため、動的配列のパフォーマンスのみを示します。 timeマーキングは、time.hの低精度の時計機能を使用して、100万回の反復でmsです。

clang 3.8(clang-clフロントエンドを使用、最適化フラグ=/OX/Arch:AVX/Oi/Ot)

int:
memset:      99
fill:        97
ZERO:        98
intrin_ZERO: 90

long long:
memset:      285
fill:        286
ZERO:        285
intrin_ZERO: 188

gcc 5.1.0(最適化フラグ:-O3 -march = native -mtune = native -mavx):

int:
memset:      268
fill:        268
ZERO:        268
intrin_ZERO: 91
long long:
memset:      402
fill:        399
ZERO:        400
intrin_ZERO: 185

msvc 2015(最適化フラグ:/ OX/Arch:AVX/Oi/Ot):

int
memset:      196
fill:        613
ZERO:        221
intrin_ZERO: 95
long long:
memset:      273
fill:        559
ZERO:        376
intrin_ZERO: 188

MSVCの典型的なむらのある最適化(gvmのgccの強制終了)は非常に興味深いものです(静的配列で印象的なデッドコードの除去を行い、塗りつぶしに対してひどいパフォーマンスを発揮します)。私の実装は非常に高速ですが、これはビットクリアリングが他の設定操作よりもオーバーヘッドがはるかに少ないことを認識しているためです。

Clangの実装は、非常に高速であるため、より注目する価値があります。いくつかの追加テストでは、そのmemsetが実際にゼロに特化されていることが示されています。400バイト配列の非ゼロmemsetsは非常に遅く(〜220ms)、gccに匹敵します。ただし、800バイト配列を使用したゼロ以外のmemsettingは速度の違いをもたらさないため、おそらくその場合、memsetのパフォーマンスは実装よりも劣ります。特殊化は小さな配列専用であり、カットオフは約800バイトです。また、gccの 'fill'と 'ZERO'はmemsetに対して最適化されていない(生成されたコードを見て)ことに注意してください。gccは単純に同一のパフォーマンス特性を持つコードを生成しています。

結論:memsetはこのタスクに対して実際には最適化されておらず、人々がそうするふりをするでしょう(そうでない場合、gccとmsvcとllvmのmemsetは同じパフォーマンスになります)。パフォーマンスが問題になる場合、特にこれらの厄介な中規模の配列の場合、memsetは最終的な解決策にはなりません。ビットクリアに特化されておらず、コンパイラが単独で行うよりも手作業で最適化されていないためです。

14
Benjamin

memset() から:

memset(myarray, 0, sizeof(myarray));

myarrayのサイズがコンパイル時にわかっている場合は、sizeof(myarray)を使用できます。それ以外の場合、mallocまたはnewを介して取得した動的サイズの配列を使用している場合、長さを追跡する必要があります。

11
Alex Reynolds

memsetを使用できますが、これは型の選択が整数型に制限されているためです。

Cの一般的なケースでは、マクロを実装するのが理にかなっています

#define ZERO_ANY(T, a, n) do{\
   T *a_ = (a);\
   size_t n_ = (n);\
   for (; n_ > 0; --n_, ++a_)\
     *a_ = (T) { 0 };\
} while (0)

これにより、memsetのようなハッキングに頼ることなく、あらゆるタイプのオブジェクトの配列を「ゼロにリセット」できるC++のような機能が得られます。基本的に、これはC++関数テンプレートのCアナログです。ただし、type引数を明示的に指定する必要があります。

その上で、非減衰配列用の「テンプレート」を構築できます

#define ARRAY_SIZE(a) (sizeof (a) / sizeof *(a))
#define ZERO_ANY_A(T, a) ZERO_ANY(T, (a), ARRAY_SIZE(a))

あなたの例では、次のように適用されます

int a[100];

ZERO_ANY(int, a, 100);
// or
ZERO_ANY_A(int, a);

また、特にスカラー型のオブジェクトについては、型に依存しないマクロを実装できることに注意してください

#define ZERO(a, n) do{\
   size_t i_ = 0, n_ = (n);\
   for (; i_ < n_; ++i_)\
     (a)[i_] = 0;\
} while (0)

そして

#define ZERO_A(a) ZERO((a), ARRAY_SIZE(a))

上記の例を

 int a[100];

 ZERO(a, 100);
 // or
 ZERO_A(a);
5
AnT

静的宣言には、次のものを使用できると思います。

T myarray[100] = {0};

動的宣言についても、同じ方法をお勧めします:memset

3
Bruno Soares

zero(myarray);は、C++で必要なものすべてです。

これをヘッダーに追加するだけです:

template<typename T, size_t SIZE> inline void zero(T(&arr)[SIZE]){
    memset(arr, 0, SIZE*sizeof(T));
}
2
Navin

私が使用する関数は次のとおりです。

template<typename T>
static void setValue(T arr[], size_t length, const T& val)
{
    std::fill(arr, arr + length, val);
}

template<typename T, size_t N>
static void setValue(T (&arr)[N], const T& val)
{
    std::fill(arr, arr + N, val);
}

次のように呼び出すことができます。

//fixed arrays
int a[10];
setValue(a, 0);

//dynamic arrays
int *d = new int[length];
setValue(d, length, 0);

上記は、memsetを使用するよりもC++ 11の方法です。また、サイズを指定して動的配列を使用すると、コンパイル時エラーが発生します。

1
Shital Shah