Це питання, хоч і досить старе, потребує певних орієнтирів, оскільки воно вимагає не самого ідіоматичного способу, або способу, який можна записати в найменшій кількості рядків, але найшвидшого . І безглуздо відповісти на це питання без фактичного тестування. Тож я порівняв чотири рішення: 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 int та довгих довгих масивів, розподілених як статично, так і динамічно, але за винятком msvc, який робив усунення мертвого коду на статичних масивах, результати були надзвичайно порівнянні, тому я покажу лише динамічну продуктивність масиву. Позначення часу - це мс на 1 мільйон ітерацій, використовуючи функцію годинника з низькою точністю time.h.
clang 3.8 (Використовуючи frontend 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 / арка: 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 (це робить вражаюче усунення мертвого коду на статичних масивах, а потім має жахливу продуктивність для заповнення). Хоча моя реалізація значно швидша, це може бути лише тому, що вона визнає, що очищення бітів має значно менші накладні витрати, ніж будь-яка інша операція налаштування.
Реалізація Кланг заслуговує більше уваги, оскільки це значно швидше. Деякі додаткові тестування показують, що його мемсет насправді спеціалізується на нульових - ненульових мемсетах для 400-байтного масиву набагато повільніше (~ 220 мс) і порівнянні з gcc. Однак ненульове запам’ятовування з масивом 800 байтів не має різниці в швидкості, тому, мабуть, тому в такому випадку їх мемсет має більш низьку продуктивність, ніж моя реалізація - спеціалізація призначена лише для невеликих масивів, а обріз - близько 800 байт. Також зауважте, що gcc 'fill' та 'ZERO' не оптимізують мемсет (дивлячись на генерований код), gcc просто генерує код з однаковими характеристиками продуктивності.
Висновок: memset насправді не оптимізований для цього завдання, тому що люди будуть робити вигляд, що це (інакше gcc і msvc та memset llvm матимуть однакову продуктивність). Якщо продуктивність має значення, то memset не повинен бути остаточним рішенням, особливо для цих незручних масивів середнього розміру, оскільки він не спеціалізований для очищення бітів і не оптимізований вручну краще, ніж компілятор може зробити самостійно.
new
це C ++ ...