Cách nhanh nhất để xóa mảng 2d trong C?


92

Tôi muốn lặp lại bằng không một mảng 2d lớn trong C. Đây là những gì tôi làm vào lúc này:

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

Tôi đã thử sử dụng memset:

memset(array, 0, sizeof(array))

Nhưng điều này chỉ hoạt động đối với mảng 1D. Khi tôi in nội dung của mảng 2D, hàng đầu tiên là số 0, nhưng sau đó tôi nhận được một tải các số lớn ngẫu nhiên và nó bị treo.

Câu trả lời:


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

Ở đâu mnlà chiều rộng và chiều cao của mảng hai chiều (trong ví dụ của bạn, bạn có một mảng hai chiều vuông, vì vậy m == n).


1
Nó dường như không hoạt động. Tôi nhận được 'process trả về -1073741819' trên codeblocks, đó là lỗi seg phải không?
Eddy

8
@Eddy: Cho chúng tôi xem phần khai báo của mảng.
GManNickG

1
Tôi cá là nó bị rơi trên các dòng khác, không phải memset, bởi vì bạn đã đề cập đến việc bị rơi từ số không chỉ một hàng.
Blindy

3
Huh. Vừa thử kiểm tra một mảng được khai báo là int d0=10, d1=20; int arr[d0][d1]memset(arr, 0, sizeof arr);hoạt động như mong đợi (gcc 3.4.6, được biên dịch với các -std=c99 -Wallcờ). Tôi nhận ra rằng "nó hoạt động trên máy của tôi" có nghĩa là cố gắng ngồi xổm, nhưng memset(arr, 0, sizeof arr); lẽ ra phải hoạt động. sizeof arr sẽ trả về số byte được sử dụng bởi toàn bộ mảng (d0 * d1 * sizeof (int)). sizeof array[0] * m * nsẽ không cung cấp cho bạn kích thước chính xác của mảng.
John Bode vào

4
@John Bode: Đúng, nhưng nó phụ thuộc vào cách lấy mảng. Nếu bạn có một hàm nhận tham số int array[][10], thì sizeof(array) == sizeof(int*)vì kích thước của thứ nguyên đầu tiên không được biết. OP không chỉ định cách lấy mảng.
James McNellis

77

Nếu arraythực sự là một mảng, thì bạn có thể "xóa nó" bằng:

memset(array, 0, sizeof array);

Nhưng có hai điểm bạn nên biết:

  • điều này chỉ hoạt động nếu arraythực sự là một "mảng hai-d", tức là, đã được khai báo T array[M][N];cho một số kiểu T.
  • nó chỉ hoạt động trong phạm vi arrayđã được khai báo. Nếu bạn chuyển nó cho một hàm, thì tên array sẽ phân rã thành một con trỏsizeofsẽ không cung cấp cho bạn kích thước của mảng.

Hãy làm một thử nghiệm:

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

Trên máy của tôi, các bản in trên:

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

Mặc dù arrlà một mảng, nhưng nó phân rã thành một con trỏ đến phần tử đầu tiên của nó khi được chuyển đến f(), và do đó kích thước được in ra f()là "sai". Ngoài ra, trong f()kích thước của arr[0]là kích thước của mảng arr[0], là "mảng [5] của int". Nó không phải là kích thước của một int *, bởi vì "phân rã" chỉ xảy ra ở cấp độ đầu tiên, và đó là lý do tại sao chúng ta cần khai báo f()là lấy một con trỏ đến một mảng có kích thước chính xác.

Vì vậy, như tôi đã nói, những gì bạn đang làm ban đầu sẽ chỉ hoạt động nếu hai điều kiện trên được thỏa mãn. Nếu không, bạn sẽ cần phải làm những gì người khác đã nói:

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

Cuối cùng, memset()forvòng lặp bạn đã đăng không tương đương theo nghĩa chặt chẽ. Có thể có (và đã có) các trình biên dịch trong đó "tất cả các bit không" không bằng 0 đối với một số loại nhất định, chẳng hạn như con trỏ và giá trị dấu phẩy động. Tôi nghi ngờ rằng bạn cần phải lo lắng về điều đó.


memset(array, 0, n*n*sizeof array[0][0]);Tôi đoán ý bạn là m*nkhông n*nđúng?
Tagc

Thật là thú vị đủ, điều này không có vẻ làm việc với các giá trị như 1 và 2, thay vì 0.
Ashish Ahuja

memsethoạt động ở mức byte (char). Vì 1hoặc 2không có các byte giống nhau trong biểu diễn bên dưới, bạn không thể làm điều đó với memset.
Alok Singhal

@AlokSinghal Có thể chỉ ra rằng " inttrên hệ thống của bạn là 4 byte" ở đâu đó trước ví dụ làm việc tối thiểu, để người đọc có thể dễ dàng tính tổng.
71GA

9

Chà, cách nhanh nhất để làm điều đó là không làm gì cả.

Nghe có vẻ kỳ lạ mà tôi biết, đây là một số mã giả:

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

Trên thực tế, nó vẫn đang xóa mảng, nhưng chỉ khi một cái gì đó đang được ghi vào mảng. Đây không phải là một lợi thế lớn ở đây. Tuy nhiên, nếu mảng 2D được triển khai bằng cách sử dụng cây tứ phân (không phải một tâm động) hoặc một tập hợp các hàng dữ liệu, thì bạn có thể bản địa hóa hiệu ứng của cờ boolean, nhưng bạn sẽ cần nhiều cờ hơn. Trong cây quad chỉ đặt cờ trống cho nút gốc, trong mảng các hàng chỉ cần đặt cờ cho mỗi hàng.

