Процедуры копирования памяти могут быть намного более сложными и быстрыми, чем простая копия памяти с помощью указателей, таких как:
void simple_memory_copy(void* dst, void* src, unsigned int bytes)
{
unsigned char* b_dst = (unsigned char*)dst;
unsigned char* b_src = (unsigned char*)src;
for (int i = 0; i < bytes; ++i)
*b_dst++ = *b_src++;
}
Улучшения
Первое улучшение, которое можно сделать, - это выровнять один из указателей по границе слова (под словом я имею в виду собственный целочисленный размер, обычно 32 бита / 4 байта, но может быть 64 бита / 8 байт на более новой архитектуре) и использовать размерные инструкции перемещения / копирования. Это требует использования байта для копирования байтов, пока указатель не будет выровнен.
void aligned_memory_copy(void* dst, void* src, unsigned int bytes)
{
unsigned char* b_dst = (unsigned char*)dst;
unsigned char* b_src = (unsigned char*)src;
// Copy bytes to align source pointer
while ((b_src & 0x3) != 0)
{
*b_dst++ = *b_src++;
bytes--;
}
unsigned int* w_dst = (unsigned int*)b_dst;
unsigned int* w_src = (unsigned int*)b_src;
while (bytes >= 4)
{
*w_dst++ = *w_src++;
bytes -= 4;
}
// Copy trailing bytes
if (bytes > 0)
{
b_dst = (unsigned char*)w_dst;
b_src = (unsigned char*)w_src;
while (bytes > 0)
{
*b_dst++ = *b_src++;
bytes--;
}
}
}
Различные архитектуры будут работать по-разному в зависимости от того, правильно ли выровнены указатель источника или назначения. Например, на процессоре XScale я получил лучшую производительность, совместив указатель назначения, а не указатель источника.
Для дальнейшего повышения производительности может быть выполнено некоторое развертывание цикла, так что большее количество регистров процессора загружается данными, и это означает, что инструкции загрузки / сохранения могут чередоваться, а их задержка скрывается дополнительными инструкциями (такими как подсчет циклов и т. Д.). ). Преимущество, которое это приносит, сильно зависит от процессора, так как задержки загрузки / сохранения команд могут быть совершенно разными.
На этом этапе код заканчивается написанием на ассемблере, а не на C (или C ++), поскольку вам необходимо вручную разместить инструкции загрузки и сохранения, чтобы получить максимальную выгоду от сокрытия задержки и пропускной способности.
Обычно всю строку данных кэша следует копировать за одну итерацию развернутого цикла.
Что подводит меня к следующему улучшению, добавляя предварительную выборку. Это специальные инструкции, которые говорят кеш-системе процессора загружать определенные части памяти в кеш. Поскольку существует задержка между выполнением инструкции и заполнением строки кэша, инструкции должны быть размещены таким образом, чтобы данные были доступны, когда они должны быть скопированы, и не раньше / позже.
Это означает размещение инструкций предварительной выборки в начале функции, а также внутри основного цикла копирования. С инструкциями предварительной выборки в середине цикла копирования, извлекающими данные, которые будут скопированы за несколько итераций.
Не помню, но может быть также полезно предварительно выбрать адреса назначения, а также адреса источника.
Факторы
Основные факторы, влияющие на скорость копирования памяти:
- Задержка между процессором, его кэшами и основной памятью.
- Размер и структура строк кэша процессора.
- Инструкции перемещения / копирования памяти процессора (задержка, пропускная способность, размер регистра и т. Д.).
Так что, если вы хотите написать эффективную и быструю подпрограмму управления памятью, вам нужно много знать о процессоре и архитектуре, для которой вы пишете. Достаточно сказать, что если вы не пишете на какой-либо встроенной платформе, было бы намного проще использовать встроенные процедуры копирования в память.