очистка небольшого целочисленного массива: memset против цикла for

Есть два способа обнуления массива integer / float:

memset(array, 0, sizeof(int)*arraysize);

или же:

for (int i=0; i <arraysize; ++i)
    array[i]=0;

очевидно, memset быстрее для большихarraysize, Тем не менее, в какой момент издержки memset на самом деле больше, чем издержки цикла for? Например, для массива размером 5 - что будет лучше? Первая, вторая или, может быть, даже развернутая версия:

array[0] = 0;
array[1] = 0;
array[2] = 0;
array[3] = 0;
array[4] = 0;
 Artem Barger15 июл. 2009 г., 23:30
Мне просто интересно, как ты пришел к этому вопросу?
 Andrew Medico16 июл. 2009 г., 03:29
Это только для инициализации на сайте декларации. Формы в вопросе могут быть использованы в любое время для очистки существующего массива.
 Hosam Aly16 июл. 2009 г., 00:13
Вы не упомянули третью версию:int array[5] = {0};
 neuro15 июл. 2009 г., 23:17
ну, я действительно не вижу интересную часть. Единственный реальный ответ будет через сравнение трех версий на данной платформе / компиляторе. Более того, зная ответ не очень полезно, нет? Но ради эффективного написания кода бенчмарка;)
 Claudiu15 июл. 2009 г., 23:23
да, я планирую сделать это сам. но теперь весь интернет узнает! (по крайней мере для Windows с GCC от MINGW ...)

Ответы на вопрос(4)

Нет возможности ответить на вопрос без измерения. Это будет полностью зависеть от реализации компилятора, процессора и библиотеки времени выполнения.

memset () может быть немного «запахом кода», потому что он может быть склонен к переполнению буфера, аннулированию параметров и имеет неудачную способность очищать только «побайтно». Однако можно с уверенностью сказать, что он будет «самым быстрым». во всех случаях, кроме крайних.

Я склонен использовать макрос, чтобы обернуть это, чтобы избежать некоторых проблем:

#define CLEAR(s) memset(&(s), 0, sizeof(s))

Это обходит вычисления размера и устраняет проблему обмена параметрами длины и значения.

Короче говоря, используйте memset () "под капотом". Напишите, что вы собираетесь, и дайте компилятору позаботиться об оптимизации. Большинство из них невероятно хороши в этом.

 16 июл. 2009 г., 10:41
@micmoo - спасибо - исправлено. @mike re Macros: да ... однако они неизбежны в C. Ответ C ++ на этот вопрос будетvery разные!
 30 июл. 2009 г., 23:44
Ваш макрос затруднит обнаружение проблемы с кодом, подобным этому:char* foo = malloc(80); CLEAR(foo);
 16 июл. 2009 г., 03:19
Я думаю, что вы сделали параметр макроса (x), но использовали (ы) в фактическом теле ... возможно, захотите изменить это.
 16 июл. 2009 г., 02:44
++ Ohmygosh! Вы используете макрос !? Лучше иди в подполье!
 05 июл. 2012 г., 17:25
@ Родди, в этой дискуссии определенно есть место для субъективизма. По моему опыту, такой вызов макроса добавляет больше запутывания, чем оно того стоит. Опытный программист, скорее всего, сразу получит двойную оценку при чтенииmemset звонки, которые вы только что написали. Но видя ошибку вchar* foo = malloc(80); CLEAR(foo); требует знания определенияCLEAR, (Возможно, опытный программист сразу понял бы, что нет разумного определенияCLEAR может быть правильным для динамически размещенного указателя; но это не тот опыт, на который я бы хотел положиться.)
Решение Вопроса

memset () будет встроен вашим компилятором (большинство компиляторов трактуют его как «встроенный», что в основном означает, что он встроен, за исключением, может быть, с наименьшей оптимизацией или без явного отключения).

Например, вот некоторыезаметки о выпуске из GCC 4.3:

Code generation of block move (memcpy) and block set (memset) was rewritten. GCC can now pick the best algorithm (loop, unrolled loop, instruction with rep prefix or a library call) based on the size of the block being copied and the CPU being optimized for. A new option -minline-stringops-dynamically has been added. With this option string operations of unknown size are expanded such that small blocks are copied by in-line code, while for large blocks a library call is used. This results in faster code than -minline-all-stringops when the library implementation is capable of using cache hierarchy hints. The heuristic choosing the particular algorithm can be overwritten via -mstringop-strategy. Newly also memset of values different from 0 is inlined.

Может быть, компилятор может сделать что-то похожее с альтернативными примерами, которые вы привели, но я держу пари, что это менее вероятно.

И этоgrep-Возможно и более сразу видно, какова цель загрузки (не то, что цикл особенно труден для выполнения).

 30 июл. 2009 г., 23:55
расслабьтесь, если вы думаете, что «компилятор лучше, чем вы»; это то, что вы должны следовать, проверить это -liranuna.com/sse-intrinsics-optimizations-in-popular-compilers
 31 июл. 2009 г., 00:44
