Очень быстрый memcpy для обработки изображений? - PullRequest
31 голосов
/ 11 ноября 2009

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

Какой самый быстрый способ сделать это на платформе x86 с использованием GCC (где доступны SSE , SSE2, но НЕ SSE3)?

Я ожидаю, что решение будет либо в сборке, либо с использованием встроенных функций GCC?

Я нашел следующую ссылку, но понятия не имею, является ли это лучшим способом (автор также говорит, что в ней есть несколько ошибок): http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2006-02/msg00123.html

РЕДАКТИРОВАТЬ: обратите внимание, что копия необходима, я не могу обойтись без необходимости копировать данные (я мог бы объяснить, почему, но я избавлю вас от объяснения :))

Ответы [ 6 ]

40 голосов
/ 11 ноября 2009

Предоставлено Уильям Чан и 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) и исключить его обработку в специальном случае. Может быть возможно оптимизировать дальше!

6 голосов
/ 15 августа 2013

Этому вопросу уже четыре года, и я немного удивлен, что никто еще не упомянул пропускную способность памяти. CPU-Z сообщает, что на моей машине установлена ​​память PC3-10700. То, что RAM имеет пиковую пропускную способность (скорость передачи, пропускную способность и т. Д.), Составляет 10700 МБ / с. Процессор в моей машине - процессор i5-2430M с пиковой турбо частотой 3 ГГц.

Теоретически, с бесконечно быстрым процессором и моей оперативной памятью memcpy может иметь значение 5300 МБайт / с , то есть половину 10700, потому что memcpy должен считывать и затем записывать в RAM. (править: как указывал В.Одду, это упрощенное приближение).

С другой стороны, представьте, что у нас бесконечно быстрая оперативная память и реалистичный процессор, чего мы можем достичь? Давайте использовать мой процессор 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. Я предполагаю, что это быстрее, потому что он использует инструкцию предварительной выборки для повышения производительности кэша.

Таким образом, код, работающий на ЦП, не является узким местом, и его настройка приведет к небольшим улучшениям.

6 голосов
/ 12 ноября 2009

SSE-код, опубликованный hapalibashi, - это путь.

Если вам нужна еще большая производительность и не уклоняться от долгой и извилистой дороги написания драйвера устройства: все важные платформы в настоящее время имеют DMA-контроллер, способный выполнять копирование быстрее и параллельно чтобы код процессора мог сделать.

Это включает в себя написание драйвера, хотя. Ни одна большая операционная система, о которой я знаю, не предоставляет эту функциональность пользователю из-за угроз безопасности.

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

6 голосов
/ 12 ноября 2009

При любом уровне оптимизации -O1 или выше, GCC будет использовать встроенные определения для таких функций, как memcpy - с правильным параметром -march (-march=pentium4 для набора функций, который вы упомянули), он должен генерировать довольно оптимальный встроенный код для конкретной архитектуры.

Я бы проверил это и посмотрел, что получится.

3 голосов
/ 11 ноября 2009

Если специфично для процессоров Intel, вы можете воспользоваться IPP . Если вы знаете, что он будет работать с графическим процессором Nvidia, возможно, вы могли бы использовать CUDA - в обоих случаях может быть лучше выглядеть шире, чем оптимизировать memcpy () - они предоставляют возможности для улучшения вашего алгоритма на более высоком уровне. Однако оба они зависят от конкретного оборудования.

2 голосов
/ 11 ноября 2009

Если вы работаете в Windows, используйте API DirectX , который имеет определенные подпрограммы, оптимизированные для GPU , для обработки графики (насколько быстро это может быть? Ваш процессор не загружен . Делайте что-нибудь еще, пока это обрабатывает GPU).

Если вы хотите быть независимым от ОС, попробуйте OpenGL .

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

...