Cで2D配列をゼロにする最も速い方法は?


92

Cで大きな2D配列を繰り返しゼロ化したいのですが、これが現在私がしていることです。

// Array of size n * m, where n may not equal m
for(j = 0; j < n; j++)
{
    for(i = 0; i < m; i++)
    {  
        array[i][j] = 0;
    }
}

memsetを使用してみました:

memset(array, 0, sizeof(array))

ただし、これは1Dアレイでのみ機能します。2D配列の内容をprintfすると、最初の行はゼロですが、ランダムな大きな数の負荷がかかり、クラッシュします。

回答:


177
memset(array, 0, sizeof(array[0][0]) * m * n);

どこmn2次元配列の幅と高さは、(あなたの例では、あなたがそう、正方形の2次元配列を持っていますm == n)。


1
動作しないようです。コードブロックで「プロセスが-1073741819を返しました」と表示されますが、これはセグメンテーション違反ですか?
Eddy

8
@Eddy:配列の宣言を見せてください。
GManNickG 2010年

1
memsetたぶん、1行だけをゼロにしてクラッシュしたと言ったので、ではなく他の行でクラッシュしていると思います。
ブリンディ

3
ええと。ちょうどアレイとして宣言されたテストを試みint d0=10, d1=20; int arr[d0][d1]、そしてmemset(arr, 0, sizeof arr);予想される(でコンパイルGCC 3.4.6、として働いていた-std=c99 -Wallフラグ)。「自分のマシンで動作する」とは、しゃがむことを意味しますが、動作するmemset(arr, 0, sizeof arr); はずでした。配列全体で使用されるバイト数を返すsizeof arr 必要があります(d0 * d1 * sizeof(int))。 sizeof array[0] * m * n配列の正しいサイズが得られません。
John Bode

4
@John Bode:はい、ただし配列の取得方法によって異なります。あなたは、パラメータをとる関数を持っている場合はint array[][10]、その後、sizeof(array) == sizeof(int*)以来、最初の次元の大きさが知られていません。OPは配列の取得方法を指定しませんでした。
James McNellis、2010年

77

arrayが本当に配列の場合、次のようにして「ゼロ化」できます。

memset(array, 0, sizeof array);

ただし、知っておくべき2つのポイントがあります。

  • これは、arrayが実際に「2次元配列」である場合、つまり、T array[M][N];あるタイプに対して宣言された場合にのみ機能しTます。
  • array宣言されたスコープでのみ機能します。関数に渡すと、名前array はポインタ減衰しsizeof、配列のサイズはわかりません。

実験してみましょう:

#include <stdio.h>

void f(int (*arr)[5])
{
    printf("f:    sizeof arr:       %zu\n", sizeof arr);
    printf("f:    sizeof arr[0]:    %zu\n", sizeof arr[0]);
    printf("f:    sizeof arr[0][0]: %zu\n", sizeof arr[0][0]);
}

int main(void)
{
    int arr[10][5];
    printf("main: sizeof arr:       %zu\n", sizeof arr);
    printf("main: sizeof arr[0]:    %zu\n", sizeof arr[0]);
    printf("main: sizeof arr[0][0]: %zu\n\n", sizeof arr[0][0]);
    f(arr);
    return 0;
}

私のマシンでは、上記は印刷されます:

main: sizeof arr:       200
main: sizeof arr[0]:    20
main: sizeof arr[0][0]: 4

f:    sizeof arr:       8
f:    sizeof arr[0]:    20
f:    sizeof arr[0][0]: 4

arr配列であっても、に渡されるf()と最初の要素へのポインタに減衰するため、出力されるサイズf()は「間違っています」。また、f()のサイズarr[0]はのサイズですarr[0]。これは、「配列[5]のint」です。int *「減衰」は最初のレベルでのみ発生するため、これはのサイズではありませんf()。そのため、正しいサイズの配列へのポインタを受け取ると宣言する必要があります。

