Il modo più veloce per azzerare un array 2d in C?


92

Voglio azzerare ripetutamente un grande array 2d in C.Questo è quello che faccio al momento:

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

Ho provato a usare memset:

memset(array, 0, sizeof(array))

Ma questo funziona solo per array 1D. Quando stampo il contenuto dell'array 2D, la prima riga è zero, ma poi ho ricevuto un carico di grandi numeri casuali e si blocca.

Risposte:


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

Dove me nsono la larghezza e l'altezza della matrice bidimensionale (nel tuo esempio, hai una matrice bidimensionale quadrata, quindi m == n).


1
Non sembra funzionare. Ottengo 'processo restituito -1073741819' sui blocchi di codice, che è un errore di seg, giusto?
Eddy

8
@Eddy: mostraci la dichiarazione dell'array.
GManNickG

1
Scommetto che si blocca su altre righe, non su memset, perché hai menzionato l'arresto per azzerare anche solo una riga.
Blindy

3
Eh. Ho appena provato a testare un array dichiarato come int d0=10, d1=20; int arr[d0][d1]e ha memset(arr, 0, sizeof arr);funzionato come previsto (gcc 3.4.6, compilato con -std=c99 -Wallflag). Mi rendo conto che "funziona sulla mia macchina" significa squat, ma memset(arr, 0, sizeof arr); avrebbe dovuto funzionare. sizeof arr dovrebbe restituire il numero di byte usati dall'intero array (d0 * d1 * sizeof (int)). sizeof array[0] * m * nnon ti darà la dimensione corretta dell'array.
John Bode

4
@ John Bode: Vero, ma dipende da come si ottiene l'array. Se hai una funzione che accetta un parametro int array[][10], sizeof(array) == sizeof(int*)poiché la dimensione della prima dimensione non è nota. L'OP non ha specificato come è stato ottenuto l'array.
James McNellis

77

Se arrayè veramente un array, puoi "azzerarlo" con:

memset(array, 0, sizeof array);

Ma ci sono due punti che dovresti sapere:

  • funziona solo se arrayè realmente un "array a due d", cioè è stato dichiarato T array[M][N];per qualche tipo T.
  • funziona solo nell'ambito in cui è arraystato dichiarato. Se lo passi a una funzione, il nome array decade in un puntatore e sizeofnon ti darà la dimensione dell'array.

Facciamo un esperimento:

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

Sulla mia macchina, quanto sopra stampa:

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

Anche se arrè un array, decade in un puntatore al suo primo elemento quando viene passato a f(), e quindi le dimensioni stampate f()sono "sbagliate". Inoltre, nella f()dimensione di arr[0]è la dimensione dell'array arr[0], che è un "array [5] di int". Non ha le dimensioni di un int *, perché il "decadimento" avviene solo al primo livello, ed è per questo che dobbiamo dichiararef() che prende un puntatore a un array della dimensione corretta.

Quindi, come ho detto, ciò che stavi facendo originariamente funzionerà solo se le due condizioni sopra sono soddisfatte. In caso contrario, dovrai fare ciò che gli altri hanno detto:

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

Infine, memset()e il forciclo che hai postato non sono equivalenti in senso stretto. Potrebbero esserci (e sono stati) compilatori in cui "tutti i bit zero" non è uguale a zero per alcuni tipi, come puntatori e valori a virgola mobile. Dubito che tu debba preoccuparti di questo però.


memset(array, 0, n*n*sizeof array[0][0]);Immagino tu intenda m*nnon n*ngiusto?
Tagc

Stranamente, questo non sembra funzionare con valori come 1 e 2, invece di 0.
Ashish Ahuja

memsetfunziona a livello di byte (char). Poiché 1o 2non hanno gli stessi byte nella rappresentazione sottostante, non puoi farlo con memset.
Alok Singhal

@AlokSinghal Forse fai notare che " intsul tuo sistema sono 4 byte" da qualche parte prima dell'esempio minimo di lavoro, in modo che il lettore possa facilmente calcolare le somme.
71GA

9

Ebbene, il modo più veloce per farlo è non farlo affatto.

Sembra strano, lo so, ecco uno pseudocodice:

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

In realtà, sta ancora cancellando l'array, ma solo quando viene scritto qualcosa sull'array. Questo non è un grande vantaggio qui. Tuttavia, se l'array 2D è stato implementato utilizzando, ad esempio, un albero quadruplo (non dinamico) o una raccolta di righe di dati, è possibile localizzare l'effetto del flag booleano, ma avresti bisogno di più flag. Nell'albero quad basta impostare il flag vuoto per il nodo radice, nella matrice di righe basta impostare il flag per ogni riga.

