Сбросить массив C int до нуля: самый быстрый способ?

Предполагая, что у нас есть T myarray[100] с T = int, unsigned int, long long int или unsigned long long int, каков самый быстрый способ сбросить все его содержимое до нуля (не только для инициализации, но и для сброса содержимого несколько раз в моей программе )? Может с memset?

Тот же вопрос для динамического массива, такого как T *myarray = new T[100].


person Vincent    schedule 05.02.2012    source источник
comment
@BoPersson: ну, new это C ++ ...   -  person Matteo Italia    schedule 06.02.2012
comment
@ Маттео - ну да. Не сильно повлиял на ответы (до сих пор :-).   -  person Bo Persson    schedule 06.02.2012
comment
@BoPersson: Мне было неприятно говорить только о memset, когда каким-то образом задействован C ++ ... :)   -  person Matteo Italia    schedule 06.02.2012
comment
В современном компиляторе невозможно обыграть простой цикл for. Но, что удивительно, вы можете сделать намного хуже, если будете умны.   -  person David Schwartz    schedule 21.09.2015
comment
Используйте структуру и вставьте в нее массив. Создайте экземпляр, в котором все нули. Используйте это, чтобы обнулить других, которые вы создаете. Это работает хорошо. Нет включает, нет функций, довольно быстро.   -  person Xofo    schedule 05.03.2019


Ответы (7)


memset (от <string.h>) - вероятно, самый быстрый стандартный способ, поскольку обычно это процедура, написанная непосредственно на ассемблере и оптимизированная вручную.

memset(myarray, 0, sizeof(myarray)); // for automatically-allocated arrays
memset(myarray, 0, N*sizeof(*myarray)); // for heap-allocated arrays, where N is the number of elements

Кстати, в C ++ идиоматическим способом было бы использовать std::fill (из <algorithm>):

std::fill(myarray, myarray+N, 0);

который может быть автоматически оптимизирован до memset; Я совершенно уверен, что он будет работать так же быстро, как memset для ints, в то время как он может работать немного хуже для меньших типов, если оптимизатор недостаточно умен. Тем не менее, если есть сомнения, профиль.

person Matteo Italia    schedule 05.02.2012
comment
Что касается стандарта ISO C 1999 г., не было фактически гарантировано, что memset установит целое число в 0; не было никакого конкретного утверждения, что все нулевые биты являются представлением 0. Техническое исправление добавило такую ​​гарантию, которая включена в стандарт ISO 2011 года. Я считаю, что all-bit-zero является допустимым представлением 0 для всех целочисленных типов во всех существующих реализациях C и C ++, поэтому комитет смог добавить это требование. (Нет аналогичной гарантии для типов с плавающей запятой или указателей.) - person Keith Thompson; 16.06.2013
comment
Добавление к комментарию @ KeithThompson: эта гарантия была добавлена ​​в 6.2.6.2/5 в виде обычного текста в TC2 (2004); однако, если битов заполнения нет, то 6.2.6.2/1 и / 2 уже гарантировали, что все-биты-ноль были 0. (С битами заполнения существует возможность, что все-биты-ноль могут быть представлением ловушки). Но в любом случае TC должен подтверждать и заменять дефектный текст, поэтому с 2004 года мы должны действовать так, как если бы C99 всегда содержал этот текст. - person M.M; 29.05.2015
comment
В C, если вы распределили динамический массив правильно, тогда не будет никакой разницы между двумя наборами памяти. Правильное динамическое размещение будет int (*myarray)[N] = malloc(sizeof(*myarray));. - person Lundin; 21.09.2015
comment
@Lundin: конечно - если вы знаете во время компиляции, насколько велик N, но в подавляющем большинстве случаев, если вы использовали malloc, вы знали только во время выполнения. - person Matteo Italia; 21.09.2015
comment
@MatteoItalia У нас есть VLA с 1999 года. - person Lundin; 21.09.2015
comment
И если вы хотите очистить только часть этого массива: memset(&(myarray[offset]),0,sizeof(myarray)-(sizeof(myarray[0])*offset)); - person mbger; 28.02.2017

