Reset C int array to zero: самый быстрый способ?

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

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

Ответ 1

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

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

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

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

Ответ 2

Из memset():

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

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

Ответ 3

Этот вопрос, хотя и довольно старый, нуждается в некоторых тестах, поскольку он запрашивает не самый идиоматический путь или способ, который может быть написан в наименьшем числе строк, но самый быстрый способ. И глупо отвечать на этот вопрос без какого-либо фактического тестирования. Таким образом, я сравнил четыре решения: memset vs. std:: fill vs. 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 и более длинный длинный, как статически, так и динамически распределенных, но за исключением msvc, который удалил мертвый код на статических массивах, результаты были чрезвычайно сопоставимы, поэтому я буду показывать только динамическую производительность массива. Маркировка времени - ms для 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 gilling gcc, MSVC типичная пятнистая оптимизация (он делает впечатляющее уничтожение мертвого кода на статических массивах, а затем имеет ужасную производительность для заполнения). Хотя моя реализация значительно быстрее, это может быть связано только с тем, что она распознает, что для очистки бит имеет гораздо меньше накладных расходов, чем любая другая операция настройки.

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

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

Ответ 4

Вы можете использовать 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)

Это даст вам функциональность на С++, которая позволит вам "reset к нулям" массива объектов любого типа, не прибегая к хакам типа memset. В принципе, это 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);

Ответ 5

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

T myarray[100] = {0};

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

Ответ 6

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

Просто добавьте это в другой файл:

template<typename T, size_t SIZE> inline void zero(T(&arr)[SIZE]){
    memset(arr, 0, SIZE*sizeof(T));
}

Ответ 7

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

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

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