Подтвердить что ты не робот

Является memset() более эффективным, чем для цикла в C?

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

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

или

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

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

4b9b3361

Ответ 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). Таким образом, вы будете искать, как его обернуть.

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