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


102

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

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


16
@BoPersson:まあ、new される C ++ ...
マッテオイタリア

@マッテオ-ええ、ええ。答えにはあまり影響しませんでした(今までは:-)。
Bo Persson、2012

3
@BoPersson:memsetC ++がなんらかの形で関与しているときだけ話していると気分が悪くなりました... :)
Matteo Italia

2
最近のコンパイラーでは、単純なforループを打ち負かすことはできません。しかし、驚くべきことに、賢くなろうとすることで、もっと悪いことができるようになります。
David Schwartz

構造体を使用し、その中に配列を貼り付けます。すべてゼロのインスタンスを作成します。これを使用して、作成した他のオブジェクトをゼロにします。それはうまくいきます。インクルードなし、機能なし、かなり高速。
Xofo

回答:


170

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(fromから<algorithm>)を使用することです。

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

これできるに自動的に最適化しますmemset。私はかなり確実で、それは限り速く動作することだmemsetためint、オプティマイザは、スマート十分でない場合、それは小さいタイプのため、わずかに悪化し実行するかもしれないが、S。それでも、疑わしい場合は、プロファイルを作成します。


10
1999 ISO C標準の時点ではmemset、整数を0に設定することが実際に保証されていませんでした。all-bits-zeroがの表現であるという特定のステートメントはありませんでした0。Technical Corrigendumは、2011年のISO C規格に含まれているそのような保証を追加しました。all-bits-zero 0既存のすべてのCおよびC ++実装におけるすべての整数型の有効な表現であると私は信じています。そのため、委員会はその要件を追加できました。(浮動小数点型またはポインター型には同様の保証はありません。)
キース・トンプソン

3
@KeithThompsonのコメントに追加:この保証は、TC2(2004)のプレーンテキストで6.2.6.2/5に追加されました。ただし、パディングビットがない場合、6.2.6.2 / 1および/ 2は、すべてのビットがゼロであることをすでに保証しています0。(パディングビットを使用すると、all-bits-zeroがトラップ表現になる可能性があります)。しかし、いずれにせよ、TCは欠陥のあるテキストを承認して置き換えることになっているため、2004年の時点では、C99に常にこのテキストが含まれているかのように行動する必要があります。
MM

Cでは、動的配列を正しく割り当てた場合、2つのメムセットの間に違いはありません。正しい動的割り当てはになりますint (*myarray)[N] = malloc(sizeof(*myarray));
ランディン

@Lundin:もちろん-コンパイル時にその大きさNがわかっている場合は、ほとんどの場合、使用したmalloc場合は実行時にしかわかっていません。
Matteo Italia

@MatteoItalia我々は1999年以来のVLAを持っていた
ランディン

20

この質問はかなり古いですが、最も慣用的な方法ではなく、最少数の行で記述できる方法で最速の方法を要求するため、いくつかのベンチマークが必要です。そして、実際のテストなしにその質問に答えることはばかげています。そこで、memsetとstd :: fillとAnTの答えのZEROと、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.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

ここでは多くの興味深いことが行われています:llvm killing gcc、MSVCの典型的なむらのある最適化(静的配列で印象的なデッドコードの除去を行い、fillのパフォーマンスがひどい)。私の実装は大幅に高速ですが、これは、ビットのクリアが他のどの設定操作よりもオーバーヘッドがはるかに少ないことを認識しているためである可能性があります。

Clangの実装は、大幅に高速であるため、より詳細に検討するメリットがあります。いくつかの追加のテストは、そのmemsetが実際にゼロに特化していることを示しています-400バイト配列の非ゼロmemsetsははるかに遅く(〜220ms)、gccに匹敵します。ただし、800バイトの配列を使用したゼロ以外のmemsettingは速度の違いをもたらさないため、おそらくその場合、それらのmemsetは私の実装よりもパフォーマンスが悪いのです-特殊化は小さな配列でのみ行われ、カットオフは約800バイトです。また、gccの「fill」と「ZERO」はmemsetに最適化されていない(生成されたコードを見る)ことに注意してください。gccは、同じパフォーマンス特性を持つコードを生成するだけです。

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


