C에서 2D 배열을 0으로 만드는 가장 빠른 방법은 무엇입니까?


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 배열의 내용을 인쇄하면 첫 번째 행은 0이지만 임의의 큰 숫자가로드되어 충돌합니다.

답변:


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

어디 mn2 차원 배열의 폭과 높이 (귀하의 예제에서, 당신이 그렇게, 사각형 2 차원 배열을 가지고 있습니다 m == n).


1
작동하지 않는 것 같습니다. 코드 블록에서 '프로세스가 -1073741819를 반환했습니다'라는 메시지가 나타납니다. 이것이 바로 세그 오류입니까?
Eddy

8
@Eddy : 배열의 선언을 보여주세요.
GManNickG

1
나는 그것이 아닌 다른 줄에서 충돌하고 있다고 확신합니다 memset.
Blindy

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

77

경우 array진정으로 배열입니다, 당신은 함께 "그것을 밖으로 제로"할 수 있습니다 :

memset(array, 0, sizeof array);

그러나 알아야 할 두 가지 사항이 있습니다.

  • 이것은 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()의 크기 는 "array [5] of "인 arr[0]배열의 크기입니다 . "감쇠"는 첫 번째 수준에서만 발생하기 때문에 의 크기가 아니므 로 올바른 크기의 배열에 대한 포인터를 사용하는 것으로 선언해야합니다 .arr[0]intint *f()

그래서 제가 말했듯이, 당신이 원래하던 일은 위의 두 가지 조건이 충족 되어야만 작동 할 것입니다. 그렇지 않은 경우 다른 사람이 말한대로해야합니다.

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

마지막으로 게시 memset()for루프는 엄격한 의미에서 동일하지 않습니다. 포인터 및 부동 소수점 값과 같은 특정 유형에 대해 "모든 비트 0"이 0이 아닌 컴파일러가있을 수 있습니다. 나는 당신이 그것에 대해 걱정할 필요가 있는지 의심합니다.


memset(array, 0, n*n*sizeof array[0][0]);옳지 m*n않다는 뜻 n*n인가요?
Tagc

이상하게 충분히,이 0 대신 1, 2와 같은 값으로 작동하지 않는 것
인 Ashish Ahuja

memset바이트 (char) 수준에서 작동합니다. 기본 표현에 동일한 바이트가 1있거나 없기 때문에 . 2memset
Alok Singhal

어쩌면 지적 @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 배열이 쿼드 트리 (동적 한 마음이 아님) 또는 데이터 행 모음을 사용하여 구현 된 경우 부울 플래그의 효과를 지역화 할 수 있지만 더 많은 플래그가 필요합니다. 쿼드 트리에서는 루트 노드에 대해 빈 플래그를 설정하고 행 배열에서 각 행에 대한 플래그를 설정합니다.

"대형 2D 배열을 반복적으로 0으로 설정하려는 이유"라는 질문으로 이어지는 것은 무엇입니까? 어레이의 용도는 무엇입니까? 배열을 0으로 만들 필요가 없도록 코드를 변경하는 방법이 있습니까?

예를 들어 다음과 같은 경우 :

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.

각 저장 명령은 한 번의 히트에서 4 개의 32 비트 정수를 0으로 설정합니다.

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캐시를 우회 하는 변형도 있습니다 (즉, 어레이를 0으로 설정해도 캐시가 오염되지 않음). 일부 상황에서 2 차 성능 이점을 제공 할 수 있습니다.

SSE2 참조는 여기를 참조하십시오 : http://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx


5

로 배열을 초기화하는 malloc경우 calloc대신 사용하십시오. 무료로 어레이를 제로화합니다. (분명히 memset과 동일한 성능이며 코드가 적습니다.)


어레이를 반복적으로 제로화하려는 경우 memset보다 빠릅니까?
Eddy

calloc은 초기화 시간에 한 번 0으로 설정하고 malloc 다음에 memset을 호출하는 것보다 빠르지 않을 것입니다. 그 후에는 혼자이며 다시 제로화하려면 memset을 사용할 수 있습니다. 배열이 정말 거대하지 않는 한 perf는 합리적인 시스템에서 실제로 고려할 사항이 아닙니다.
Ben Zotto


2

2D 배열은 어떻게 선언 되었습니까?

다음과 같은 경우 :

int arr[20][30];

다음을 수행하여 제로화 할 수 있습니다.

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

char [10] [10] 배열을 사용했습니다. 하지만 오류가 발생했습니다. 'memset'기능에 인수가 너무 적고 memset(a, 0, sizeof(char)*10*10);잘 작동합니다. , 어떻게 발생합니까?
noufal

1

malloc 대신 calloc을 사용하십시오. calloc은 모든 필드를 0으로 초기화합니다.

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

// a의 모든 셀이 0으로 초기화되었습니다.


0

손으로하는 가장 빠른 방법은 코드를 따르는 것입니다. 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;
}


코드는 아마도 memsetchar 유형 보다 느릴 것 입니다.
tofro jul.

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

1
array [n] [n]은 배열의 요소 1 개의 크기이므로 배열의 첫 번째 요소 만 초기화됩니다.
EvilTeach

죄송합니다. 맞아요. 배열 조회가 아닌 괄호에 유형 서명을 넣으려고했습니다. 고쳤다.
swestrup


-2

이것은 sizeof (array)가 array가 가리키는 객체의 할당 크기를 제공하기 때문에 발생합니다 . ( 배열 은 다차원 배열의 첫 번째 행에 대한 포인터입니다). 그러나 크기가 i 인 j 배열 을 할당했습니다 . 따라서 할당 된 행 수와 sizeof (array)로 반환되는 한 행의 크기를 곱해야합니다. 예 :

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.