したがって、先に述べたように、元々行っていたことが機能するのは、上記の2つの条件が満たされた場合のみです。そうでない場合は、他の人が言ったことを行う必要があります。

memset(array, 0, m*n*sizeof array[0][0]);

最後に、投稿しmemset()forループは厳密には同等ではありません。ポインターや浮動小数点値などの特定のタイプでは、「すべてのビット0」がゼロに等しくないコンパイラーが存在する可能性があります(かつて存在していました)。私はあなたがそれについて心配する必要があるとは思いません。


memset(array, 0, n*n*sizeof array[0][0]);あなたm*nn*n正しくないことを意味すると思いますか?
Tagc

不気味なことに、これが0の代わり、1及び2のような値で動作するようには思えない
アシシュ・アフジャ

memsetバイト(文字)レベルで動作します。以来1または2基礎となる表現で同じバイトを持っていない、あなたがそれを行うことはできませんmemset
Alok Singhal 2018

たぶん、ということを指摘@AlokSinghal intあなたのシステムに4バイトである」という読者が簡単に合計を計算することができるように、最小限の作業例の前にどこか。
71GA

9

まあ、それを行う最も速い方法は、それをまったく行わないことです。

奇妙に聞こえますが、ここにいくつかの擬似コードがあります:

int array [][];
bool array_is_empty;


void ClearArray ()
{
   array_is_empty = true;
}

int ReadValue (int x, int y)
{
   return array_is_empty ? 0 : array [x][y];
}

void SetValue (int x, int y, int value)
{
   if (array_is_empty)
   {
      memset (array, 0, number of byte the array uses);
      array_is_empty = false;
   }
   array [x][y] = value;
}

実際、それはまだ配列をクリアしていますが、何かが配列に書き込まれているときだけです。これはここでは大きな利点ではありません。ただし、2D配列が、四分木(動的な1つのマインドではない)、またはデータの行のコレクションを使用して実装された場合は、ブールフラグの効果をローカライズできますが、さらに多くのフラグが必要になります。クアッドツリーではルートノードに空のフラグを設定し、行の配列では各行にフラグを設定します。

「なぜ大きな2D配列を繰り返しゼロにしたいのか」という質問につながるのはどれですか。アレイは何に使用されますか?配列をゼロにする必要がないようにコードを変更する方法はありますか?

たとえば、次の場合:

clear array
for each set of data
  for each element in data set
    array += element 

つまり、それをアキュムレーションバッファーに使用し、次のように変更すると、パフォーマンスが向上します。

 for set 0 and set 1
   for each element in each set
     array = element1 + element2

 for remaining data sets
   for each element in data set
     array += element 

これは、配列をクリアする必要はありませんが、機能します。そして、それは配列をクリアするよりもはるかに速くなります。私が言ったように、最速の方法はそもそもそれをしないことです。


問題を調べるための興味深い代替方法。
Beska

1
この場合、すべての読み取りに対して追加の比較/ブランチを追加することで、配列の初期化を延期する価値があるかどうかはわかりません(ただし、それはあなたの場合です)。配列が非常に大きいため初期化時間が深刻な問題になる場合は、代わりにハッシュを使用できます。
tixxit

8

あなたが本当に、本当にスピードにこだわっている(そして移植性にそれほど夢中になっていない)場合、これを行うための絶対最速の方法は、SIMDベクトル組み込み関数を使用することだと思います。たとえばIntel CPUでは、次のSSE2命令を使用できます。

__m128i _mm_setzero_si128 ();                   // Create a quadword with a value of 0.
void _mm_storeu_si128 (__m128i *p, __m128i a);  // Write a quadword to the specified address.

各ストア命令は、1つのヒットで4つの32ビット整数をゼロに設定します。

pは16バイト境界で整列する必要がありますが、この制限はキャッシュに役立つため、速度の点でも優れています。他の制限は、pが16バイトの倍数である割り当てサイズをポイントする必要があることですが、これもループを簡単に展開できるのでクールです。