Этот вопрос, хотя и довольно старый, требует некоторых тестов, так как он запрашивает не самый идиоматический способ или способ, который можно записать с наименьшим количеством строк, но самый самый быстрый способ. И глупо отвечать на этот вопрос без реального тестирования. Итак, я сравнил четыре решения: memset против std :: fill против ZERO ответа AnT и решения, которое я сделал с использованием встроенных функций AVX.

Обратите внимание, что это решение не является универсальным, оно работает только с 32- или 64-битными данными. Прокомментируйте, если этот код делает что-то неправильно.

#include<immintrin.h>
#define intrin_ZERO(a,n){\
size_t x = 0;\
const size_t inc = 32 / sizeof(*(a));/*size of 256 bit register over size of variable*/\
for (;x < n-inc;x+=inc)\
    _mm256_storeu_ps((float *)((a)+x),_mm256_setzero_ps());\
if(4 == sizeof(*(a))){\
    switch(n-x){\
    case 3:\
        (a)[x] = 0;x++;\
    case 2:\
        _mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
    case 1:\
        (a)[x] = 0;\
        break;\
    case 0:\
        break;\
    };\
}\
else if(8 == sizeof(*(a))){\
switch(n-x){\
    case 7:\
        (a)[x] = 0;x++;\
    case 6:\
        (a)[x] = 0;x++;\
    case 5:\
        (a)[x] = 0;x++;\
    case 4:\
        _mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
    case 3:\
        (a)[x] = 0;x++;\
    case 2:\
        ((long long *)(a))[x] = 0;break;\
    case 1:\
        (a)[x] = 0;\
        break;\
    case 0:\
        break;\
};\
}\
}

Я не буду утверждать, что это самый быстрый метод, так как я не специалист по оптимизации низкого уровня. Скорее, это пример правильной реализации, зависящей от архитектуры, которая работает быстрее, чем memset.

Теперь о результатах. Я рассчитал производительность для массивов размера 100 int и long long, как статически, так и динамически распределенных, но за исключением msvc, который выполнил удаление мертвого кода на статических массивах, результаты были чрезвычайно сопоставимы, поэтому я покажу только производительность динамического массива. Разметка времени составляет миллисекунды для 1 миллиона итераций с использованием функции часов с низкой точностью time.h.

clang 3.8 (Используя интерфейс clang-cl, флаги оптимизации = / OX / arch: AVX / Oi / Ot)

int:
memset:      99
fill:        97
ZERO:        98
intrin_ZERO: 90

long long:
memset:      285
fill:        286
ZERO:        285
intrin_ZERO: 188

gcc 5.1.0 (флаги оптимизации: -O3 -march = native -mtune = native -mavx):

int:
memset:      268
fill:        268
ZERO:        268
intrin_ZERO: 91
long long:
memset:      402
fill:        399
ZERO:        400
intrin_ZERO: 185

msvc 2015 (флаги оптимизации: / OX / arch: AVX / Oi / Ot):

int
memset:      196
fill:        613
ZERO:        221
intrin_ZERO: 95
long long:
memset:      273
fill:        559
ZERO:        376
intrin_ZERO: 188

Здесь происходит много интересного: llvm убивает gcc, типичные точечные оптимизации MSVC (он производит впечатляющее удаление мертвого кода на статических массивах, а затем имеет ужасную производительность для заполнения). Хотя моя реализация значительно быстрее, это может быть только потому, что она признает, что очистка битов имеет гораздо меньше накладных расходов, чем любая другая операция настройки.

Реализация Clang заслуживает большего внимания, поскольку она значительно быстрее. Некоторое дополнительное тестирование показывает, что его memset на самом деле специализирован для нулевых - ненулевых memset для 400-байтового массива намного медленнее (~ 220 мс) и сопоставим с gcc. Однако ненулевое значение memset с 800-байтовым массивом не влияет на скорость, вероятно, поэтому в этом случае их memset имеет худшую производительность, чем моя реализация - специализация предназначена только для небольших массивов, а отсечение составляет около 800 байтов. Также обратите внимание, что gcc 'fill' и 'ZERO' не оптимизируются для memset (глядя на сгенерированный код), gcc просто генерирует код с идентичными характеристиками производительности.

