У меня есть буферы изображения произвольного размера, которые я копирую в буферы одинакового или большего размера со смещением 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;
}
}
}
Он работает быстро, но мне было любопытно, смогу ли я что-нибудь сделать, чтобы улучшить его и получить несколько дополнительных миллисекунд. Если это связано с переходом на ассемблерный код, я бы предпочел этого избежать, но я готов добавить дополнительные библиотеки.
Ваш 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 уже должна делать эти вещи.
Один популярный ответ о StackOverflow, который использует сборку x86-64 и SSE, можно найти здесь: Очень быстрый memcpy для обработки изображений?. Если вы используете этот код, вам нужно убедиться, что ваши буферы выровнены по 128 битам. Основное объяснение этого кода:
Я нашел этот документ — Оптимизация доступа ЦП к памяти на рабочих станциях SGI Visual 320 и 540 — которая, кажется, вдохновила приведенный выше код, но для более старых поколений процессоров; тем не менее, он содержит значительное количество дискуссий о том, как это работает.
Например, рассмотрим это обсуждение комбинирующих / не временных хранилищ:
Кэш процессоров Pentium II и III работает на 32-байтовой строке
блоки. Когда данные записываются или читаются из (кэшированной) памяти, все
строки кэша читаются или пишутся. Хотя это, как правило, повышает
Производительность процессора, при некоторых условиях это может привести к
ненужные выборки данных. В частности, рассмотрим случай, когда процессор
сделает 8-байтовый регистр MMX:movq
, Так как это только один
четверть строки кэша, это будет рассматриваться как чтение-изменение-запись
работа с точки зрения кеша; целевая строка кэша будет
извлекается в кеш, тогда произойдет 8-байтовая запись. В случае
копия памяти, эти извлеченные данные не нужны; последующие магазины будут
перезаписать оставшуюся часть строки кэша. Чтение-изменение-запись
поведения можно избежать, если процессор собирает все записи в кеш
Затем строка делает одну запись в память. Объединение отдельных записей
запись в одну строку кэша называется объединением записи.
Объединение записи имеет место, когда память, в которую записывается
явно помечены как объединение записи (в отличие от кэширования или
uncached), или когда используется инструкция временного хранения MMX.
Память обычно помечается как объединяющая запись, только когда она используется в
кадровые буферы; память, выделеннаяVirtualAlloc
либо не кэшируется, либо
кэшируется (но не пишется объединение). MMXmovntps
а такжеmovntq
Временные инструкции по сохранению инструктируют ЦП записывать данные
непосредственно в память, минуя кэши L1 и L2. Как побочный эффект,
это также позволяет комбинировать записи, если целевая память кэшируется.
Если вы предпочитаете использовать memcpy, рассмотрите возможность изучения исходного кода для используемой вами реализации memcpy. Некоторые реализации memcpy ищут буферы с выравниванием по собственным словам, чтобы повысить производительность, используя полный размер регистра; другие автоматически скопируют столько, сколько возможно, используя выравнивание по собственным словам, а затем убирают остатки. Убедиться, что ваши буферы выровнены по 8 байтов, облегчит эти механизмы.
Некоторые реализации memcpy содержат тонну предварительных условных выражений, чтобы сделать их эффективными для небольших буферов (<512) — вы можете рассмотреть возможность копирования-вставки кода с разорванными фрагментами, поскольку вы, вероятно, не работаете с небольшими буферами.