Очень быстрый memcpy для обработки изображений?
Я делаю обработку изображений в C, которая требует копирования больших объемов данных вокруг памяти - источник и место назначения никогда не пересекаются.
Какой самый быстрый способ сделать это на платформе x86 с использованием GCC (где доступны SSE, SSE2, но НЕ SSE3)?
Я ожидаю, что решение будет либо в сборке, либо с использованием встроенных функций GCC?
Я нашел следующую ссылку, но понятия не имею, является ли это лучшим способом (автор также говорит, что в ней есть несколько ошибок): http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2006-02/msg00123.html
РЕДАКТИРОВАТЬ: обратите внимание, что копия необходима, я не могу обойтись без необходимости копировать данные (я мог бы объяснить, почему, но я избавлю вас от объяснения:))
8 ответов
Предоставлено Уильямом Чаном и Google. На 30-70% быстрее, чем memcpy в Microsoft Visual Studio 2005.
void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size)
{
__asm
{
mov esi, src; //src pointer
mov edi, dest; //dest pointer
mov ebx, size; //ebx is our counter
shr ebx, 7; //divide by 128 (8 * 128bit registers)
loop_copy:
prefetchnta 128[ESI]; //SSE2 prefetch
prefetchnta 160[ESI];
prefetchnta 192[ESI];
prefetchnta 224[ESI];
movdqa xmm0, 0[ESI]; //move data from src to registers
movdqa xmm1, 16[ESI];
movdqa xmm2, 32[ESI];
movdqa xmm3, 48[ESI];
movdqa xmm4, 64[ESI];
movdqa xmm5, 80[ESI];
movdqa xmm6, 96[ESI];
movdqa xmm7, 112[ESI];
movntdq 0[EDI], xmm0; //move data from registers to dest
movntdq 16[EDI], xmm1;
movntdq 32[EDI], xmm2;
movntdq 48[EDI], xmm3;
movntdq 64[EDI], xmm4;
movntdq 80[EDI], xmm5;
movntdq 96[EDI], xmm6;
movntdq 112[EDI], xmm7;
add esi, 128;
add edi, 128;
dec ebx;
jnz loop_copy; //loop please
loop_copy_end:
}
}
Вы можете оптимизировать его в зависимости от конкретной ситуации и любых допущений, которые вы можете сделать.
Вы также можете проверить источник memcpy (memcpy.asm) и исключить его обработку в специальном случае. Может быть возможно оптимизировать дальше!
Код SSE, опубликованный hapalibashi, - это путь.
Если вам нужна еще большая производительность и не уклоняйтесь от долгой и извилистой дороги написания драйвера устройства: все важные платформы в настоящее время имеют DMA-контроллер, способный выполнять работу копирования быстрее и параллельно с кодом ЦП. может сделать.
Это включает в себя написание драйвера, хотя. Ни одна большая операционная система, о которой я знаю, не предоставляет эту функциональность пользователю из-за угроз безопасности.
Однако это может стоить (если вам нужна производительность), поскольку ни один код на Земле не может превзойти аппаратное обеспечение, предназначенное для такой работы.
Этому вопросу уже четыре года, и я немного удивлен, что никто еще не упомянул пропускную способность памяти. CPU-Z сообщает, что на моей машине установлена память PC3-10700. ОЗУ имеет пиковую пропускную способность (скорость передачи, пропускную способность и т. Д.) В 10700 МБ / с. Процессор в моей машине - процессор i5-2430M с пиковой турбо частотой 3 ГГц.
Теоретически, с бесконечно быстрым процессором и моей оперативной памятью memcpy может работать со скоростью 5300 МБ / с, то есть половиной 10700, потому что memcpy должен считывать и затем записывать в ОЗУ. (править: как указывал В.Одду, это упрощенное приближение).
С другой стороны, представьте, что у нас бесконечно быстрая оперативная память и реалистичный процессор, чего мы можем достичь? Давайте использовать мой процессор 3 ГГц в качестве примера. Если бы он мог выполнять 32-битное чтение и 32-битную запись каждый цикл, то он мог бы передавать 3e9 * 4 = 12000 МБ / с. Это кажется легко доступным для современного процессора. Мы уже видим, что код, работающий на ЦП, на самом деле не является узким местом. Это одна из причин того, что современные машины имеют кэши данных.
Мы можем измерить, что на самом деле может делать процессор, сравнив memcpy, когда мы знаем, что данные кэшируются. Делать это точно - неудобно. Я сделал простое приложение, которое записывало случайные числа в массив, записывало их в другой массив, а затем проверяло скопированные данные. Я прошел по коду в отладчике, чтобы убедиться, что умный компилятор не удалил копию. Изменение размера массива изменяет производительность кэша - маленькие массивы помещаются в кэш, а большие меньше. Я получил следующие результаты:
- Массивы 40 КБ: 16000 МБ / с
- Массивы 400 Кбайт: 11000 Мбайт / с
- Массивы 4000 Кбайт: 3100 Мбайт / с
Очевидно, что мой процессор может читать и записывать более 32 бит за цикл, поскольку 16000 - это больше, чем 12000, которые я рассчитал теоретически выше. Это означает, что процессор еще меньше, чем я думал. Я использовал Visual Studio 2005 и, войдя в стандартную реализацию memcpy, я вижу, что она использует инструкцию movqda на моей машине. Я предполагаю, что это может читать и записывать 64 бита за цикл.
Хороший код, опубликованный hapalibashi, достигает 4200 МБ / с на моей машине - примерно на 40% быстрее, чем реализация VS 2005. Я предполагаю, что это быстрее, потому что он использует инструкцию prefetch для повышения производительности кэша.
Таким образом, код, работающий на ЦП, не является узким местом, и настройка этого кода принесет лишь небольшие улучшения.
На любом уровне оптимизации -O1
или выше, GCC будет использовать встроенные определения для таких функций, как memcpy
- с правом -march
параметр (-march=pentium4
для набора функций, который вы упомянули) он должен генерировать довольно оптимальный специфичный для архитектуры встроенный код.
Я бы оценил это и посмотрел, что получится.
Если вы используете процессоры Intel, вы можете воспользоваться IPP. Если вы знаете, что он будет работать с графическим процессором Nvidia, возможно, вы могли бы использовать CUDA - в обоих случаях может быть лучше выглядеть шире, чем оптимизировать memcpy() - они предоставляют возможности для улучшения вашего алгоритма на более высоком уровне. Они оба, однако, зависят от конкретного оборудования.
Если вы работаете в Windows, используйте API-интерфейсы DirectX, в которых есть специальные подпрограммы, оптимизированные для графического процессора, для обработки графики (насколько это может быть быстрым? Ваш ЦП не загружен. Сделайте что-нибудь еще, пока его обрабатывает графический процессор).
Если вы хотите быть независимым от ОС, попробуйте OpenGL.
Не возитесь с ассемблером, потому что слишком велика вероятность того, что вам не удастся превзойти 10 лет + опытных разработчиков программного обеспечения для создания библиотек.
Старый вопрос, но на две вещи пока никто не указал:
Большинство компиляторов имеют свою собственную версию ; поскольку он четко определен и является частью стандарта C, компиляторам не обязательно использовать реализацию, поставляемую с системными библиотеками, они могут использовать свою собственную. Поскольку в вопросе упоминается «внутренность», ну, на самом деле большую часть времени, когда вы пишете в своем коде, вы на самом деле используете встроенную функцию компилятора, поскольку это то, что компилятор будет использовать внутри себя вместо реального вызова, поскольку тогда он может даже встроить его и, таким образом, устранить любые накладные расходы на вызов функций.
Большинство известных мне реализаций уже используют такие вещи, как SSE2, когда они доступны, по крайней мере, хорошие. В Visual Studio 2005, возможно, это не использовалось, но GCC использует это уже целую вечность. Конечно, то, что они используют, зависит от настроек сборки. Они будут использовать только инструкции, доступные для всех процессоров, на которых будет работать код, поэтому обязательно установите правильную архитектуру (например,
march
иmtune
), а также другие флаги (например, включение поддержки дополнительных наборов инструкций). Все это влияет на то, какой код генерирует компилятор в конечном двоичном файле.
Итак, как всегда, не думайте, что вы сможете перехитрить компилятор или систему (у которой могут быть разныеmemcpy
реализации доступны и для разных процессоров), эталонный тест доказывает это! Если тест не покажет, что ваш рукописный код работает быстрее в реальной жизни, лучше оставьте это компилятору и системе, поскольку они адаптируются к новым процессорам, и система может получать обновления, которые автоматически заставят ваш код работать быстрее в будущем, тогда как вам придется заново оптимизировать рукописный код самостоятельно, и он никогда не станет быстрее, если вы сами не выпустите обновление.
Если у вас есть доступ к движку DMA, ничто не будет быстрее.