Заключение: memset на самом деле не оптимизирован для этой задачи, как люди могли бы это представить (иначе gcc, msvc и llvm memset имели бы одинаковую производительность). Если производительность имеет значение, тогда memset не должен быть окончательным решением, особенно для этих неудобных массивов среднего размера, потому что он не специализируется на очистке битов и не оптимизирован вручную лучше, чем компилятор может сделать сам по себе.

person Benjamin    schedule 21.09.2015
comment
Тест без кода и без упоминания версии компилятора и используемых опций? Хм... - person Marc Glisse; 21.09.2015
comment
У меня уже были версии компилятора (они были немного скрыты), и я просто добавил применимые используемые параметры. - person Benjamin; 23.09.2015
comment
недопустимый аргумент типа унарного '*' (имеет 'size_t {aka unsigned int}') | - person Piotr Wasilewicz; 25.06.2017
comment
Будучи настолько щедрым, что написал свой собственный оптимизированный метод обнуления - не могли бы вы сказать несколько слов о том, КАК он работает и ПОЧЕМУ он быстрее? код почти не требует пояснений. - person Motti Shneor; 31.12.2019
comment
@MottiShneor Выглядит сложнее, чем есть на самом деле. Регистр AVX имеет размер 32 байта. Поэтому он вычисляет, сколько значений a помещается в регистр. После этого он перебирает все 32-байтовые блоки, которые должны быть полностью перезаписаны с использованием арифметики указателей ((float *)((a)+x)). Две встроенные функции (начиная с _mm256) просто создают 32-байтовый регистр с нулевой инициализацией и сохраняют его в текущем указателе. Это первые 3 строчки. Остальное просто обрабатывает все особые случаи, когда последний 32-байтовый блок не должен быть полностью перезаписан. Это быстрее за счет векторизации. - Надеюсь, это поможет. - person wychmaster; 05.03.2020

Из memset():

memset(myarray, 0, sizeof(myarray));

Вы можете использовать sizeof(myarray), если размер myarray известен во время компиляции. В противном случае, если вы используете массив динамического размера, например, полученный с помощью malloc или new, вам нужно будет отслеживать длину.

person Alex Reynolds    schedule 05.02.2012
comment
sizeof будет работать, даже если размер массива неизвестен во время компиляции. (конечно, только когда это массив) - person asaelr; 05.02.2012
comment
@asaelr: В C ++ sizeof всегда оценивается во время компиляции (и не может использоваться с VLA). В C99 это может быть выражение времени выполнения в случае VLA. - person Ben Voigt; 16.06.2013
comment
@BenVoigt Ну, вопрос касается как c, так и c++. Я прокомментировал ответ Алекса, в котором говорится, что вы можете использовать sizeof (myarray), если размер myarray известен во время компиляции. - person asaelr; 16.06.2013
comment
@asaelr: А в C ++ он совершенно прав. В вашем комментарии ничего не говорится о C99 или VLA, поэтому я хотел его прояснить. - person Ben Voigt; 16.06.2013

Вы можете использовать memset, но только потому, что наш выбор типов ограничен целыми типами.

В общем случае в C имеет смысл реализовать макрос

#define ZERO_ANY(T, a, n) do{\
   T *a_ = (a);\
   size_t n_ = (n);\
   for (; n_ > 0; --n_, ++a_)\
     *a_ = (T) { 0 };\
} while (0)

Это даст вам функциональность, подобную C ++, которая позволит вам «сбросить до нуля» массив объектов любого типа, не прибегая к хакам вроде memset. По сути, это аналог C шаблона функции C ++, за исключением того, что вы должны явно указать аргумент типа.

Вдобавок вы можете построить «шаблон» для нераспавшихся массивов.