これをループに入れ、ループを数回展開すると、非常に高速なイニシャライザができます。

// Assumes int is 32-bits.
const int mr = roundUpToNearestMultiple(m, 4);      // This isn't the optimal modification of m and n, but done this way here for clarity.    
const int nr = roundUpToNearestMultiple(n, 4);    

int i = 0;
int array[mr][nr] __attribute__ ((aligned (16)));   // GCC directive.
__m128i* px = (__m128i*)array;
const int incr = s >> 2;                            // Unroll it 4 times.
const __m128i zero128 = _mm_setzero_si128();

for(i = 0; i < s; i += incr)
{
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
}

のバリアントもあります _mm_storeuキャッシュをバイパス(つまり、アレイをゼロにしてもキャッシュは汚染されません)。これにより、状況によっては、副次的なパフォーマンス上の利点が得られます。

SSE2リファレンスについては、こちらを参照してくださいhttp ://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx


5

で配列を初期化する場合はmalloccalloc代わりにを使用してください。それはあなたの配列を無料でゼロにします。(明らかにmemsetと同じパフォーマンスで、コードが少なくて済みます。)


配列を繰り返しゼロにしたい場合、これはmemsetよりも高速ですか?
Eddy

callocは、初期化時に一度それをゼロにし、おそらくmallocの後にmemsetを呼び出すよりも速くはありません。その後は独力で、再びゼロに戻したい場合はmemsetを使用できます。配列が非常に大きい場合を除いて、perfは妥当なマシンでの考慮事項ではありません。
Ben Zotto


2

2D配列はどのように宣言されましたか?

次のような場合:

int arr[20][30];

あなたはそれをゼロにすることができます:

memset(arr, sizeof(int)*20*30);

char [10] [10]配列を使用しました。しかし、エラーが発生memset(a, 0, sizeof(char)*10*10);しました。「memset」関数には引数が少なすぎて、うまく機能します。、どうやって起こるの?
noufal

1

mallocの代わりにcallocを使用してください。callocはすべてのフィールドを0に開始します。

int * a =(int *)calloc(n、size of(int));

// aのすべてのセルが0に初期化されました


0

手作業で行うには、次のコードに従うのが最も速いと思います。速度をmemset関数と比較できますが、遅くなることはありません。

(配列タイプがintと異なる場合は、ptrおよびptr1ポインターのタイプをintに変更します)


#define SIZE_X 100
#define SIZE_Y 100

int *ptr, *ptr1;
ptr = &array[0][0];
ptr1 = ptr + SIZE_X*SIZE_Y*sizeof(array[0][0]);

while(ptr < ptr1)
{
    *ptr++ = 0;
}


コードはおそらくmemsetchar型よりも遅くなります。
tofro

0
memset(array, 0, sizeof(int [n][n]));

1
array [n] [n]は配列の1要素のサイズであるため、配列の最初の要素のみが初期化されます。
EvilTeach 2010年

おっとっと。その通りその通り。配列のルックアップではなく、型のシグネチャを括弧に入れるつもりでした。修正しました。
swestrup


-2

これは、sizeof(array)がarrayが指すオブジェクトの割り当てサイズを提供するために発生します。(配列は、多次元配列の最初の行へのポインタにすぎません)。ただし、サイズiのj個の配列を割り当てました。したがって、sizeof(array)によって返される1つの行のサイズに、割り当てた行の数を掛ける必要があります。例:

bzero(array, sizeof(array) * j);

sizeof(array)は静的に割り当てられた配列に対してのみ機能することにも注意してください。動的に割り当てられた配列の場合は、次のように記述します

size_t arrayByteSize = sizeof(int) * i * j; 
int *array = malloc(array2dByteSite);
bzero(array, arrayByteSize);

最初の部分は間違っています。以下のためにsizeofオペレータ、arrayポインタではない(これは、配列を宣言された場合)。例については、私の回答を参照してください。
Alok Singhal
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.