C 2d dizisini sıfırlamanın en hızlı yolu?


93

C'de büyük bir 2d dizisini tekrar tekrar sıfırlamak istiyorum. Şu anda yaptığım şey bu:

// 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 kullanmayı denedim:

memset(array, 0, sizeof(array))

Ancak bu yalnızca 1B diziler için geçerlidir. 2B dizinin içeriğini yazdırdığımda, ilk satır sıfırdır, ancak sonra rastgele büyük sayılar aldım ve çöküyor.

Yanıtlar:


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

İki boyutlu dizinin genişliği ve yüksekliği nerede mve nerede n(sizin örneğinizde, iki boyutlu kare bir diziniz var, yani m == n).


1
İşe yaramıyor gibi görünüyor. Kod bloklarında 'işlem döndürdü -1073741819' alıyorum, bu bir segment hatası değil mi?
Eddy

8
@Eddy: Bize dizinin bildirimini göster.
GManNickG

1
Bahse girerim başka satırlarda da çöküyor memset, çünkü sadece bir satırı sıfırlamaktan da bahsetmiştin.
Blindy

3
Huh. Olarak bildirilen bir diziyi test etmeyi denedim int d0=10, d1=20; int arr[d0][d1]ve memset(arr, 0, sizeof arr);beklendiği gibi çalıştı (gcc 3.4.6, -std=c99 -Wallbayraklarla derlendi ). Ben diddly bodur yollara "benim makine üzerinde çalışır" ama farkında memset(arr, 0, sizeof arr); olmalıdır çalıştık. sizeof arr gereken tüm dizi (d0 * d1 * sizeof (int)) tarafından kullanılan bayt sayısını döndürür. sizeof array[0] * m * nsize dizinin doğru boyutunu vermeyecektir.
John Bode

4
@John Bode: Doğru, ancak dizinin nasıl elde edildiğine bağlı. Bir parametre alan bir fonksiyonunuz varsa int array[][10], o zaman sizeof(array) == sizeof(int*)birinci boyutun boyutu bilinmemektedir. OP, dizinin nasıl elde edildiğini belirtmedi.
James McNellis

80

Eğer arraygerçekten bir diziyse, şu şekilde "sıfırlayabilirsiniz":

memset(array, 0, sizeof array);

Ancak bilmeniz gereken iki nokta var:

  • bu yalnızca arraygerçekten bir "iki-d dizisi" ise, yani T array[M][N];bir tür için bildirilmişse çalışır T.
  • sadece arraybeyan edildiği kapsamda çalışır . Bunu bir işleve aktarırsanız, ad array bir işaretçi olarak bozulur ve sizeofsize dizinin boyutunu vermez.

Bir deney yapalı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;
}

Makinemde yukarıdaki yazı:

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

arrBir dizi olmasına rağmen , aktarıldığında ilk elemanına bir göstericiye dönüşür f()ve bu nedenle yazdırılan boyutlar f()"yanlıştır". Ayrıca, f()boyutunda "dizi [5]" olan arr[0]dizinin boyutu bulunur . Bu, an'ın boyutu değildir , çünkü "bozunma" yalnızca ilk seviyede gerçekleşir ve bu yüzden doğru büyüklükteki bir diziye bir gösterici götürdüğünü bildirmemiz gerekir .arr[0]intint *f()

Dediğim gibi, başlangıçta yaptığınız şey ancak yukarıdaki iki koşul yerine getirilirse işe yarayacaktır. Değilse, başkalarının söylediklerini yapmanız gerekecek:

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

Son olarak, memset()ve foryayınladığınız döngü tam anlamıyla eşdeğer değildir. İşaretçiler ve kayan nokta değerleri gibi belirli türler için "tüm bitlerin sıfır" ın sıfıra eşit olmadığı derleyiciler olabilir (ve olmuştur). Bunun için endişelenmen gerektiğinden şüpheliyim.


memset(array, 0, n*n*sizeof array[0][0]);Ne demek tahmin m*ndeğil n*ndeğil mi?
Tagc

Garip bir şekilde, bu 0 yerine 1 ve 2 gibi değerlerle işe yaramıyor gibi görünüyor.
Ashish Ahuja

memsetbayt (karakter) düzeyinde çalışır. Temel temsilde aynı baytlara sahip olduğundan 1veya 2olmadığından, bunu ile yapamazsınız memset.
Alok Singhal

@AlokSinghal Okuyucunun toplamları kolayca hesaplayabilmesi için belki de " intsisteminizde 4 bayttır" minimum çalışma örneğinden önce bir yerde işaret edebilir.
71GA

9

Bunu yapmanın en hızlı yolu, hiç yapmamaktır.

Kulağa garip geliyor biliyorum, işte bazı sözde kod:

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

Aslında, diziyi temizlemeye devam ediyor, ancak yalnızca diziye bir şey yazılırken. Bu, burada büyük bir avantaj değil. Bununla birlikte, 2B dizi, örneğin bir dörtlü ağaç (dinamik tek zihin değil) veya veri satırları koleksiyonu kullanılarak uygulanmışsa, boole bayrağının etkisini yerelleştirebilirsiniz, ancak daha fazla bayrağa ihtiyacınız olacaktır. Dörtlü ağaçta sadece kök düğüm için boş bayrağı ayarlayın, satır dizisinde her satır için bayrak ayarlayın.

