Сброс массива C int до нуля: самый быстрый способ?
предположим, что у нас есть T myarray[100] с T = int, unsigned int, long long int или unsigned long long int, каков самый быстрый способ сбросить все его содержимое до нуля (не только для инициализации, но и для сброса содержимого несколько раз в моей программе)? Может быть, с мемсетом?
тот же вопрос для динамического массива, как T *myarray = new T[100].
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, в то время как он может работать немного хуже для небольших типов, если оптимизатор недостаточно умен. Еще, когда сомневаешься, профиль.
С
memset():memset(myarray, 0, sizeof(myarray));можно использовать
sizeof(myarray)если размерmyarrayизвестен во время компиляции. В противном случае, если вы используете массив динамического размера, например полученный черезmallocилиnew, вам нужно будет отслеживать длину.
этот вопрос, хотя и довольно старый, нуждается в некоторых ориентирах, поскольку он требует не самого идиоматического способа или способа, который можно написать в наименьшем количестве строк, но быстрый путь. И глупо отвечать на этот вопрос без фактического тестирования. Поэтому я сравнил четыре решения, memset против std:: fill против нуля ответа 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 и длинных длинных массивов, как статически, так и динамически выделенных, но за исключением msvc, который сделал исключение мертвого кода на статических массивах результаты были чрезвычайно сопоставимы, поэтому я покажу только динамическую производительность массива. Маркировка времени-ms для 1 миллиона итераций, используя время.функция часов низкой точности 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: 188gcc 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: 185msvc 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 оказывается на самом деле специализируется на ноль-не ноль memsets за 400 байт массива значительно медленнее (~220ms) и сравним с ССЗ. Тем не менее, ненулевой memsetting с байтовым массивом 800 делает никакой разницы в скорости, поэтому, наверное, в этом случае их функцию memset, имеет худшую производительность, чем моя реализация--специализация только для небольших массивов, и cuttoff составляет примерно 800 байт. Также обратите внимание, что gcc 'fill' и 'ZERO' не оптимизируются для memset (глядя на сгенерированный код), gcc просто генерирует код с идентичными характеристиками производительности.
вывод: memset на самом деле не оптимизирован для этой задачи, а также люди будут притворяться, что это так (в противном случае GCC и msvc и llvm memset будут иметь одинаковую производительность). Если производительность имеет значение, то memset не должен быть окончательным решением, особенно для этих неудобных массивов среднего размера, потому что он не специализируется на очистке битов и не оптимизирован вручную лучше, чем компилятор может сделать сам по себе.
можно использовать
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);
для статического объявления я думаю, что вы могли бы использовать:
T myarray[100] = {0};для динамического объявления я предлагаю тот же путь:
memset
zero(myarray);все, что вам нужно в C++.просто добавьте это в другой файл:
template<typename T, size_t SIZE> inline void zero(T(&arr)[SIZE]){ memset(arr, 0, SIZE*sizeof(T)); }
вот функция, которую я использую:
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. Также вы получите ошибку компиляции, если вы используете динамический массив с указанием размера.
Comments