Процедури копіювання пам'яті можуть бути набагато складнішими та швидшими, ніж прості копії пам'яті за допомогою покажчиків, таких як:
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 ++), оскільки вам потрібно вручну розмістити навантаження та зберегти інструкції, щоб отримати максимальну вигоду від приховування та пропускної здатності затримки.
Як правило, цілий рядок даних кешу повинен бути скопійований за одну ітерацію розкрученого циклу.
Що підводить мене до наступного вдосконалення, додавання попереднього вибору. Це спеціальні вказівки, які повідомляють кеш-систему процесора завантажувати певні частини пам'яті в свій кеш. Оскільки між видачею інструкції та заповненням рядка кеш-пам’яті є затримка, інструкції потрібно розміщувати таким чином, щоб дані були доступні тоді, коли їх потрібно скопіювати, а не раніше / пізніше.
Це означає розміщувати вказівки щодо попереднього вибору на початку функції, а також всередині основного циклу копіювання. За допомогою інструкцій попередньої вибірки в середині циклу копіювання отримують дані, які будуть скопійовані за кілька ітерацій.
Я не можу згадати, але, можливо, також буде корисно попередньо встановити цільові адреси, а також вихідні.
Фактори
Основними факторами, що впливають на швидкість копіювання пам'яті, є:
- Затримка між процесором, його кешами та основною пам'яттю.
- Розмір і структура рядків кешу процесора.
- Інструкції щодо переміщення / копіювання пам'яті процесора (затримка, пропускна здатність, розмір реєстру тощо).
Отже, якщо ви хочете написати ефективний і швидкий режим роботи з пам'яттю, вам доведеться знати досить багато про процесор та архітектуру, для якої ви пишете. Досить сказати, що якщо ви не пишете на якійсь вбудованій платформі, було б набагато простіше просто використовувати вбудовану процедуру копіювання пам’яті.