Найшвидший спосіб обнулити 2d-масив у C?


92

Я хочу кілька разів обнулити великий 2d-масив у C. Це те, що я роблю на даний момент:

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

Але це працює лише для одновимірних масивів. Коли я надрукую вміст 2D-масиву, перший рядок дорівнює нулям, але тоді я отримав навантаження випадкових великих чисел, і він виходить з ладу.

Відповіді:


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

Де mі n- ширина та висота двовимірного масиву (у вашому прикладі у вас є квадратний двовимірний масив, отже m == n).


1
Здається, це не працює. Я отримую `` процес повернуто -1073741819 '' на кодових блоках, що є помилкою сегмента, чи не так?
Едді

8
@Eddy: Покажіть нам декларацію масиву.
GManNickG

1
Б'юся об заклад, це збій на інших рядках, а не на memsetтому, що ви вже згадали про збій з нуля лише одного рядка.
Сліпий

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не дасть вам правильного розміру масиву.
Джон Боде

4
@ Джон Боде: Так, але це залежить від того, як буде отриманий масив. Якщо у вас є функція, яка приймає параметр int array[][10], то sizeof(array) == sizeof(int*)оскільки розмір першого виміру невідомий. В ОП не вказано, як був отриманий масив.
Джеймс МакНелліс,

78

Якщо arrayце справді масив, тоді ви можете "обнулити" за допомогою:

memset(array, 0, sizeof array);

Але є два моменти, які ви повинні знати:

  • це працює, лише якщо arrayнасправді є "двовимірний масив", тобто було оголошено 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()за розміром arr[0]є розмір масиву arr[0], який є "масивом [5] з int". Це не розмір an int *, тому що "загнивання" відбувається лише на першому рівні, і саме тому нам потрібно заявитиf() , що бере вказівник на масив правильного розміру.

Отже, як я вже казав, те, що ви робили спочатку, працюватиме лише за умови, якщо будуть виконані дві наведені вище умови. Якщо ні, то вам потрібно буде зробити те, що сказали інші:

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

Нарешті, memset()і forцикл, який ви опублікували, не є рівнозначним у строгому сенсі. Могли бути (і були) компілятори, де "всі біти нуль" не дорівнює нулю для певних типів, таких як покажчики та значення з плаваючою комою. Я сумніваюся, що вам потрібно про це переживати.


memset(array, 0, n*n*sizeof array[0][0]);Думаю, ви маєте на увазі m*nне n*nтак?
Tagc

Як не дивно, але це, здається, не працює з такими значеннями, як 1 і 2, а не 0.
Ashish Ahuja

memsetпрацює на рівні байтів (символів). Оскільки 1або 2не мають однакових байтів у базовому поданні, ви не можете цього зробити memset.
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-масив"? Для чого використовується масив? Чи є спосіб змінити код, щоб масив не потребував обнулення?

Наприклад, якщо у вас було:

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 

Це не вимагає очищення масиву, але все одно працює. І це буде набагато швидше, ніж очищення масиву. Як я вже сказав, найшвидший спосіб - це не робити в першу чергу.


Цікавий альтернативний спосіб розглянути проблему.
Беска

1
Я не впевнений, що додавання додаткового порівняння / гілки для кожного окремого прочитання варто відкласти ініціалізацію масиву в цьому випадку (хоча може бути і вашим). Якщо масив справді настільки великий, що час ініціалізації викликає серйозне занепокоєння, тоді він міг би використовувати хеш.
tixxit

8

Якщо ви дійсно, справді одержимі швидкістю (і не стільки переносимістю), я думаю, що найшвидшим способом зробити це було б використання властивостей SIMD-вектора. наприклад, на процесорах Intel, ви можете скористатися цими інструкціями 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.

Кожна інструкція магазину встановлюватиме чотири 32-бітові вставки до нуля за один удар.

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 який обходить кеш-пам’ять (тобто обнулення масиву не забруднює кеш-пам’ять), що за певних обставин може дати вам певні переваги щодо продуктивності.

Див. Тут посилання на SSE2: http://msdn.microsoft.com/en-us/library/kcwz153a(v=vs.80).aspx


5

Якщо ви ініціалізуєте масив за допомогою malloc, використовуйте callocнатомість; це обнулить ваш масив безкоштовно. (Те саме, що очевидно, як memset, просто менше коду для вас.)


Це швидше, ніж memset, якщо я хочу кілька разів обнулити свій масив?
Едді

calloc обнулить його один раз, під час ініціалізації, і, швидше за все, це не буде швидше, ніж виклик malloc з наступним memset. Після цього ви перебуваєте на своєму і можете просто використовувати memset, якщо хочете знову його обнулити. Якщо ваш масив насправді не величезний, на справді це не стосується жодної розумної машини.
Бен Зотто


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

Використовуйте мозоль замість мальлоку. calloc ініціює всі поля до 0.

int * a = (int *) calloc (n, розмір (int));

// всі комірки а були ініціалізовані до 0


0

Я думаю, що найшвидший спосіб зробити це вручну - це слідувати коду. Ви можете порівняти свою швидкість із функцією memset, але вона не повинна бути повільнішою.

(змінити тип покажчиків ptr та ptr1, якщо тип масиву відрізняється від 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;
}


Ваш код, швидше за все, буде повільнішим, ніж memsetдля типів символів.
tofro

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

1
масив [n] [n] - це розмір 1 елемента масиву, отже, ініціалізується лише перший елемент масиву.
EvilTeach

На жаль Ви маєте рацію. Я мав на увазі поставити підпис типу в паренах, а не пошук масиву. Виправлено.
swestrup


-2

Це відбувається тому, що sizeof (масив) дає вам розмір розміщення об'єкта, на який вказує масив . ( масив - це просто вказівник на перший рядок вашого багатовимірного масиву). Однак ви виділили j масивів розміром i . Отже, вам потрібно помножити розмір одного рядка, який повертається на sizeof (масив) з кількістю виділених вами рядків, наприклад:

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

Також зауважте, що sizeof (масив) працюватиме лише для статично виділених масивів. Для динамічно виділеного масиву ви б писали

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

Перша частина помилкова. Для sizeofоператора arrayне є покажчиком (якщо він був оголошений масивом). Див. Мою відповідь для прикладу.
Alok Singhal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.