Является memset() более эффективным, чем для цикла в C?
является memset более эффективным, чем для цикла.
поэтому, если у меня есть
char x[500];
memset(x,0,sizeof(x));
или
char x[500];
for(int i = 0 ; i < 500 ; i ++) x[i] = 0;
какой из них более эффективен и почему? есть ли какая-либо специальная инструкция в оборудовании для инициализации уровня блока.
Ответы
Ответ 1
Скорее всего, memset
будет намного быстрее, чем этот цикл. Обратите внимание, как вы обрабатываете один символ за раз, но эти функции настолько оптимизированы, что задают несколько байтов за раз, даже используя, когда это доступно, инструкции MMX и SSE.
Я думаю, что парадигматический пример этих оптимизаций, которые обычно остаются незамеченными, является библиотекой GNU C strlen
. Казалось бы, он имеет как минимум O (n) производительность, но на самом деле имеет O (n/4) или O (n/8) в зависимости от архитектуры (да, я знаю, в больших O() будет одинаковым, но вы фактически получаете восьмую часть времени). Как? Трудно, но приятно: strlen.
Ответ 2
Хорошо, почему бы нам не взглянуть на сгенерированный код сборки, полную оптимизацию в VS 2010.
char x[500];
char y[500];
int i;
memset(x, 0, sizeof(x) );
003A1014 push 1F4h
003A1019 lea eax,[ebp-1F8h]
003A101F push 0
003A1021 push eax
003A1022 call memset (3A1844h)
И ваша петля...
char x[500];
char y[500];
int i;
for( i = 0; i < 500; ++i )
{
x[i] = 0;
00E81014 push 1F4h
00E81019 lea eax,[ebp-1F8h]
00E8101F push 0
00E81021 push eax
00E81022 call memset (0E81844h)
/* note that this is *replacing* the loop,
not being called once for each iteration. */
}
Итак, в этом компиляторе сгенерированный код будет точно таким же. memset
работает быстро, и компилятор достаточно умен, чтобы знать, что вы делаете то же самое, что и призывать memset
один раз, поэтому он делает это за вас.
Если компилятор фактически оставил цикл как-есть, то он, вероятно, будет медленнее, поскольку вы можете установить более одного блока размера байта за раз (т.е. вы можете немного развернуть свой цикл. memset
будет, по крайней мере, столь же быстрым, как наивная реализация, такая как цикл. Попробуйте его в сборке отладки, и вы заметите, что цикл не заменен.
Тем не менее, это зависит от того, что делает компилятор для вас. Глядя на разборку, всегда есть хороший способ точно знать, что происходит.
Ответ 3
Это действительно зависит от компилятора и библиотеки. Для старых компиляторов или простых компиляторов memset может быть реализован в библиотеке и не будет работать лучше, чем настраиваемый цикл.
Для почти всех компиляторов, которые стоит использовать, memset является неотъемлемой функцией, и компилятор будет генерировать для нее оптимизированный встроенный код.
Другие предложили профилирование и сравнение, но я бы не стал беспокоиться. Просто используйте memset. Код прост и понятен. Не беспокойтесь об этом, пока ваши тесты не скажут вам, что эта часть кода - это горячая точка производительности.
Ответ 4
Ответ: "Это зависит". memset
МОЖЕТ быть более эффективным или внутренне использовать цикл for. Я не могу придумать случай, когда memset
будет менее эффективным. В этом случае он может превратиться в более эффективный цикл: цикл повторяется 500 раз, каждый раз задавая значение байта массива 0. На 64-битной машине вы можете прокручивать, устанавливая 8 байтов (длинный длинный) за раз, что было бы почти в 8 раз быстрее и просто занималось оставшимися 4 байтами (500% 8) в конце.
EDIT:
на самом деле, это то, что memset
делает в glibc:
http://repo.or.cz/w/glibc.git/blob/HEAD:/string/memset.c
Как отметил Майкл, в некоторых случаях (когда длина массива известна во время компиляции), компилятор C может встроить memset
, избавляясь от накладных расходов на вызов функции. Glibc также имеет версии с оптимизацией сборки memset
для большинства основных платформ, например amd64:
http://repo.or.cz/w/glibc.git/blob/HEAD:/sysdeps/x86_64/memset.S
Ответ 5
Хорошие компиляторы распознают цикл for и заменяют его либо оптимальной последовательностью, либо вызовом memset. Они также заменят memset оптимальной последовательностью, когда размер буфера невелик.
На практике с оптимизирующим компилятором сгенерированный код (и, следовательно, производительность) будет идентичным.
Ответ 6
Согласитесь с выше. Это зависит. Но, наверняка, memset быстрее или равен циклу for. Если вы не уверены в своей среде или слишком ленивы для проверки, берете безопасный маршрут и идите с memset.
Ответ 7
void fill_array(void* array, size_t size_of_item, size_t length, void* value) {
uint8_t* bytes = value;
uint8_t first_byte = bytes[0];
if (size_of_item == 1) {
memset(array, first_byte, length);
return;
}
// size_of_item > 1 here.
bool all_bytes_are_identical = true;
for (size_t byte_index = 1; byte_index < size_of_item; byte_index++) {
if (bytes[byte_index] != first_byte) {
all_bytes_are_identical = false;
break;
}
}
if (all_bytes_are_identical) {
memset(array, first_byte, size_of_item * length);
return;
}
for (size_t index = 0; index < length; index++) {
memcpy((uint8_t*)array + size_of_item * index, value, size_of_item);
}
}
memset
более эффективен, он не должен заботиться о несимметричных значениях (где all_bytes_are_identical
- false
). Таким образом, вы будете искать, как его обернуть.
Это мой вариант. Он работает как для маленьких, так и для больших систем.