#define ARRAY_SIZE(a) (sizeof (a) / sizeof *(a))
#define ZERO_ANY_A(T, a) ZERO_ANY(T, (a), ARRAY_SIZE(a))

В вашем примере это будет применяться как

int a[100];

ZERO_ANY(int, a, 100);
// or
ZERO_ANY_A(int, a);

Также стоит отметить, что специально для объектов скалярных типов можно реализовать не зависящий от типа макрос

#define ZERO(a, n) do{\
   size_t i_ = 0, n_ = (n);\
   for (; i_ < n_; ++i_)\
     (a)[i_] = 0;\
} while (0)

и

#define ZERO_A(a) ZERO((a), ARRAY_SIZE(a))

превращая приведенный выше пример в

 int a[100];

 ZERO(a, 100);
 // or
 ZERO_A(a);
person AnT    schedule 15.06.2013
comment
Я бы пропустил ; после while(0), чтобы можно было назвать ZERO(a,n);, +1 отличный ответ - person 0x90; 16.06.2013
comment
@ 0x90: Да, вы совершенно правы. Весь смысл do{}while(0) идиомы не требует ; в определении макроса. Фиксированный. - person AnT; 16.06.2013

Для статического объявления, я думаю, вы могли бы использовать:

T myarray[100] = {0};

Для динамического объявления я предлагаю такой же способ: memset

person Bruno Soares    schedule 06.02.2012
comment
Вопрос гласит: Не только для инициализации. - person Ben Voigt; 16.06.2013

zero(myarray); - это все, что вам нужно в C ++.

Просто добавьте это в заголовок:

template<typename T, size_t SIZE> inline void zero(T(&arr)[SIZE]){
    memset(arr, 0, SIZE*sizeof(T));
}
person Navin    schedule 27.01.2014
comment
Это неверно, будет очищено SIZE байтов. 'memset (arr, 0, РАЗМЕР * sizeof (T));' было бы правильно. - person Kornel Kisielewicz; 27.05.2015
comment
@KornelKisielewicz D'oh! Надеюсь, за последние 1,5 года эту функцию никто не копировал :( - person Navin; 29.05.2015
comment
надеюсь, что нет, я прокомментировал, потому что Google привел меня сюда :) - person Kornel Kisielewicz; 03.06.2015
comment
Обратите внимание, что эта функция zero также верна, например, для T=char[10], что может быть в случае, когда аргумент arr является многомерным массивом, например. char arr[5][10]. - person mandrake; 23.10.2015
comment
@mandrake Ты в этом уверен? Я ожидал, что T=char* в этом случае :( - person Navin; 24.10.2015
comment
Да, я тестировал несколько случаев с gcc 4.7.3. Я считаю, что это было бы хорошо отметить для этого ответа, поскольку в противном случае вам потребовались бы специализации шаблонов для каждого счетчика измерений массива. Другие ответы также не обобщают, например макрос ARRAY_SIZE, который дает неправильный размер при использовании в многомерном массиве, лучшим именем, возможно, будет ARRAY_DIM<n>_SIZE. - person mandrake; 26.10.2015
comment
Это неверно для числа с плавающей запятой и удваивается, поскольку 0,0f и 0,0 - это не все нули. - person t0k3n1z3r; 20.11.2018

Вот функция, которую я использую:

template<typename T>
static void setValue(T arr[], size_t length, const T& val)
{
    std::fill(arr, arr + length, val);
}

template<typename T, size_t N>
static void setValue(T (&arr)[N], const T& val)
{
    std::fill(arr, arr + N, val);
}

Вы можете назвать это так:

//fixed arrays
int a[10];
setValue(a, 0);

//dynamic arrays
int *d = new int[length];
setValue(d, length, 0);

Выше приведен более способ использования C ++ 11, чем использование memset. Также вы получите ошибку времени компиляции, если используете динамический массив с указанием размера.

person Shital Shah    schedule 02.12.2016
comment
исходный вопрос касается C, а не C ++, поэтому std :: fill не может быть правильным ответом - person Motti Shneor; 31.12.2019