оптимизированный memcpy - PullRequest
       32

оптимизированный memcpy

16 голосов
/ 31 июля 2009

Есть ли более быстрые альтернативы memcpy () в C ++?

Ответы [ 8 ]

19 голосов
/ 31 июля 2009

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

Во-вторых, да, есть лучшие альтернативы.

  • В C ++ используйте функцию std::copy. Он делает то же самое, но в некоторых случаях он 1) безопаснее и 2) потенциально быстрее. Это шаблон, означающий, что он может быть специализирован для определенных типов, что делает его потенциально более быстрым, чем общий memcpy C.
  • Или вы можете использовать свои превосходные знания вашей конкретной ситуации. Разработчики memcpy должны были написать его, чтобы он хорошо работал в в каждом случае. Если у вас есть конкретная информация о ситуации, в которой вы нуждаетесь, вы можете написать более быструю версию. Например, сколько памяти вам нужно скопировать? Как это выровнено? Это может позволить вам написать более эффективный memcpy для этого конкретного случая. Но это не будет так хорошо в большинстве других случаев (если это будет работать вообще)
18 голосов
/ 31 июля 2009

Вряд ли. Ваш компилятор / стандартная библиотека, вероятно, будет иметь очень эффективную и адаптированную реализацию memcpy. И memcpy, по сути, является самым низким API для копирования одной части памяти в другую.

Если вы хотите еще больше ускорить работу, найдите способ, который не требует копирования памяти.

9 голосов
/ 31 июля 2009

Эксперт по оптимизации Agner Fog опубликовал оптимизированные функции памяти: http://agner.org/optimize/#asmlib. Хотя он под GPL.

Некоторое время назад Агнер сказал, что эти функции должны заменить встроенные функции GCC, потому что они намного быстрее. Я не знаю, было ли это сделано с тех пор.

7 голосов
/ 31 июля 2009

Этот ответ на очень похожий вопрос (около memset()) также применим и здесь.

В основном это говорит о том, что компиляторы генерируют некоторый очень оптимальный код для memcpy() / memset() - и другой код в зависимости от природы объектов (размер, выравнивание и т. Д.).

И помните, только memcpy() POD в C ++.

3 голосов
/ 10 мая 2017

Чтобы найти или написать процедуру быстрого копирования памяти, мы должны понять, как работают процессоры.

Процессоры, начиная с Intel Pentium Pro, выполняют «выполнение вне очереди». Они могут выполнять много инструкций параллельно, если инструкции не имеют зависимостей. Но это только тот случай, когда инструкции работают только с регистрами. Если они работают с памятью, используются дополнительные модули ЦП, называемые «загрузочными блоками» (для чтения данных из памяти) и «хранилищами» (для записи данных в память). Большинство процессоров имеют два загрузочных модуля и один запоминающий модуль, то есть они могут параллельно выполнять две инструкции, которые читают из памяти, и одну инструкцию, которая записывает в память (опять же, если они не влияют друг на друга). Размер этих блоков обычно совпадает с максимальным размером регистров - если у процессора есть регистры XMM (SSE) - это 16 байтов, если у него есть регистры YMM (AVX) - это 32 байта и так далее. Все инструкции, которые читают или записывают память, переводятся в микрооперации (микрооперации), которые переходят в общий пул микроопераций и ждут там, пока модули загрузки и хранения смогут их обслуживать. Одна единица загрузки или хранения может обслуживать только одну микрооперацию за раз, независимо от размера данных, которые необходимо загрузить или сохранить, будь то 1 байт или 32 байта.

Таким образом, самая быстрая копия памяти будет перемещаться в регистры и из них с максимальным размером. Для процессоров с поддержкой AVX самым быстрым способом копирования памяти будет повторение следующей последовательности, развернутой в цикле:

vmovdqa     ymm0,ymmword ptr [rcx]
vmovdqa     ymm1,ymmword ptr [rcx+20h]
vmovdqa     ymmword ptr [rdx],ymm0
vmovdqa     ymmword ptr [rdx+20h],ymm1

Код Google, опубликованный ранее hplbsh, не очень хорош, потому что они используют все 8 регистров xmm для хранения данных до того, как они начнут записывать их обратно, хотя это и не нужно - так как у нас всего две единицы загрузки и одно хранилище Блок. Так что только два регистра дают лучшие результаты. Использование такого количества регистров никоим образом не повышает производительность.

Процедура копирования памяти также может использовать некоторые «продвинутые» методы, такие как «предварительная выборка», чтобы дать указание процессору заранее загружать память в кэш и «невременные записи» (если вы копируете очень большие фрагменты памяти и не необходимо, чтобы данные из выходного буфера были немедленно прочитаны), выровнены и не выровнены записи и т. д.

Современные процессоры, выпущенные с 2013 года, если у них есть бит ERMS в CPUID, имеют так называемый «расширенный rep movsb», поэтому для большой копии памяти может использоваться «rep movsb» - копия будет очень быстро, даже быстрее, чем с регистрами ymm, и он будет правильно работать с кешем. Однако затраты на запуск этой инструкции очень высоки - около 35 циклов, поэтому она рассчитывается только на больших блоках памяти.

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

Вы можете даже сохранить стандартную memcpy / memmove, но получите свою собственную специальную largememcpy () для своих нужд.

1 голос
/ 31 июля 2009

Я не уверен, что использование memcpy по умолчанию всегда лучший вариант. Большинство реализаций memcpy, на которые я смотрел, стараются выровнять данные в начале, а затем делать выровненные копии. Если данные уже выровнены или достаточно малы, то это трата времени.

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

Также вам может потребоваться более точный контроль над алгоритмом фактического распределения. В игровой индустрии люди обычно пишут свои собственные процедуры распределения памяти, независимо от того, сколько усилий было потрачено разработчиками инструментальной цепочки, прежде всего разрабатывающими ее. Игры, которые я видел, почти всегда имеют тенденцию использовать Malloc Дуга Леа *.

Хотя, в общем, вы бы тратили время на оптимизацию memcpy, поскольку, несомненно, в вашем приложении будет много более простых кусочков кода для ускорения.

1 голос
/ 31 июля 2009

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

1 голос
/ 31 июля 2009

В зависимости от того, что вы пытаетесь сделать ... если это достаточно большой memcpy, и вы редко пишете в копию, mmap с MMAP_PRIVATE для создания сопоставления копирования при записи может быть быстрее .

...