Cara tercepat untuk menghilangkan array 2d di C?

92

Saya ingin berulang kali membidik array 2d besar di C. Inilah yang saya lakukan saat ini:

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

Saya sudah mencoba menggunakan memset:

memset(array, 0, sizeof(array))

Tapi ini hanya berfungsi untuk array 1D. Ketika saya mencetak isi dari array 2D, baris pertama adalah nol, tapi kemudian saya mendapat beban angka besar acak dan macet.

Eddy
sumber

Jawaban:

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

Di mana mdan nadalah lebar dan tinggi larik dua dimensi (dalam contoh Anda, Anda memiliki larik dua dimensi persegi, jadi m == n).

James McNellis
sumber
1
Sepertinya tidak berhasil. Saya mendapatkan 'proses dikembalikan -1073741819' pada blok kode, yang merupakan kesalahan seg, bukan?
Eddy
8
@ Eddy: Tunjukkan deklarasi array.
GManNickG
1
Saya yakin itu crash di baris lain, bukan memset, karena Anda menyebutkan crash dari nol hanya satu baris juga.
Blindy
3
Hah. Baru saja mencoba menguji sebuah array yang dideklarasikan sebagai int d0=10, d1=20; int arr[d0][d1], dan memset(arr, 0, sizeof arr);bekerja seperti yang diharapkan (gcc 3.4.6, dikompilasi dengan -std=c99 -Wallflag). Saya menyadari bahwa "itu bekerja pada mesin saya" berarti jongkok yang tidak tepat, tetapi memset(arr, 0, sizeof arr); seharusnya berhasil. sizeof arr harus mengembalikan jumlah byte yang digunakan oleh seluruh array (d0 * d1 * sizeof (int)). sizeof array[0] * m * ntidak akan memberi Anda ukuran larik yang benar.
John Bode
4
@ John Bode: Benar, tetapi itu tergantung bagaimana array diperoleh. Jika Anda memiliki fungsi yang mengambil parameter int array[][10], maka sizeof(array) == sizeof(int*)karena ukuran dimensi pertama tidak diketahui. OP tidak menentukan bagaimana array itu diperoleh.
James McNellis
78

Jika arraybenar-benar sebuah larik, maka Anda dapat "memboloskannya" dengan:

memset(array, 0, sizeof array);

Tetapi ada dua hal yang harus Anda ketahui:

  • ini hanya berfungsi jika arraybenar-benar merupakan "larik dua-d", yaitu, dideklarasikan T array[M][N];untuk beberapa tipe T.
  • ini hanya berfungsi dalam lingkup arrayyang dideklarasikan. Jika Anda meneruskannya ke suatu fungsi, maka nama tersebut array meluruh menjadi penunjuk , dan sizeoftidak akan memberi Anda ukuran larik.

Mari lakukan percobaan:

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

Di mesin saya, cetakan di atas:

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

Meskipun arrmerupakan sebuah array, ia meluruh ke pointer ke elemen pertamanya saat diteruskan f(), dan oleh karena itu ukuran yang dicetak f()adalah "salah". Juga, dalam f()size of arr[0]adalah ukuran dari array arr[0], yang merupakan "array [5] dari int". Ini bukan ukuran an int *, karena "peluruhan" hanya terjadi pada tingkat pertama, dan itulah mengapa kita perlu mendeklarasikan f()sebagai mengambil pointer ke array dengan ukuran yang benar.

Jadi, seperti yang saya katakan, apa yang Anda lakukan awalnya hanya akan berhasil jika kedua kondisi di atas terpenuhi. Jika tidak, Anda perlu melakukan apa yang orang lain katakan:

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

Akhirnya, memset()dan forloop yang Anda posting tidak setara dalam arti sebenarnya. Mungkin ada (dan telah ada) kompiler di mana "semua bit nol" tidak sama dengan nol untuk jenis tertentu, seperti pointer dan nilai floating-point. Saya ragu Anda perlu mengkhawatirkan hal itu.

Alok Singhal
sumber
memset(array, 0, n*n*sizeof array[0][0]);Saya kira maksud Anda m*ntidak n*nbenar?
Tagc
Anehnya, ini tampaknya tidak bekerja dengan nilai seperti 1 dan 2, bukan 0.
Ashish Ahuja
memsetbekerja pada tingkat byte (char). Karena 1atau 2tidak memiliki byte yang sama dalam representasi yang mendasarinya, Anda tidak dapat melakukannya dengan memset.
Alok Singhal
@AlokSinghal Mungkin menunjukkan bahwa " intdi sistem Anda adalah 4 byte" di suatu tempat sebelum contoh kerja minimal, sehingga pembaca dapat dengan mudah menghitung jumlahnya.
71GA
9

Nah, cara tercepat untuk melakukannya adalah dengan tidak melakukannya sama sekali.

Kedengarannya aneh, saya tahu, inilah beberapa pseudocode:

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

Sebenarnya, ini masih membersihkan larik, tetapi hanya jika ada sesuatu yang sedang ditulis ke larik. Ini bukan keuntungan besar di sini. Namun, jika larik 2D diimplementasikan menggunakan, katakanlah, pohon quad (bukan satu pikiran yang dinamis), atau kumpulan baris data, maka Anda dapat melokalkan efek bendera boolean, tetapi Anda memerlukan lebih banyak bendera. Di pohon quad hanya mengatur bendera kosong untuk simpul akar, dalam array baris hanya mengatur bendera untuk setiap baris.

Yang mengarah ke pertanyaan "mengapa Anda ingin berulang kali membidik array 2d besar"? Untuk apa larik itu digunakan? Apakah ada cara untuk mengubah kode sehingga array tidak perlu dibidik?

Misalnya, jika Anda memiliki:

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

