Вопрос

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

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

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

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

Редактировать:обратите внимание, что необходима копия, я не могу обойти необходимость копировать данные (я мог бы объяснить почему, но я избавлю вас от объяснений :))

Это было полезно?

Решение

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

Другие советы

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

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

Код SSE, опубликованный hapalibashi, — это то, что вам нужно.

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

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

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

Этому вопросу уже четыре года, и я немного удивлен, что никто еще не упомянул пропускную способность памяти.CPU-Z сообщает, что на моем компьютере установлена оперативная память PC3-10700.Что оперативная память имеет максимальную пропускную способность (она же скорость передачи, пропускная способность и т.д.) 10700 Мбайт / сек.Центральный процессор в моей машине - это процессор i5-2430M с максимальной частотой turbo 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 бита за цикл.

Хороший код, опубликованный хапалибаси, достигает 4200 Мбайт / сек на моем компьютере - примерно на 40% быстрее, чем реализация VS 2005.Я предполагаю, что это быстрее, потому что он использует инструкцию предварительной выборки для повышения производительности кэша.

Таким образом, код, выполняемый на центральном процессоре, не является узким местом, и настройка этого кода приведет лишь к небольшим улучшениям.

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top