Что на самом деле важные случаи, когда memcpy () работает быстрее, чем memmove ()?

[textField setKeyboardType:UIKeyboardTypeNumberPad];
16
задан Community 23 May 2017 в 11:46
поделиться

3 ответа

По крайней мере, есть неявная ветвь, которую нужно скопировать вперед или назад для memmove(), если компилятор не может сделать вывод, что перекрытие невозможно. Это означает, что без возможности оптимизации в пользу memcpy(), memmove() как минимум медленнее одной ветвью, а любое дополнительное пространство занято встроенными инструкциями для обработки каждого случая (если встраивание возможно).

Чтение кода eglibc-2.11.1 для memcpy() и memmove() подтверждает это как подозрение. Кроме того, нет возможности копирования страниц при обратном копировании, существенное ускорение доступно только в том случае, если нет шансов для наложения.

Подводя итог, это значит: если вы можете гарантировать, что регионы не перекрываются, то выбор memcpy() вместо memmove() позволяет избежать ветвления. Если источник и назначение содержат соответствующие области, выровненные по размеру страницы и по размеру страницы, и не перекрываются, некоторые архитектуры могут использовать аппаратно ускоренные копии для этих областей, независимо от того, вызвали ли вы memmove() или memcpy().

Update0

На самом деле есть еще одно отличие от предположений и наблюдений, которые я перечислил выше. Начиная с C99, следующие прототипы существуют для 2 функций:

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void * s1, const void * s2, size_t n);

Из-за возможности предположить, что 2 указателя s1 и s2 не указывают на перекрывающуюся память, прямые реализации C в memcpy могут использовать это для генерации более эффективного кода без обращения к ассемблеру, подробнее см. здесь . Я уверен, что memmove может сделать это, однако дополнительные проверки потребуются выше тех, которые я видел в eglibc, означая, что затраты производительности могут быть немного больше, чем одна ветвь для реализаций этих функций на Си.

19
ответ дан 30 November 2019 в 16:04
поделиться

Вполне возможно, что в большинстве реализаций стоимость вызова функции memmove () не будет значительно выше, чем memcpy () в любом сценарии, в котором определяется поведение обоих. Однако есть еще два момента, которые еще не упомянуты:

  1. В некоторых реализациях определение перекрытия адресов может быть дорогостоящим. В стандарте C нет способа определить, указывают ли исходные и целевые объекты на одну и ту же выделенную область памяти, и, следовательно, нельзя использовать операторы «больше» или «меньше», не вызывая спонтанных побуждений кошек и собак. ладить друг с другом (или вызывать другое неопределенное поведение). Вполне вероятно, что любая практическая реализация будет иметь некоторые эффективные средства для определения, перекрываются ли указатели, но стандарт не требует, чтобы такие средства существовали. Функция memmove (), написанная полностью на переносимом языке C, на многих платформах, вероятно, займет по крайней мере вдвое больше времени, чем memcpy (), также написанная полностью на переносимом языке C.
  2. Реализациям разрешено расширять функции в Строка при этом не изменит их семантику. На компиляторе 80x86, если регистры ESI и EDI не содержат ничего важного, memcpy (src, dest, 1234) может сгенерировать код:
      mov esi,[src]
      mov edi,[dest]
      mov ecx,1234/4 ; Compiler could notice it's a constant
      cld
      rep movsl
    
    Это займет столько же встроенного кода, но будет выполнено намного быстрее, чем:
      push [src]
      push [dest]
      push dword 1234
      call _memcpy
    
      ...
    
    _memcpy:
      push ebp
      mov  ebp,esp
      mov  ecx,[ebp+numbytes]
      test ecx,3   ; See if it's a multiple of four
      jz   multiple_of_four
    
    multiple_of_four:
      push esi ; Can't know if caller needs this value preserved
      push edi ; Can't know if caller needs this value preserved
      mov esi,[ebp+src]
      mov edi,[ebp+dest]
      rep movsl
      pop edi
      pop esi
      ret  
    

Многие компиляторы будут выполнять такие оптимизации с помощью memcpy (). Я не знаю ни одного, который будет делать это с memmove, хотя в некоторых случаях оптимизированная версия memcpy может предлагать ту же семантику, что и memmove. Например, если numbytes было 20:

; Assuming values in eax, ebx, ecx, edx, esi, and edi are not needed
  mov esi,[src]
  mov eax,[esi]
  mov ebx,[esi+4]
  mov ecx,[esi+8]
  mov edx,[esi+12]
  mov edi,[esi+16]
  mov esi,[dest]
  mov [esi],eax
  mov [esi+4],ebx
  mov [esi+8],ecx
  mov [esi+12],edx
  mov [esi+16],edi

Это будет работать правильно, даже если диапазоны адресов перекрываются, поскольку эффективно делает копию (в регистрах) всей области, которую нужно переместить, прежде чем какая-либо из них будет написано. Теоретически, компилятор мог бы обработать memmove (), увидев, что если его использовать как memcpy (), получится реализация, которая будет безопасна, даже если диапазоны адресов перекрываются, и вызовет _memmove в тех случаях, когда подстановка реализации memcpy () не будет безопасный. Я не знаю ни одного, кто делает такую ​​оптимизацию, хотя.

2
ответ дан 30 November 2019 в 16:04
поделиться

Ну, memmove приходится копировать в обратном направлении, когда источник и место назначения перекрываются, и источник перед пунктом назначения. Таким образом, некоторые реализации memmove просто копируют назад, когда источник находится перед адресатом, независимо от того, перекрываются ли эти две области.

Качественная реализация memmove может обнаруживать, перекрываются ли регионы, и делать прямое копирование, если они этого не делают. В таком случае единственные дополнительные издержки по сравнению с memcpy - это просто проверки перекрытия.

4
ответ дан 30 November 2019 в 16:04
поделиться