Điều này dẫn đến câu hỏi "tại sao bạn muốn lặp lại số 0 một mảng 2d lớn"? Mảng được sử dụng để làm gì? Có cách nào để thay đổi mã để mảng không cần số 0 không?

Ví dụ, nếu bạn có:

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

nghĩa là, sử dụng nó cho một bộ đệm tích lũy, sau đó thay đổi nó như thế này sẽ cải thiện hiệu suất không ngừng:

 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 

Điều này không yêu cầu mảng phải được xóa nhưng vẫn hoạt động. Và điều đó sẽ nhanh hơn nhiều so với việc xóa mảng. Như tôi đã nói, cách nhanh nhất là đừng làm điều đó ngay từ đầu.


Cách thay thế thú vị để xem xét vấn đề.
Beska

1
Tôi không chắc việc thêm một so sánh / nhánh bổ sung cho mỗi lần đọc có đáng để trì hoãn việc khởi tạo mảng trong trường hợp này (mặc dù có thể là của bạn). Nếu mảng thực sự lớn đến mức thời gian khởi tạo gây lo ngại nghiêm trọng thì anh ta có thể sử dụng hàm băm để thay thế.
tixxit

8

Nếu bạn thực sự, thực sự bị ám ảnh bởi tốc độ (và không quá nhiều về tính di động), tôi nghĩ rằng cách nhanh nhất tuyệt đối để làm điều này là sử dụng bản chất vector SIMD. ví dụ: trên CPU Intel, bạn có thể sử dụng các hướng dẫn SSE2 sau:

__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.

Mỗi lệnh lưu trữ sẽ đặt bốn số nguyên 32 bit thành 0 trong một lần truy cập.

p phải được căn chỉnh 16 byte, nhưng hạn chế này cũng tốt cho tốc độ vì nó sẽ giúp ích cho bộ đệm. Hạn chế khác là p phải trỏ đến kích thước phân bổ là bội số của 16 byte, nhưng điều này cũng rất tuyệt vì nó cho phép chúng ta giải nén vòng lặp một cách dễ dàng.

Có điều này trong một vòng lặp và bỏ cuộn vòng lặp một vài lần, và bạn sẽ có một trình khởi tạo cực nhanh:

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

Ngoài ra còn có một biến thể của _mm_storeunó bỏ qua bộ nhớ cache (tức là việc làm bằng không mảng sẽ không gây ô nhiễm bộ nhớ cache) có thể mang lại cho bạn một số lợi ích về hiệu suất thứ cấp trong một số trường hợp.

Xem tại đây để tham khảo SSE2: http://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx


5

Nếu bạn khởi tạo mảng bằng malloc, hãy sử dụng callocthay thế; nó sẽ không mảng của bạn miễn phí. (Rõ ràng giống như memset, chỉ cần ít mã hơn cho bạn.)


Điều này có nhanh hơn memset nếu tôi muốn liên tục làm 0 mảng của mình không?
Eddy

calloc sẽ bằng không nó một lần, tại thời điểm khởi tạo và có thể sẽ không nhanh hơn việc gọi malloc theo sau bởi memset. Sau đó, bạn sẽ tự túc và chỉ có thể sử dụng memset nếu bạn muốn xóa nó một lần nữa. Trừ khi mảng của bạn thực sự lớn, còn không thì hiệu suất thực sự không phải là điều cần cân nhắc ở đây trên bất kỳ máy hợp lý nào.
Ben Zotto


2

Mảng 2D của bạn được khai báo như thế nào?

Nếu nó giống như:

int arr[20][30];

Bạn có thể làm không bằng cách:

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

Tôi đã sử dụng một mảng char [10] [10]. Nhưng tôi gặp lỗi: quá ít đối số để hàm 'memset' và memset(a, 0, sizeof(char)*10*10);hoạt động tốt đối với tôi. , Nó xảy ra như thế nào?
noufal

1

Sử dụng calloc thay vì malloc. calloc sẽ bắt đầu tất cả các trường thành 0.

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

// tất cả các ô của a đã được khởi tạo thành 0


0

Tôi nghĩ rằng cách nhanh nhất để làm điều đó bằng tay là làm theo mã. Bạn có thể so sánh tốc độ của nó với chức năng memset, nhưng không nên chậm hơn.

(thay đổi kiểu con trỏ ptr và ptr1 nếu kiểu mảng của bạn khác kiểu 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;
}


Mã của bạn có thể sẽ chậm hơn so memsetvới các loại char.
tofro,

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

1
array [n] [n] là kích thước của 1 phần tử của mảng, do đó chỉ phần tử đầu tiên của mảng sẽ được khởi tạo.
EvilTeach

Giáo sư. Bạn đúng. Ý tôi là đặt một chữ ký kiểu trong parens, không phải tra cứu mảng. Đã sửa nó.
swestrup


-2

Điều này xảy ra vì sizeof (mảng) cung cấp cho bạn kích thước phân bổ của đối tượng được trỏ đến bởi mảng . ( mảng chỉ là một con trỏ đến hàng đầu tiên của mảng đa chiều của bạn). Tuy nhiên, bạn đã phân bổ mảng j có kích thước i . Do đó, bạn cần nhân kích thước của một hàng, được trả về bởi sizeof (mảng) với số hàng bạn đã phân bổ, ví dụ:

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

Cũng lưu ý rằng sizeof (mảng) sẽ chỉ hoạt động đối với các mảng được cấp phát tĩnh. Đối với một mảng được cấp phát động, bạn sẽ viết

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

Phần đầu là sai. Đối với sizeoftoán tử, arraykhông phải là một con trỏ (nếu nó được khai báo là một mảng). Xem câu trả lời của tôi cho một ví dụ.
Alok Singhal
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.