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

Существуют ли более быстрые альтернативы memcpy() в С++?

Ответы

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

Процессоры, так как 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, но для своих нужд получите свой собственный особый ресурс().

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

Кроме того, вам может потребоваться более тонкий контроль над фактическим алгоритмом распределения. В игровой индустрии исключительно важно, чтобы люди записывали свои собственные процедуры распределения памяти, независимо от того, сколько усилий было затрачено разработчиками инструментальных средств, в первую очередь на их разработку. Игры, которые я видел почти всегда, обычно используют Doug Lea Malloc.

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