Понимание реализации memcpy()

Я искал реализацию memcpy.c, я нашел другой код memcpy. Я не мог понять, почему они делают (((АДРЕС) s) | ((АДРЕС) d) | c) & (sizeof(UINT) - 1)

#if !defined(__MACHDEP_MEMFUNC)

#ifdef _MSC_VER
#pragma function(memcpy)
#undef __MEMFUNC_ARE_INLINED
#endif

#if !defined(__MEMFUNC_ARE_INLINED)
/* Copy C bytes from S to D.
 * Only works if non-overlapping, or if D < S.
 */
EXTERN_C void * __cdecl memcpy(void *d, const void *s, size_t c)
{
    if ((((ADDRESS) s) | ((ADDRESS) d) | c) & (sizeof(UINT) - 1)) {

        BYTE *pS = (BYTE *) s;
        BYTE *pD = (BYTE *) d;
        BYTE *pE = (BYTE *) (((ADDRESS) s) + c);

        while (pS != pE)
            *(pD++) = *(pS++);
    }
    else {
        UINT *pS = (UINT *) s;
        UINT *pD = (UINT *) d;
        UINT *pE = (UINT *) (BYTE *) (((ADDRESS) s) + c);

        while (pS != pE)
            *(pD++) = *(pS++);
    }
    return d;
}

#endif /* ! __MEMFUNC_ARE_INLINED */
#endif /* ! __MACHDEP_MEMFUNC */

person Angus    schedule 04.10.2013    source источник
comment
И именно поэтому вы не хотите писать memcpy самостоятельно :-) Однако настоящая красота видна только тогда, когда вы смотрите на сгенерированный машинный код.   -  person Kerrek SB    schedule 04.10.2013


Ответы (2)


Код проверяет, правильно ли выровнены адреса для UINT. Если это так, код копируется с использованием UINT объектов. Если нет, код копируется с использованием BYTE объектов.

Тест работает, сначала выполняя побитовое ИЛИ двух адресов. Любой бит, включенный в любом из адресов, будет включен и в результате. Затем тест выполняет побитовое И с sizeof(UINT) - 1. Ожидается, что размер UINT равен степени двойки. Тогда размер минус один включает все младшие биты. Например, если размер равен 4 или 8, то на единицу меньше в двоичном формате 112 или 1112. Если какой-либо из адресов не кратен размеру UINT, то он будет иметь один из этих битов, и тест укажет на это. (Обычно лучшее выравнивание для целочисленного объекта совпадает с его размером. Это не обязательно верно. Современная реализация этого кода должна использовать _Alignof(UINT) - 1 вместо размера.)

Копирование с объектами UINT выполняется быстрее, поскольку на аппаратном уровне одна инструкция загрузки или сохранения загружает или сохраняет все байты объекта UINT (вероятнее всего, четыре байта). Процессоры обычно копируют быстрее при использовании этих инструкций, чем при использовании в четыре раза большего количества однобайтовых инструкций загрузки или сохранения.

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

Более продвинутая реализация memcpy может содержать дополнительные функции, такие как:

  • Если один из адресов выровнен, а другой нет, используйте специальные инструкции загрузки-невыравнивания для загрузки нескольких байтов с одного адреса с обычными инструкциями сохранения по другому адресу.
  • Если процессор имеет инструкции Single Instruction Multiple Data, используйте эти инструкции для загрузки или сохранения большого количества байтов (часто 16, а возможно и больше) в одной инструкции.
person Eric Postpischil    schedule 04.10.2013

Код

((((ADDRESS) s) | ((ADDRESS) d) | c) & (sizeof(UINT) - 1))

Проверяет, не выровнены ли s, d или c по размеру UINT.

Например, если s = 0x7ff30b14, d = 0x7ffa81d8, c = 256 и sizeof(UINT) == 4, то:

s         = 0b1111111111100110000101100010100
d         = 0b1111111111110101000000111011000
c         = 0b0000000000000000000000100000000
s | d | c = 0b1111111111110111000101111011100
(s | d | c) & 3 =                        0b00

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

На многих архитектурах *(UINT *) ptr намного быстрее, если ptr правильно выровнено по ширине UINT. На некоторых архитектурах *(UINT *) ptr фактически вылетит из строя, если ptr выровнено неправильно.

person Dietrich Epp    schedule 04.10.2013
comment
Он также проверяет длину. - person Carl Norum; 04.10.2013
comment
Вау, поговорите о краткости, а не ясности. Любой код, который требует четырех наборов скобок, должен быть переосмыслен. - person Eric Andres; 04.10.2013
comment
@EricAndres Добро пожаловать в мир производительности. Такие вещи повсюду. :) - person Mysticial; 04.10.2013
comment
@ Mysticial Я уверен, что это так. Я не программист на C, поэтому я довольно легко отмахиваюсь от подобных вещей. Это определенно тот фрагмент кода, где микрооптимизация может быть оправдана. - person Eric Andres; 04.10.2013
comment
@EricAndres Но скобки, конечно, не имеют значения. В идеале вы должны видеть ((ADDRESS) s) и ((ADDRESS) d) как непрозрачные фрагменты S и D, поэтому у вас есть (S | D | c) & (sizeof(UNIT) - 1), что слишком неплохо, а затем у вас есть еще один набор из if ( … ) …. Само выражение не вложено особенно сложным образом. - person Joshua Taylor; 05.10.2013