Ниже приведен код, который я получил при поиске оптимизированной реализации memcpy.
Вот ссылка на сайт
void *memcpy(void *dst, void const *src, size_t len) {
long *plDst = (long *)dst;
long const *plSrc = (long const *)src;
if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC)) {
while (len >= 4) {
*plDst++ = *plSrc++;
len -= 4;
}
}
char *pcDst = (char *)plDst;
char const *pcDst = (char const *)plSrc;
while (len--) {
*pcDst++ = *pcSrc++;
}
return (dst);
}
Может кто-нибудь объяснить мне строчку ниже?
if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC))
Здесь они хотят проверить, совпадают ли адреса src и dst с границей 4 byte или нет. Но почему они используют !, поскольку он каждый раз будет вызывать состояние false?
Во-вторых, есть ли возможности для дополнительной оптимизации в приведенном выше коде?
Похоже на опечатку в статье - наверное, должно быть: if (!(src & ~0xFFFFFFFC) && !(dst & ~0xFFFFFFFC)).
Для проверки выравнивания вы можете использовать !(src&3) && !(dst&3). Условие во фрагменте проверяет что-то другое.
Также первый комментарий к этой статье замечает эту опечатку.
Спасибо за комментарий. Расклад теперь ясен. Можно ли еще немного оптимизировать код, чтобы он работал быстрее? Что, если бы мы не проверяли выравнивание по 4 байтам?
Если ваш процессор не разрешает 32-битный доступ для чтения к невыровненным адресам, вы можете получить SEGFAULT или неправильный контент. Или вы можете столкнуться с огромным штрафом в скорости доступа к памяти ....
Чтобы быть действительно оптимизированным для больших передач, он должен проверить, выровнено ли начало - скопировать байт за байтом невыровненный, скопировать исходную длину
@Abhi, вы можете расширить код, используя больше особых случаев (например, для 64-битной версии), но вы должны быть осторожны здесь, поскольку компилятор также может оптимизировать код.
Хорошо. Адрес должен быть выровнен, но требуется ли выравнивание как src, так и dest. В противном случае мы можем использовать метод, указанный @ P__J__





Эта статья, посвященная интересной теме, не дает правильных примеров. Опубликованный код упоминается как Исходный код новой библиотеки GNU. И проект GNU, и команда newlib будут удивлены, узнав об этом неожиданном заявлении о конвергенции! newlib не является проектом GNU, и большая часть его исходного кода не находится под лицензией GPL.
Эта реализация memcpyоптимизированный непереносима, неоптимальна и во многих аспектах неверна.
Тест if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC)) пытается определить, совпадают ли адреса src и dst на границах long. Это громоздко и непереносимо по нескольким причинам и, как вы заметили, совершенно неверно:
void * в int некрасиво и определяется реализацией. Указатели должны иметь вид (uintptr_t) для лучшей переносимости.0xFFFFFFFC предполагает, что тип long составляет 4 байта. Это может быть неверно, и действительно, тип long имеет длину 8 байт в 64-битных системах Linux и Mac.src & 0xFFFFFFFC не является проверкой на выравнивание и вряд ли будет 0, предполагаемый тест на выравнивание по 4-байтовым границам - это src & 3.Кроме того, код не может оптимизировать случай, когда src и dst имеют одинаковое выравнивание, но не выравниваются по границам long.
Другие возможные улучшения включают развертывание циклов, использование переключателя для малых значений len, объединение байтов, считанных из src, в long для записи в dst после его выравнивания по границам long ...
Вот улучшенная альтернатива:
#include <stdint.h>
void *memcpy(void *dst, void const *src, size_t len) {
unsigned char *pcDst = (unsigned char *)dst;
unsigned char const *pcSrc = (unsigned char const *)src;
if (len >= sizeof(long) * 2
&& ((uintptr_t)src & (sizeof(long) - 1)) == ((uintptr_t)dst & (sizeof(long) - 1))) {
while (((uintptr_t)pcSrc & (sizeof(long) - 1)) != 0) {
*pcDst++ = *pcSrc++;
len--;
}
long *plDst = (long *)pcDst;
long const *plSrc = (long const *)pcSrc;
/* manually unroll the loop */
while (len >= sizeof(long) * 4) {
plDst[0] = plSrc[0];
plDst[1] = plSrc[1];
plDst[2] = plSrc[2];
plDst[3] = plSrc[3];
plSrc += 4;
plDst += 4;
len -= sizeof(long) * 4;
}
while (len >= sizeof(long)) {
*plDst++ = *plSrc++;
len -= sizeof(long);
}
pcDst = (unsigned char *)plDst;
pcSrc = (unsigned char const *)plSrc;
}
while (len--) {
*pcDst++ = *pcSrc++;
}
return dst;
}
Обратите внимание, что приведение из void * не требуется в C, но требуется в C++.
Вот некоторые важные моменты, о которых следует помнить при попытке оптимизировать код для повышения скорости:
memcpy обычно оптимизируется в сборке или реализуется как встроенный в современных компиляторах.Я бы посоветовал проверить наличие len <= 8, прежде чем делать что-либо еще, и использовать оператор switch для обработки коротких операций копирования. Это позволит избежать необходимости проверять, можете ли вы копировать данные до точки, в которой источник и / или место назначения совпадают. Я бы посоветовал копировать данные до тех пор, пока место назначения не будет выровнено, а затем, если источник не выровнен, а копия «большая», иметь основной цикл примерно как x = *adjustedSrc++; *dest++ = (x << shift1) | (y >> shift2); y=x;, что позволяет избежать необходимости разбивать операции на более мелкие фрагменты.
@supercat: хорошие моменты. Что касается объединения невыровненных исходных байтов в long для сохранения в выровненный адрес назначения, легко упустить из виду проблему порядка байтов. В зависимости от архитектуры это может быть даже не быстрее. Фактически, на целевых объектах x86, где разрешены невыровненные операции чтения и записи, простой цикл чтения / записи через 64-битный тип может быть самым быстрым решением. Бенчмаркинг - это судья.
Не говоря уже о том, что оптимизация memcpy() очень зависит от процессора. На x86 часто лучше всего или, по крайней мере, достаточно разумно использовать memcpy-opcode.
Как вы думаете, почему условие может быть ложным каждый раз?