4
コードなしで、コンパイラーのバージョンと使用されているオプションの言及がないベンチマーク?うーん...
マーク・グリセ

私はすでにコンパイラーのバージョンを持っています(それらはほんの少し隠されていました)、そして使用される適切なオプションを追加しました。
ベンジャミン

単項 '*'の型引数が無効です( 'size_t {aka unsigned int}'があります)|
Piotr Wasilewicz 2017年

独自の最適化されたゼロ化方法を書くほど寛大である-それがどのように機能するかについて少しの言葉を惜しみませんか、なぜそれが速いのですか?コードはすべて自明です。
Motti Shneor

1
@MottiShneorそれはそれよりも複雑に見えます。AVXレジスタのサイズは32バイトです。したがって、彼aはレジスターに適合する値の数を計算します。その後、彼はすべての32バイトブロックをループします。これは、ポインター演算((float *)((a)+x))を使用して完全に上書きする必要があります。2つの組み込み関数(で始まる_mm256)は、ゼロで初期化された32バイトのレジスターを作成し、それを現在のポインターに格納するだけです。これは最初の3行です。残りは、最後の32バイトブロックが完全に上書きされるべきではないすべての特殊なケースを処理します。ベクトル化により高速になります。-お役に立てば幸いです。
wychmaster

11

からmemset()

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

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


2
コンパイル時に配列のサイズが不明な場合でも、sizeofは機能します。(もちろん、配列の場合のみ)
asaelr

2
@asaelr:C ++では、sizeof常にコンパイル時に評価されます(VLAでは使用できません)。C99では、VLAの場合はランタイム式にすることができます。
Ben Voigt 2013年

まあ@BenVoigt、質問は両方についてですcc++。「コンパイル時にmyarrayのサイズがわかっている場合は、sizeof(myarray)を使用できます」とAlexの回答にコメントしました。
asaelr 2013年

2
@asaelr:そしてC ++では、彼は完全に正しいです。あなたのコメントはC99やVLAについて何も述べていなかったので、それを明確にしたいと思いました。
Ben Voigt 2013年

5

使用できます 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)

これにより、C ++に似た機能が提供され、次のようなハックに頼ることなく、あらゆるタイプのオブジェクトの配列を「ゼロにリセット」できます。 memset。基本的に、これは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);

1
私はの;後を省略しwhile(0)ますのでZERO(a,n);、電話をかけることができます、+ 1素晴らしい答え
0x90

@ 0x90:はい、その通りです。do{}while(0)イディオムの要点はすべて;、マクロ定義に必要ありません。修繕。
AnT 2013年

3

静的宣言の場合、次のように使用できると思います。

T myarray[100] = {0};

動的宣言の場合も同じ方法をお勧めします。 memset


2
「初期化のためだけではない」という質問です。
Ben Voigt 2013年

2

zero(myarray); C ++で必要なすべてです。

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

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

1
これは不正解です。SIZEバイトがクリアされます。「memset(arr、0、SIZE * sizeof(T));」正しいでしょう。
Kornel Kisielewicz、2015年

@KornelKisielewiczドーッ!過去1.5年間でこの関数をコピー貼り付けした人がいないことを願っています:(
Navin

1
グーグルが私をここに連れてきたので、私はコメントしませんでしたね:)
Kornel Kisielewicz

1
この関数zeroは、たとえばT=char[10]arr引数が多次元配列である場合にも当てはまることに注意してくださいchar arr[5][10]
mandrake 2015年

1
はい、gcc 4.7.3でいくつかのケースをテストしました。配列の次元数ごとにテンプレートの特殊化が必要になるため、これはこの回答に注意するのが良いでしょう。ARRAY_SIZE多次元配列で使用した場合に誤ったサイズを与えるマクロなど、他の回答も一般化されていませんARRAY_DIM<n>_SIZE。より適切な名前はおそらくです。
mandrake

1

これが私が使う関数です:

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の方法です。また、サイズを指定して動的配列を使用すると、コンパイル時エラーが発生します。


元の質問はC ++ではなくCにあるため、std :: fillは適切な回答にはなりません
Motti Shneor
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.