memset () более эффективен, чем для цикла в C? - PullRequest
32 голосов
/ 10 сентября 2011

memset более эффективен, чем для цикла.так что если у меня есть

char x[500];
memset(x,0,sizeof(x));

или

char x[500];
for(int i = 0 ; i < 500 ; i ++) x[i] = 0;

, какой из них более эффективен и почему?Есть ли какая-либо специальная инструкция в аппаратном обеспечении для инициализации на уровне блока.

Ответы [ 7 ]

31 голосов
/ 10 сентября 2011

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

25 голосов
/ 10 сентября 2011

Скорее всего, memset будет намного быстрее, чем этот цикл.Обратите внимание, как вы обрабатываете один символ за один раз, но эти функции настолько оптимизированы, что устанавливают несколько байтов за раз, даже используя, когда это возможно, инструкции MMX и SSE.

Я думаю, чтоПримером таких оптимизаций, которые обычно остаются незамеченными, является функция библиотеки GNU C strlen.Можно было бы подумать, что он имеет как минимум O (n) производительность, но на самом деле он имеет O (N / 4) или O (N / 8) в зависимости от архитектуры (да, я знаю, в большом O () будет то же самое, но на самом деле вы получаете восьмое времени).Как?Хитро, но приятно: strlen .

12 голосов
/ 10 сентября 2011

Это действительно зависит от компилятора и библиотеки.Для более старых компиляторов или простых компиляторов memset может быть реализован в библиотеке и не будет работать лучше, чем пользовательский цикл.

Почти для всех компиляторов, которые стоит использовать, memset является встроенной функцией, и компилятор генерирует оптимизированныйвстроенный код для этого.

Другие предложили профилирование и сравнение, но я бы не стал беспокоиться.Просто используйте memset.Код прост и понятен.Не беспокойтесь об этом, пока ваши тесты не покажут вам, что эта часть кода является горячей точкой производительности.

8 голосов
/ 10 сентября 2011

Ответ «это зависит».memset МОЖЕТ быть более эффективным или внутренним циклом for.Я не могу вспомнить случай, когда memset будет менее эффективным.В этом случае он может превратиться в более эффективный цикл for: ваш цикл повторяется 500 раз, каждый раз устанавливая значение массива в байтах равным 0.На 64-битной машине вы можете выполнить цикл, устанавливая 8 байтов (длинный длинный) за раз, что будет почти в 8 раз быстрее, и просто обрабатывать оставшиеся 4 байта (500% 8) в конце.

РЕДАКТИРОВАТЬ:

на самом деле, это то, что 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

3 голосов
/ 10 сентября 2011

Хорошие компиляторы распознают цикл for и заменяют его оптимальной последовательностью или вызовом memset.Они также заменят memset оптимальной последовательностью при небольшом размере буфера.

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

2 голосов
/ 10 сентября 2011

Согласен с выше.Это зависит.Но наверняка memset быстрее или равен циклу for.Если вы не уверены в своем окружении или вам лень тестировать, выберите безопасный маршрут и отправляйтесь с memset.

0 голосов
/ 23 ноября 2018
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). Так что вы будете искать, как обернуть его.

Это мой вариант. Он работает как для маленьких, так и для больших систем байтов.

...