x86 64 — самый быстрый способ превратить буфер изображения в смещение xy другого буфера в C ++ на архитектуре amd64

У меня есть буферы изображения произвольного размера, которые я копирую в буферы одинакового или большего размера со смещением x, y. Цветовое пространство — BGRA. Мой текущий метод копирования:

void render(guint8* src, guint8* dest, uint src_width, uint src_height, uint dest_x, uint dest_y, uint dest_buffer_width) {
bool use_single_memcpy = (dest_x == 0) && (dest_y == 0) && (dest_buffer_width == src_width);

if(use_single_memcpy) {
memcpy(dest, src, src_width * src_height * 4);
}
else {
dest += (dest_y * dest_buffer_width * 4);
for(uint i=0;i < src_height;i++) {
memcpy(dest + (dest_x * 4), src, src_width * 4);
dest += dest_buffer_width * 4;
src += src_width * 4;
}
}
}

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

2

Решение

Ваш use_single_memcpy тест слишком ограничительный. Небольшая перестановка позволяет удалить dest_y == 0 требование.

void render(guint8* src, guint8* dest,
uint src_width, uint src_height,
uint dest_x, uint dest_y,
uint dest_buffer_width)
{
bool use_single_memcpy = (dest_x == 0) && (dest_buffer_width == src_width);
dest_buffer_width <<= 2;
src_width <<= 2;
dest += (dest_y * dest_buffer_width);

if(use_single_memcpy) {
memcpy(dest, src, src_width * src_height);
}
else {
dest += (dest_x << 2);
while (src_height--) {
memcpy(dest, src, src_width);
dest += dest_buffer_width;
src += src_width;
}
}
}

Я также изменил цикл на обратный отсчет (который может быть более эффективным), удалил ненужную временную переменную и отменил повторные вычисления.

Вполне вероятно, что вы можете сделать еще лучше, используя встроенные функции SSE для копирования 16 байтов за раз вместо 4, но тогда вам придется беспокоиться о выравнивании и кратности 4 пикселя. Хорошая реализация memcpy уже должна делать эти вещи.

1

Другие решения

Один популярный ответ о StackOverflow, который использует сборку x86-64 и SSE, можно найти здесь: Очень быстрый memcpy для обработки изображений?. Если вы используете этот код, вам нужно убедиться, что ваши буферы выровнены по 128 битам. Основное объяснение этого кода:

  • Временные хранилища используются, поэтому ненужные записи в кеш можно обойти и записи в основную память могут быть объединены.
  • Чтение и запись чередуются только в очень больших фрагментах (много операций чтения, а затем много операций записи). Выполнение многократных последовательных чтений обычно имеет лучшую производительность, чем одиночные шаблоны чтения-записи-чтения-записи.
  • Используются гораздо большие регистры (128-битные регистры SSE).
  • Инструкции предварительной выборки включены в качестве подсказок для конвейерной обработки ЦП.

Я нашел этот документ — Оптимизация доступа ЦП к памяти на рабочих станциях SGI Visual 320 и 540 — которая, кажется, вдохновила приведенный выше код, но для более старых поколений процессоров; тем не менее, он содержит значительное количество дискуссий о том, как это работает.

Например, рассмотрим это обсуждение комбинирующих / не временных хранилищ:

Кэш процессоров Pentium II и III работает на 32-байтовой строке
блоки. Когда данные записываются или читаются из (кэшированной) памяти, все
строки кэша читаются или пишутся. Хотя это, как правило, повышает
Производительность процессора, при некоторых условиях это может привести к
ненужные выборки данных. В частности, рассмотрим случай, когда процессор
сделает 8-байтовый регистр MMX: movq, Так как это только один
четверть строки кэша, это будет рассматриваться как чтение-изменение-запись
работа с точки зрения кеша; целевая строка кэша будет
извлекается в кеш, тогда произойдет 8-байтовая запись. В случае
копия памяти, эти извлеченные данные не нужны; последующие магазины будут
перезаписать оставшуюся часть строки кэша. Чтение-изменение-запись
поведения можно избежать, если процессор собирает все записи в кеш
Затем строка делает одну запись в память. Объединение отдельных записей
запись в одну строку кэша называется объединением записи.
Объединение записи имеет место, когда память, в которую записывается
явно помечены как объединение записи (в отличие от кэширования или
uncached), или когда используется инструкция временного хранения MMX.
Память обычно помечается как объединяющая запись, только когда она используется в
кадровые буферы; память, выделенная VirtualAlloc либо не кэшируется, либо
кэшируется (но не пишется объединение). MMX movntps а также movntq
Временные инструкции по сохранению инструктируют ЦП записывать данные
непосредственно в память, минуя кэши L1 и L2. Как побочный эффект,
это также позволяет комбинировать записи, если целевая память кэшируется.

Если вы предпочитаете использовать memcpy, рассмотрите возможность изучения исходного кода для используемой вами реализации memcpy. Некоторые реализации memcpy ищут буферы с выравниванием по собственным словам, чтобы повысить производительность, используя полный размер регистра; другие автоматически скопируют столько, сколько возможно, используя выравнивание по собственным словам, а затем убирают остатки. Убедиться, что ваши буферы выровнены по 8 байтов, облегчит эти механизмы.

Некоторые реализации memcpy содержат тонну предварительных условных выражений, чтобы сделать их эффективными для небольших буферов (<512) — вы можете рассмотреть возможность копирования-вставки кода с разорванными фрагментами, поскольку вы, вероятно, не работаете с небольшими буферами.

1

По вопросам рекламы [email protected]