artinya, gunakan untuk buffer akumulasi, kemudian mengubahnya seperti ini akan meningkatkan kinerja tanpa akhir:

 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 

Ini tidak memerlukan array untuk dihapus tetapi masih berfungsi. Dan itu akan jauh lebih cepat daripada membersihkan array. Seperti saya katakan, cara tercepat adalah dengan tidak melakukannya sejak awal.

Skizz
sumber
Cara alternatif yang menarik untuk melihat masalah ini.
Beska
1
Saya tidak yakin menambahkan perbandingan / cabang tambahan untuk setiap pembacaan adalah layak untuk menunda inisialisasi array dalam kasus ini (meskipun mungkin milik Anda). Jika array benar-benar sangat besar sehingga waktu inisialisasi menimbulkan masalah serius, maka dia dapat menggunakan hash.
tixxit
8

Jika Anda benar-benar terobsesi dengan kecepatan (dan tidak begitu banyak dengan portabilitas) saya pikir cara tercepat mutlak untuk melakukan ini adalah dengan menggunakan intrinsik vektor SIMD. misalnya pada CPU Intel, Anda dapat menggunakan instruksi SSE2 ini:

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

Setiap instruksi penyimpanan akan menetapkan empat int 32-bit menjadi nol dalam satu klik.

p harus sejajar 16-byte, tetapi pembatasan ini juga bagus untuk kecepatan karena akan membantu cache. Batasan lainnya adalah bahwa p harus menunjuk ke ukuran alokasi yang merupakan kelipatan 16-byte, tetapi ini juga keren karena memungkinkan kita untuk membuka gulungan dengan mudah.

Lakukan ini dalam satu lingkaran, dan buka gulungannya beberapa kali, dan Anda akan memiliki inisialisasi cepat yang gila:

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

Ada juga varian _mm_storeuyang melewati cache (yaitu memusatkan perhatian pada array tidak akan mencemari cache) yang dapat memberi Anda beberapa keuntungan kinerja sekunder dalam beberapa keadaan.

Lihat di sini untuk referensi SSE2: http://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx

Jarrod Smith
sumber
5

Jika Anda menginisialisasi array dengan malloc, gunakan calloc; itu akan nol array Anda secara gratis. (Performa yang sama jelas seperti memset, hanya lebih sedikit kode untuk Anda.)

Ben Zotto
sumber
Apakah ini lebih cepat daripada memset jika saya ingin berulang kali mengosongkan array saya?
Eddy
calloc akan membolos sekali, pada waktu inisialisasi, dan kemungkinan besar tidak akan lebih cepat daripada memanggil malloc diikuti oleh memset. Anda sendirian setelah itu, dan dapat menggunakan memset jika Anda ingin menghapusnya lagi. Kecuali jika susunan Anda sangat besar, kinerja tidak benar-benar menjadi pertimbangan di sini pada mesin yang masuk akal.
Ben Zotto
3

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

... setidaknya di GCC 4.8.

Insinyur
sumber
2

Bagaimana array 2D Anda dideklarasikan?

Jika itu seperti:

int arr[20][30];

Anda dapat membidiknya dengan melakukan:

memset(arr, sizeof(int)*20*30);
Pablo Santa Cruz
sumber
Saya telah menggunakan array char [10] [10]. Tapi saya mendapat kesalahan: terlalu sedikit argumen untuk berfungsi 'memset', dan memset(a, 0, sizeof(char)*10*10);berfungsi dengan baik untuk saya. , bagaimana itu bisa terjadi?
noufal
1

Gunakan calloc, bukan malloc. calloc akan memulai semua bidang ke 0.

int * a = (int *) calloc (n, ukuran (int));

// semua sel a telah diinisialisasi ke 0

DUDE_MXP
sumber
0

Saya pikir cara tercepat untuk melakukannya dengan tangan adalah dengan mengikuti kode. Anda dapat membandingkan kecepatannya dengan fungsi memset, tetapi seharusnya tidak lebih lambat.

(ubah tipe pointer ptr dan ptr1 jika tipe array Anda berbeda dengan 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;
}

mack369
sumber
Kode Anda kemungkinan besar akan lebih lambat daripada memsetjenis karakter.
tofro
0
memset(array, 0, sizeof(int [n][n]));
swestrup
sumber
1
array [n] [n] adalah ukuran dari 1 elemen array, maka hanya elemen pertama dari array yang akan diinisialisasi.
EvilTeach
Ups. Kamu benar. Saya bermaksud untuk meletakkan tanda tangan tipe dalam tanda kurung, bukan pencarian array. Diperbaiki.
swestrup
0

Anda bisa mencobanya

int array[20,30] = {{0}};
Călin Calin
sumber
Tambahkan lebih banyak detail
Marko Popovic
-2

Ini terjadi karena sizeof (array) memberi Anda ukuran alokasi objek yang ditunjuk oleh array . ( array hanyalah penunjuk ke baris pertama dari array multidimensi Anda). Namun, Anda mengalokasikan j array dengan ukuran i . Akibatnya, Anda perlu mengalikan ukuran satu baris, yang dikembalikan oleh sizeof (larik) dengan jumlah baris yang Anda alokasikan, misalnya:

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

Perhatikan juga bahwa sizeof (array) hanya akan berfungsi untuk array yang dialokasikan secara statis. Untuk larik yang dialokasikan secara dinamis, Anda akan menulis

size_t arrayByteSize = sizeof(int) * i * j; 
int *array = malloc(array2dByteSite);
bzero(array, arrayByteSize);
VoidPointer
sumber
Bagian pertama salah. Untuk sizeofoperator, arraybukan pointer (jika dideklarasikan sebagai array). Lihat jawaban saya sebagai contoh.
Alok Singhal