Bu da "neden büyük bir 2d dizisini tekrar tekrar sıfırlamak istiyorsunuz" sorusuna yol açar? Dizi ne için kullanılıyor? Dizinin sıfırlamaya ihtiyaç duymaması için kodu değiştirmenin bir yolu var mı?

Örneğin, şunlara sahipseniz:

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

yani, onu bir biriktirme tamponu için kullanın, sonra bunu bu şekilde değiştirmek performansı sonsuza kadar iyileştirecektir:

 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 

Bu, dizinin temizlenmesini gerektirmez, ancak yine de çalışır. Ve bu, diziyi temizlemekten çok daha hızlı olacaktır. Dediğim gibi, en hızlı yol ilk etapta yapmamaktır.


Konuya bakmanın ilginç alternatif yolu.
Beska

1
Her bir okuma için fazladan bir karşılaştırma / dal eklemenin bu durumda dizinin başlatılmasını ertelemeye değeceğinden emin değilim (yine de sizin olabilir). Dizi gerçekten o kadar büyükse, başlangıç ​​zamanı ciddi bir endişe yaratıyorsa, onun yerine bir hash kullanabilir.
tixxit

8

Hıza gerçekten, gerçekten takıntılıysanız (ve taşınabilirlik konusunda çok fazla değil), bence bunu yapmanın en hızlı yolu SIMD vektör içsellerini kullanmak olacaktır. örneğin Intel CPU'larda şu SSE2 talimatlarını kullanabilirsiniz:

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

Her saklama talimatı, bir vuruşta dört 32-bit girişi sıfıra ayarlayacaktır.

p 16 bayt hizalı olmalıdır, ancak bu sınırlama hız için de iyidir çünkü önbelleğe yardımcı olacaktır. Diğer kısıtlama, p'nin 16 bayt katı olan bir tahsis boyutunu göstermesi gerektiğidir, ancak bu da iyidir çünkü döngüyü kolayca açmamıza izin verir.

Bunu bir döngüde yapın ve döngüyü birkaç kez açın ve çılgın hızlı bir başlatıcıya sahip olacaksınız:

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

Ayrıca _mm_storeuönbelleği atlayan (yani diziyi sıfırlamak önbelleği kirletmeyecek) bazı durumlarda size bazı ikincil performans avantajları sağlayabilecek bir varyant da vardır .

SSE2 referansı için buraya bakın: http://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx


5

Eğer diziyi başlatmak durumunda malloc, kullanmak callocyerine; dizinizi ücretsiz olarak sıfırlayacaktır. (Memset ile aynı performans, sizin için daha az kod.)


Dizimi tekrar tekrar sıfırlamak istersem, bu memset'ten daha hızlı mı?
Eddy

calloc onu bir kez, başlatma zamanında sıfırlar ve büyük olasılıkla malloc ve ardından memset'i çağırmaktan daha hızlı olmayacaktır. Bundan sonra tek başınasın ve tekrar sıfırlamak istiyorsan memset'i kullanabilirsin. Diziniz gerçekten çok büyük olmadıkça, burada herhangi bir makul makinede performans gerçekten dikkate alınmaz.
Ben Zotto


2

2D diziniz nasıl ilan edildi?

Şöyle bir şeyse:

int arr[20][30];

Şunları yaparak sıfırlayabilirsiniz:

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

Bir char [10] [10] dizisi kullandım. Ama hata aldım: 'memset' işlevi için çok az argüman ve memset(a, 0, sizeof(char)*10*10);benim için iyi çalışıyor. , Nasıl olur?
noufal

1

Malloc yerine calloc kullanın. calloc, tüm alanları 0'a başlatır.

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

// a'nın tüm hücreleri 0 olarak başlatıldı


0

Bunu elle yapmanın en hızlı yolunun kodu takip etmek olduğunu düşünüyorum. Hızını memset işleviyle karşılaştırabilirsiniz, ancak daha yavaş olmamalıdır.

(dizi türünüz int'ten farklıysa ptr ve ptr1 işaretçilerinin türünü değiştirin)


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


Kodunuz büyük olasılıkla memsetkarakter türlerinden daha yavaş olacaktır .
tofro

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

1
dizi [n] [n], dizinin 1 öğesinin boyutudur, bu nedenle dizinin yalnızca ilk öğesi başlatılır.
EvilTeach

Oops. Haklısın. Parenlere bir dizi araması değil, bir tür imzası koymak istedim. Onu düzeltti.
swestrup


-2

Bunun nedeni sizeof (dizi) 'nin size işaret ettiği nesnenin ayırma boyutunu vermesidir. diziden . ( dizi , çok boyutlu dizinizin ilk satırına yalnızca bir göstericidir). Ancak, i boyutunda j dizileri ayırdınız . Sonuç olarak, sizeof (dizi) ile döndürülen bir satırın boyutunu, ayırdığınız satır sayısıyla çarpmanız gerekir, örneğin:

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

Ayrıca sizeof (dizi) 'nin yalnızca statik olarak ayrılmış diziler için çalışacağını unutmayın. Dinamik olarak ayrılmış bir dizi için yazarsınız

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

İlk kısım yanlış. İçin sizeofoperatör, arraybir gösterici değildir (bir dizi ilan ise). Bir örnek için cevabıma bakın.
Alok Singhal
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.