Il che porta alla domanda "perché vuoi azzerare ripetutamente un grande array 2d"? A cosa serve l'array? C'è un modo per modificare il codice in modo che l'array non necessiti di azzeramento?

Ad esempio, se avessi:

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

cioè, usalo per un buffer di accumulo, quindi cambiarlo in questo modo migliorerebbe le prestazioni senza fine:

 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 

Ciò non richiede la cancellazione dell'array ma funziona ancora. E sarà molto più veloce che cancellare l'array. Come ho detto, il modo più veloce è non farlo in primo luogo.


Interessante modo alternativo per esaminare il problema.
Beska

1
Non sono sicuro che l'aggiunta di un confronto / ramo aggiuntivo per ogni singola lettura valga la pena di rimandare l'inizializzazione dell'array in questo caso (anche se potrebbe essere tuo). Se l'array è davvero così grande che il tempo di inizializzazione pone una seria preoccupazione, allora potrebbe usare un hash.
tixxit

8

Se sei davvero, davvero ossessionato dalla velocità (e non tanto dalla portabilità), penso che il modo più veloce in assoluto per farlo sarebbe usare gli intrinseci vettoriali SIMD. ad esempio su CPU Intel, è possibile utilizzare queste istruzioni 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.

Ogni istruzione store imposterà quattro int a 32 bit a zero in un colpo solo.

p deve essere allineato a 16 byte, ma questa restrizione è utile anche per la velocità perché aiuterà la cache. L'altra restrizione è che p deve puntare a una dimensione di allocazione che è un multiplo di 16 byte, ma anche questo è interessante perché ci consente di svolgere facilmente il ciclo.

Mettilo in un ciclo e srotola il ciclo un paio di volte, e avrai un inizializzatore pazzo veloce:

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

C'è anche una variante di _mm_storeu che bypassa la cache (cioè l'azzeramento dell'array non inquinerà la cache) che potrebbe darti alcuni vantaggi secondari in termini di prestazioni in alcune circostanze.

Vedere qui per riferimento SSE2: http://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx


5

Se inizializzi l'array con malloc, usa callocinvece; azzererà il tuo array gratuitamente. (Stesse prestazioni ovviamente del memset, solo meno codice per te.)


È più veloce del memset se voglio azzerare ripetutamente il mio array?
Eddy

calloc lo azzererà una volta, al momento dell'inizializzazione, e probabilmente non sarà più veloce di chiamare malloc seguito da memset. Dopodiché, sei da solo e puoi semplicemente usare memset se vuoi azzerarlo di nuovo. A meno che il tuo array non sia davvero enorme, perf non è davvero una considerazione qui su nessuna macchina ragionevole.
Ben Zotto


2

Come è stato dichiarato il tuo array 2D?

Se qualcosa del tipo:

int arr[20][30];

Puoi azzerarlo facendo:

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

Ho usato un array char [10] [10]. Ma ho ricevuto un errore: troppo pochi argomenti per la funzione "memset" e memset(a, 0, sizeof(char)*10*10);funziona bene per me. , come succede?
noufal

1

Usa calloc invece di malloc. calloc avvierà tutti i campi a 0.

int * a = (int *) calloc (n, dimensione di (int));

// tutte le celle di a sono state inizializzate a 0


0

Penso che il modo più veloce per farlo a mano sia seguire il codice. Puoi confrontare la sua velocità con la funzione memset, ma non dovrebbe essere più lenta.

(cambia il tipo di puntatori ptr e ptr1 se il tipo di array è diverso da 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;
}


Il tuo codice sarà molto probabilmente più lento rispetto memsetai tipi char.
tofro

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

1
array [n] [n] è la dimensione di 1 elemento dell'array, quindi verrà inizializzato solo il primo elemento dell'array.
EvilTeach

Ops. Hai ragione. Intendevo inserire una firma del tipo nelle parentesi, non una ricerca nell'array. Aggiustato.
Swestrup


-2

Ciò accade perché sizeof (array) ti dà la dimensione di allocazione dell'oggetto puntato da array . ( array è solo un puntatore alla prima riga del tuo array multidimensionale). Tuttavia, hai allocato j array di dimensione i . Di conseguenza, è necessario moltiplicare la dimensione di una riga, che viene restituita da sizeof (array) con il numero di righe allocate, ad esempio:

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

Si noti inoltre che sizeof (array) funzionerà solo per array allocati staticamente. Per un array allocato dinamicamente dovresti scrivere

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

La prima parte è sbagliata. Per sizeofoperatore, arraynon è un puntatore (se è stato dichiarato un array). Vedi la mia risposta per un esempio.
Alok Singhal
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.