web-dev-qa-db-ja.com

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

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すると、最初の行はゼロになりますが、その後、大量のランダムな乱数が発生し、クラッシュします。

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

ここで、mnは2次元配列の幅と高さです(この例では、2次元の正方形配列があるため、m == n)。

164
James McNellis

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

memset(array, 0, sizeof array);

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

  • これは、arrayが実際に「2次元配列」である場合にのみ機能します。つまり、何らかの型Tに対してT array[M][N];が宣言されました。
  • 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]のサイズです。これは、「intの配列[5]」です。 「減衰」は最初のレベルでのみ発生するため、int *のサイズではありません。そのため、f()を正しいサイズの配列へのポインターとして宣言する必要があります。

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

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

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

73
Alok Singhal

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

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

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;
}

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

「大きな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 

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

9
Skizz

あなたが本当に、本当に速度に取りつかれているなら(そして移植性にはあまりこだわらないなら)、これを行う絶対的な最も速い方法は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バイトに揃える必要がありますが、この制限はキャッシュにも役立つため、速度にも適しています。もう1つの制限は、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

8
Jarrod Smith

mallocで配列を初期化する場合は、代わりにcallocを使用してください。配列を無料でゼロにします。 (明らかにmemsetと同じperfで、コードが少なくなります。)

5
Ben Zotto

int array[N][M] = {0};

...少なくともGCC 4.8では。

3
Engineer

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

次のような場合:

int arr[20][30];

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

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

Mallocの代わりにcallocを使用します。 callocはすべてのフィールドを0に初期化します。

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

//のすべてのセルは0に初期化されています

1
DUDE_MXP

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

(配列型がint以外の場合、ptrおよびptr1ポインターの型を変更します)


#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;
}
0
mack369
memset(array, 0, sizeof(int [n][n]));
0
swestrup

これを試すことができます

int array[20,30] = {{0}};
0
Călin Calin