2012-02-05 2 views
68

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

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

+0

Нет C++ в поле зрения - удаляющая бирка. –

+13

@BoPersson: ну, 'новый' * есть * C++ ... –

+0

@Matteo - ну, да. Не сильно повлиял на ответы (пока только сейчас :-). –

ответ

121

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 для int s, в то время как он может немного ухудшиться для меньших типов, если оптимизатор недостаточно умен. Тем не менее, если у вас есть сомнения, профиль.

+8

Начиная с стандарта ISO ISO 1999 года, на самом деле не гарантировалось, что 'memset' установит целое число в 0; не было никакого конкретного утверждения, что all-bits-zero является представлением '0'. Техническое исправление добавило такую ​​гарантию, которая включена в стандарт ISO C 2011 года. Я считаю, что all-bits-zero * является * корректным представлением '0' для всех целых типов во всех существующих реализациях C и C++, поэтому комитет смог добавить это требование. (Нет аналогичной гарантии для типов с плавающей точкой или указателя.) –

+2

Добавление к комментарию @ KeithThompson: эта гарантия была добавлена ​​в 6.2.6.2/5 в текстовом виде в TC2 (2004); однако, если нет битов заполнения, то 6.2.6.2/1 и/2 уже гарантировали, что все биты-ноль были '0'. (С битами дополнений существует вероятность того, что все бит-ноль может быть ловушкой). Но в любом случае TC должен признать и заменить дефектный текст, поэтому с 2004 года мы должны действовать так, как если бы C99 всегда содержал этот текст. –

+0

В C, если вы выделили динамический массив _correctly_, тогда не будет разницы между двумя memsets. Правильным динамическим распределением будет 'int (* myarray) [N] = malloc (sizeof (* myarray));'. – Lundin

10

От memset():

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

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

+2

sizeof будет работать, даже если размер массива неизвестен во время компиляции. (конечно, только когда это массив) – asaelr

+2

@asaelr: В C++ 'sizeof' всегда оценивается во время компиляции (и не может использоваться с VLA). В C99 это может быть выражение времени выполнения в случае VLA. –

+0

@BenVoigt Ну, вопрос касается как 'c', так и' C++ '. Я прокомментировал ответ Алекса, в котором говорится: «Вы можете использовать sizeof (myarray), если размер myarray известен во время компиляции». – asaelr

2

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

T myarray[100] = {0}; 

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

+1

Вопрос гласит: «Не только для инициализации». –

5

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

Я бы опустил ';' после 'while (0)', поэтому можно называть 'ZERO (a, n);', +1 отличный ответ – 0x90

+0

@ 0x90: Да, вы абсолютно правы. Вся точка 'do {} в то время как (0)' idiom не требует ';' в определении макроса. Исправлена. – AnT

1

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

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

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

Это неверно, оно очистит байты SIZE. 'memset (arr, 0, SIZE * sizeof (T)); будет правильным. –

+0

@KornelKisielewicz D'oh! Надеюсь, никто не копировал эту функцию за последние 1,5 года :( – Navin

+1

надеюсь, что нет, я прокомментировал, потому что google привел меня сюда :) –

7

Этот вопрос, хотя и довольно старый, нуждается в некоторых критериев, как он просит не самый идиоматических способ, или способ, который может быть записан в наименьшее число линий, но самый быстрый способ. И глупо отвечать на этот вопрос без какого-либо фактического тестирования. Поэтому я сравнил четыре решения: 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.

лязг 3,8 (Использование лязг-сл фронтэнда, флаги оптимизации =/OX/арка: 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 = родной - mtune = родной -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/арка: 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 (это делает впечатляющее уничтожение мертвого кода на статических массивах, а затем имеет ужасную производительность для заполнения). Хотя моя реализация значительно быстрее, это может быть связано только с тем, что она распознает, что для очистки бит имеет гораздо меньше накладных расходов, чем любая другая операция настройки.

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

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

+1

Тест без кода и без упоминания о версии компилятора и используемых опциях ... Хм ... –

+0

У меня уже были версии для компилятора (они были немного скрыты) и просто добавили применимые варианты. – Benjamin

+0

недопустимый аргумент типа унарного '* '(have' size_t {aka unsigned int} ') | –

0

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

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. Также вы получаете ошибку времени компиляции, если используете динамический массив с указанием размера.

Смежные вопросы