@LiraLuna - это довольно интересная статья, но я думаю, что вы согласны с тем, что memset () / memcpy () относятся к классу, отличному от встроенных в SSE, с точки зрения того, сколько работы ушло на генерацию кода компилятора. Кроме того, вы хотите выполнять только тот уровень анализа, который вы выполняли для кода, который действительно критичен к производительности (или, возможно, является академическим упражнением) и, следовательно, заслуживает вашего всестороннего внимания - не для каждой копии буфера. или ясно.
 Claudiu15 июл. 2009 г., 23:39
отличный ответ, спасибо!
 16 июл. 2009 г., 10:59
Это отличный пример того, как часто слышимый «компилятор» лучше, чем вы, оптимизируете ». Немногие прикладные программисты будут тратить это количество внимания на один вызов (и если они это сделают, их реальное приложение, скорее всего, пострадает). :)
 10 июл. 2016 г., 19:14
@LiraNuna: обновление 6 лет спустя: в настоящее время clang имеет более мощную встроенную оптимизацию SIMD, чем gcc. особенно для тасов, Clang даже не заботится о том, какие свойства вы использовали в первую очередь; он просто делает свой собственный выбор, какие инструкции выдавать для достижения того же результата в случайном порядке. Это может на самом делеbe detrimental when you try to hand-tune a sequence and clang's current choices are imperfect, но в большинстве случаев это здорово. (И в будущем, когда оптимизатор clang / LLVM узнает оптимальные тасовки для большего числа случаев, это не будет проблемой.)

все уже сказано. Но если вы рассмотрите это в своей программе, о которой я ничего не знаю, можно сделать что-то еще. Например, если этот код должен выполняться каждый раз для очистки массива, вы можете запустить поток, который постоянно выделяет новый массив с нулевыми элементами, назначенными глобальной переменной, которой ваш код, когда требуется очистить массив, просто указывает на.

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

Надеюсь, что это может помочь кому-то

 11 июл. 2016 г., 19:45
Для больших массивов, таких как пара MiB или что-то еще, может иметь смысл поддерживать пул нулевых массивов. IDK, если вы много выигрываете, позволяя ОС делать это за вас, и используяcalloc, Для меньших массивов,memset это очень дешево. Если вы собираетесь прикасаться к массиву сразу после обнуления, вам, вероятно, следует выполнять оба действия в одном и том же потоке.
 10 июл. 2016 г., 19:20
Это только хорошая идея для довольно больших массивов. Затраты на пропуск кеша при обнулении на одном ядре и использовании на другом ядре, не говоря уже о накладных расходах на синхронизацию, будут убивать для небольших массивов Я предполагаю, что отдельный поток обнуления стоит рассматривать только для массивов размером более 16 кБ. (Размер кэша L1 в современных процессорах Intel составляет 32 кБ)
 11 июл. 2016 г., 19:41
Каждое ядро имеет частный L1, и это как раз та проблема: массив будет горячим в кэше L1 на ядре, на котором выполняется поток очистки, а не на том ядре, которое пытается его использовать.memset небольшого массива в том же потоке, в котором он используется, эта память остается горячей в L1, поэтому последующие записи выполняются быстро. Если в последний раз он был записан другим потоком, он в лучшем случае будет горячим в общем кеше L3 (процессоры Intel, поскольку Nehalem использует большие инклюзивные кеши L3, поэтому трафик когерентности не должен проходить через основную память). В худшем случае он все еще находится в измененном состоянии в другом ядре L1 (MOESI).
 11 июл. 2016 г., 17:31
Я думал, что каждое ядро имеет свой собственный кэш L1, поэтому я не могу понять, где произойдет промах. Вы можете знать больше, чем я о кешах, хотя. Затраты на синхронизацию не должны быть проблемой, если массив не очищается слишком часто. Пока очищающий поток удерживает блокировку, другой поток будет делать что-то еще. Настоящим недостатком является то, что вам нужно очень хорошо знать, как часто нужно очищать массив в & quot; main & quot; я думаю, что это не всегда возможно. Если это невозможно, моя идея не сработает и все сильно замедлит
 10 июл. 2016 г., 19:23
Освобождение старых массивов и выделение новой обнуленной памяти, как вы предполагаете, может быть дешевле, особенно если большинство страниц не будет записано. Например, в Linux все вновь выделенные страницы из mmap копируются при записи и сопоставляются с одной и той же физической страницей обнуленной памяти.calloc(3) использует это и не портит страницы (в отличие отstd::vector). Первая запись на каждую страницу вызовет ошибку мягкой страницы.

Как уже заметил Майкл, gcc и я полагаю, что большинство других компиляторов оптимизируют это уже очень хорошо. Например, GCC превращает это

char arr[5];
memset(arr, 0, sizeof arr);

в

movl  $0x0, <arr+0x0>
movb  $0x0, <arr+0x4>

Это не становится лучше, чем это ...

 25 февр. 2017 г., 10:00
для немного больших массивов можно использовать 64-битные регистры или SIMD для обнуления 8/16/32 ... байт за раз

Ваш ответ на вопрос