Ваш цикл копирует только одно место. Я думаю, вы как-то хотели увеличить указатели.
Mysticial
13
Или вы можете просто исправить это для них, как я. И, кстати, ни один настоящий программист на C никогда не считает от 1до N, всегда от 0до N-1:-)
paxdiablo
6
@paxdiablo: конечно, если вы перебираете массивы. Но есть много случаев, когда цикл от 1 до N вполне подходит. Зависит от того, что вы делаете с данными - например, если вы показываете пользователю нумерованный список, начинающийся с 1, то начало с 1, вероятно, имеет больше смысла. В любом случае он игнорирует более серьезную проблему, которая заключается в использовании intв качестве счетчика, когда size_tвместо этого следует использовать беззнаковый тип, например .
Билли Онил
2
@paxdiablo Вы также можете считать от N до 1. На некоторых процессорах, которые исключают одну инструкцию сравнения, поскольку декремент устанавливает соответствующий бит для инструкции перехода, когда он достигает нуля.
onemasse
6
Я думаю, что посылка вопроса неверна. Современные компиляторы преобразуют это в memcpyили memmove(в зависимости от того, могут ли они определить, могут ли указатели быть псевдонимами).
Дэвид Шварц
Ответы:
120
Поскольку memcpy использует указатели слов вместо указателей байтов, также реализации memcpy часто записываются с инструкциями SIMD, которые позволяют перетасовывать 128 бит за раз.
Инструкции SIMD - это инструкции сборки, которые могут выполнять одну и ту же операцию с каждым элементом вектора длиной до 16 байт. Сюда входят инструкции по загрузке и сохранению.
Когда вы включаете GCC -O3, он будет использовать SIMD для цикла, по крайней мере, если он знает, pDestа pSrcне псевдоним.
Дитрих Эпп
В настоящее время я работаю над Xeon Phi с 64-битным (512-битным) SIMD-файлом, так что этот материал «до 16 байт» заставляет меня улыбаться. Кроме того, вы должны указать, какой ЦП вы нацеливаете для включения SIMD, например, с -march = native.
yakoudbz
Может мне стоит пересмотреть свой ответ. :)
onemasse
Это сильно устарело даже на момент публикации. Векторы AVX на x86 (поставлены в 2011 году) имеют длину 32 байта, а AVX-512 - длину 64 байта. Существуют архитектуры с 1024-битными или 2048-битными векторами или даже с переменной шириной вектора, например ARM
SVE
@phuclv, хотя инструкции могли быть доступны тогда, есть ли у вас доказательства того, что memcpy их использует? Обычно библиотекам требуется время, чтобы наверстать упущенное, и последние из них, которые я могу найти, используют SSSE3 и намного новее, чем 2011 год.
Пит Киркхэм,
81
Подпрограммы копирования памяти могут быть намного сложнее и быстрее, чем простое копирование памяти с помощью таких указателей, как:
voidsimple_memory_copy(void* dst, void* src, unsignedint bytes){
unsignedchar* b_dst = (unsignedchar*)dst;
unsignedchar* b_src = (unsignedchar*)src;
for (int i = 0; i < bytes; ++i)
*b_dst++ = *b_src++;
}
Улучшения
Первое улучшение, которое можно сделать, - это выровнять один из указателей на границе слова (под словом я имею в виду собственный целочисленный размер, обычно 32 бита / 4 байта, но может быть 64 бита / 8 байтов на новых архитектурах) и использовать перемещение размером слова / копировать инструкции. Это требует использования побайтного копирования до тех пор, пока указатель не будет выровнен.
Различные архитектуры будут работать по-разному в зависимости от того, правильно ли выровнен указатель источника или назначения. Например, на процессоре XScale я получил лучшую производительность за счет выравнивания указателя назначения, а не указателя источника.
Для дальнейшего повышения производительности можно выполнить развертывание некоторых циклов, чтобы большее количество регистров процессора было загружено данными, а это означает, что инструкции загрузки / сохранения могут чередоваться, а их задержка скрывается дополнительными инструкциями (такими как подсчет циклов и т. Д.). Преимущества, которые это приносит, сильно зависят от процессора, поскольку задержки инструкций загрузки / сохранения могут быть совершенно разными.
На этом этапе код пишется на ассемблере, а не на C (или C ++), поскольку вам нужно вручную разместить инструкции загрузки и сохранения, чтобы получить максимальную выгоду от скрытия задержки и пропускной способности.
Как правило, за одну итерацию развернутого цикла следует копировать всю строку данных кэша.
Это подводит меня к следующему усовершенствованию - добавлению упреждающей выборки. Это специальные инструкции, которые сообщают системе кэширования процессора загружать определенные части памяти в свой кэш. Поскольку существует задержка между выдачей инструкции и заполнением строки кэша, инструкции должны быть размещены таким образом, чтобы данные были доступны тогда, когда они должны быть скопированы, а не раньше / позже.
Это означает размещение инструкций предварительной выборки в начале функции, а также внутри основного цикла копирования. С инструкциями предварительной выборки в середине цикла копирования извлекаются данные, которые будут скопированы за несколько итераций.
Я не могу вспомнить, но также может быть полезно предварительно получить адреса назначения, а также исходные.
Факторы
Основными факторами, влияющими на скорость копирования памяти, являются:
Задержка между процессором, его кешами и основной памятью.
Размер и структура строк кэша процессора.
Инструкции по перемещению / копированию памяти процессора (задержка, пропускная способность, размер регистра и т. Д.).
Поэтому, если вы хотите написать эффективную и быструю процедуру управления памятью, вам нужно много знать о процессоре и архитектуре, для которых вы пишете. Достаточно сказать, что если вы не пишете на какой-то встроенной платформе, было бы намного проще просто использовать встроенные процедуры копирования в память.
Современные процессоры обнаруживают линейную модель доступа к памяти и самостоятельно начинают предварительную выборку. Я ожидаю, что инструкции предварительной выборки не будут иметь большого значения из-за этого.
maxy
@maxy На нескольких архитектурах, в которых я реализовал подпрограммы копирования памяти, добавление предварительной выборки заметно помогло. Хотя это может быть правдой, что чипы Intel / AMD текущего поколения выполняют предварительную выборку достаточно далеко вперед, существует множество старых чипов и других архитектур, которые этого не делают.
Daemin
кто-нибудь может объяснить "(b_src & 0x3)! = 0"? Я не могу этого понять, а также - он не компилируется (выдает ошибку: недопустимый оператор для двоичного &: unsigned char и int);
Maverick Meerkat
«(b_src & 0x3)! = 0» проверяет, равны ли 2 младших бита нулю. То есть, если указатель источника выровнен по кратному 4 байтам или нет. Ошибка компиляции возникает из-за того, что он обрабатывает 0x3 как байт, а не как in, вы можете исправить это, используя 0x00000003 или 0x3i (я думаю).
Daemin
b_src & 0x3не компилируется, потому что вам не разрешено выполнять побитовые арифметические операции с типами указателей. Вы должны (u)intptr_t
применить
18
memcpyможет копировать более одного байта одновременно в зависимости от архитектуры компьютера. Большинство современных компьютеров могут работать с 32 и более битами в одной инструкции процессора.
00026 * Для быстрого копирования оптимизируйте общий случай, когда оба указателя
00027 * и длина выровнены по словам, вместо этого копируется по одному
00028 * побайтно. В противном случае копируйте байтами.
На 386 (например), у которого не было встроенного кеша, это имело огромное значение. На большинстве современных процессоров операции чтения и записи будут происходить по одной строке кэша за раз, и шина к памяти обычно является узким местом, поэтому ожидайте улучшения на несколько процентов, а не вчетверо.
Джерри Коффин,
2
Я думаю, вам следует быть более точным, когда вы говорите «из источника». Конечно, это «исходный код» на некоторых архитектурах, но уж точно не на компьютерах с BSD или Windows. (И, черт возьми, даже между системами GNU эта функция часто сильно различается)
Билли Онил
@ Билли Онил: +1 абсолютно прав ... есть несколько способов снять шкуру с кошки. Это был всего лишь один пример. Исправлена! Спасибо за конструктивный комментарий.
Марк Байерс,
7
Вы можете реализовать memcpy()любой из следующих методов, некоторые из которых зависят от вашей архитектуры для повышения производительности, и все они будут намного быстрее, чем ваш код:
Используйте более крупные единицы, такие как 32-битные слова вместо байтов. Вы также можете (или, возможно, придется) иметь дело с выравниванием здесь. Вы не можете читать / писать 32-битное слово в нечетное место памяти, например, на некоторых платформах, а на других платформах вы платите огромную потерю производительности. Чтобы исправить это, адрес должен быть единицей, кратной 4. Вы можете увеличить это значение до 64 бит для 64-битных процессоров или даже выше, используя инструкции SIMD (одна инструкция, несколько данных) ( MMX , SSE и т. Д.)
Вы можете использовать специальные инструкции ЦП, которые ваш компилятор не сможет оптимизировать из C. Например, на 80386 вы можете использовать команду префикса «rep» + инструкция «movsb» для перемещения N байтов, продиктованных путем помещения N в счетчик. регистр. Хорошие компиляторы сделают это за вас, но, возможно, вы работаете на платформе, на которой отсутствует хороший компилятор. Обратите внимание, что этот пример, как правило, плохо демонстрирует скорость, но в сочетании с инструкциями по выравниванию + большим блоком он может быть быстрее, чем все остальное на определенных процессорах.
Развертывание цикла - ветки могут быть довольно дорогостоящими на некоторых процессорах, поэтому развертывание циклов может уменьшить количество ветвей. Это также хороший метод для объединения с инструкциями SIMD и модулями очень большого размера.
Например, http://www.agner.org/optimize/#asmlib имеет memcpyреализацию, которая превосходит все остальные (в очень малой степени). Если вы прочитаете исходный код, он будет полон тонны встроенного ассемблерного кода, который реализует все три вышеупомянутых метода, выбирая, какой из этих методов зависит от того, на каком процессоре вы работаете.
Обратите внимание, есть аналогичные оптимизации, которые можно сделать и для поиска байтов в буфере. strchr()и друзья часто будут быстрее, чем эквивалент, брошенный вами вручную. Это особенно верно для .NET и Java . Например, в .NET встроенная String.IndexOf()функция намного быстрее, чем даже строковый поиск Бойера – Мура , поскольку в ней используются описанные выше методы оптимизации.
В настоящее время большинство процессоров имеют хорошее предсказание ветвлений, что в типичных случаях должно сводить на нет преимущества развертывания цикла. Хороший оптимизирующий компилятор может иногда использовать его.
thomasrutter
5
Короткий ответ:
заполнение кеша
переводы словарного размера вместо байтовых, где это возможно
Обратите внимание, что приведенное выше не является, memcpyпоскольку оно намеренно не увеличивает toуказатель. Он реализует несколько иную операцию: запись в регистр с отображением в память. См. Статью в Википедии для подробностей.
Устройство Даффа или просто механизм начального перехода - хорошее применение для копирования первых 1..3 (или 1..7) байтов, чтобы указатели были выровнены по более удобной границе, где можно использовать большие инструкции перемещения памяти.
Daemin
@MarkByers: Код иллюстрирует несколько иную операцию ( *toотносится к регистру с отображением памяти и намеренно не увеличивается - см. Статью, на которую есть ссылка). Как я думал, я ясно дал понять, что мой ответ не пытается дать эффективный memcpy, он просто упоминает довольно любопытную технику.
NPE
@Daemin Согласен, как вы сказали, вы можете пропустить do {} while (), и переключатель будет переведен компилятором в таблицу переходов. Очень полезно, если вы хотите позаботиться об оставшихся данных. Следует упомянуть предупреждение об устройстве Даффа, очевидно, на более новых архитектурах (более новая x86) предсказание ветвлений настолько эффективно, что устройство Даффа на самом деле работает медленнее, чем простой цикл.
одномасса
1
О нет .. не устройство Даффа. Пожалуйста, не используйте устройство Даффа. Пожалуйста. Используйте PGO и позвольте мне компилятору развернуть цикл для вас там, где это имеет смысл.
Билли Онил
Нет, устройство Даффа определенно не используется ни в одной современной реализации.
gnasher729
3
Как и другие говорят, memcpy копирует блоки размером более 1 байта. Копирование кусками размером в слово происходит намного быстрее. Однако большинство реализаций идут дальше и перед зацикливанием запускают несколько инструкций MOV (word). Преимущество копирования, скажем, 8 блоков слов на цикл состоит в том, что сам цикл является дорогостоящим. Этот метод уменьшает количество условных переходов в 8 раз, оптимизируя копию для гигантских блоков.
Я не думаю, что это правда. Вы можете развернуть цикл, но вы не можете скопировать за одну инструкцию больше данных, чем можно адресовать за раз в целевой архитектуре. Кроме того , есть накладные расходы разворачивая петли тоже ...
Билли ONeal
@ Билли Онил: Я не думаю, что VoidStar имела в виду именно это. При наличии нескольких последовательных команд перемещения сокращаются накладные расходы на подсчет количества единиц.
wallyk
@ Билли Онил: Вы упускаете суть. Одно слово за раз - это как MOV, JMP, MOV, JMP и т. Д. Где, как вы можете сделать MOV MOV MOV MOV JMP. Я уже писал mempcy раньше, и я протестировал множество способов сделать это;)
VoidStar
@wallyk: Возможно. Но он говорит: «копируйте даже большие куски», что на самом деле невозможно. Если он имеет в виду разворачивание цикла, то он должен сказать: «большинство реализаций идут дальше и разворачивают цикл». Написанный ответ в лучшем случае вводит в заблуждение, в худшем - неверен.
Билли Онил
@VoidStar: Согласен --- теперь лучше. +1.
Билли Онил
2
Ответы великов, но если вы все еще хотите осуществить быстрые memcpyсебя, есть интересный блог о быстром тетсре, Fast тетсра в C .
Потому что, как и многие библиотечные подпрограммы, он оптимизирован для архитектуры, в которой вы работаете. Другие опубликовали различные методы, которые можно использовать.
Если у вас есть выбор, используйте библиотечные подпрограммы, а не катайтесь самостоятельно. Это разновидность DRY, которую я называю DRO (Не повторяйте другие). Кроме того, библиотечные процедуры с меньшей вероятностью ошибаются, чем ваша собственная реализация.
Я видел, как средства проверки доступа к памяти жалуются на чтение за пределами границ памяти или строковых буферов, которые не были кратны размеру слова. Это результат используемой оптимизации.
Вы можете посмотреть на реализацию memset, memcpy и memmove в MacOS.
Во время загрузки ОС определяет, на каком процессоре она работает. Он имеет встроенный специально оптимизированный код для каждого поддерживаемого процессора и во время загрузки сохраняет инструкцию jmp для нужного кода в фиксированном месте только для чтения.
Реализации C memset, memcpy и memmove - это всего лишь переход к этому фиксированному месту.
Реализации используют различный код в зависимости от выравнивания источника и назначения для memcpy и memmove. Очевидно, они используют все доступные векторные возможности. Они также используют варианты без кеширования при копировании больших объемов данных и имеют инструкции по минимизации ожидания таблиц страниц. Это не просто код ассемблера, это код ассемблера, написанный кем-то, кто очень хорошо знает архитектуру каждого процессора.
Intel также добавила инструкции ассемблера, которые могут ускорить строковые операции. Например, с инструкцией для поддержки strstr, которая выполняет сравнение 256 байт за один цикл.
Версия memset / memcpy / memmove от Apple с открытым исходным кодом - это просто общая версия, которая будет намного медленнее, чем реальная версия с использованием SIMD
1
доN
, всегда от0
доN-1
:-)int
в качестве счетчика, когдаsize_t
вместо этого следует использовать беззнаковый тип, например .memcpy
илиmemmove
(в зависимости от того, могут ли они определить, могут ли указатели быть псевдонимами).Ответы:
Поскольку memcpy использует указатели слов вместо указателей байтов, также реализации memcpy часто записываются с инструкциями SIMD, которые позволяют перетасовывать 128 бит за раз.
Инструкции SIMD - это инструкции сборки, которые могут выполнять одну и ту же операцию с каждым элементом вектора длиной до 16 байт. Сюда входят инструкции по загрузке и сохранению.
источник
-O3
, он будет использовать SIMD для цикла, по крайней мере, если он знает,pDest
аpSrc
не псевдоним.Подпрограммы копирования памяти могут быть намного сложнее и быстрее, чем простое копирование памяти с помощью таких указателей, как:
void simple_memory_copy(void* dst, void* src, unsigned int bytes) { unsigned char* b_dst = (unsigned char*)dst; unsigned char* b_src = (unsigned char*)src; for (int i = 0; i < bytes; ++i) *b_dst++ = *b_src++; }
Улучшения
Первое улучшение, которое можно сделать, - это выровнять один из указателей на границе слова (под словом я имею в виду собственный целочисленный размер, обычно 32 бита / 4 байта, но может быть 64 бита / 8 байтов на новых архитектурах) и использовать перемещение размером слова / копировать инструкции. Это требует использования побайтного копирования до тех пор, пока указатель не будет выровнен.
void aligned_memory_copy(void* dst, void* src, unsigned int bytes) { unsigned char* b_dst = (unsigned char*)dst; unsigned char* b_src = (unsigned char*)src; // Copy bytes to align source pointer while ((b_src & 0x3) != 0) { *b_dst++ = *b_src++; bytes--; } unsigned int* w_dst = (unsigned int*)b_dst; unsigned int* w_src = (unsigned int*)b_src; while (bytes >= 4) { *w_dst++ = *w_src++; bytes -= 4; } // Copy trailing bytes if (bytes > 0) { b_dst = (unsigned char*)w_dst; b_src = (unsigned char*)w_src; while (bytes > 0) { *b_dst++ = *b_src++; bytes--; } } }
Различные архитектуры будут работать по-разному в зависимости от того, правильно ли выровнен указатель источника или назначения. Например, на процессоре XScale я получил лучшую производительность за счет выравнивания указателя назначения, а не указателя источника.
Для дальнейшего повышения производительности можно выполнить развертывание некоторых циклов, чтобы большее количество регистров процессора было загружено данными, а это означает, что инструкции загрузки / сохранения могут чередоваться, а их задержка скрывается дополнительными инструкциями (такими как подсчет циклов и т. Д.). Преимущества, которые это приносит, сильно зависят от процессора, поскольку задержки инструкций загрузки / сохранения могут быть совершенно разными.
На этом этапе код пишется на ассемблере, а не на C (или C ++), поскольку вам нужно вручную разместить инструкции загрузки и сохранения, чтобы получить максимальную выгоду от скрытия задержки и пропускной способности.
Как правило, за одну итерацию развернутого цикла следует копировать всю строку данных кэша.
Это подводит меня к следующему усовершенствованию - добавлению упреждающей выборки. Это специальные инструкции, которые сообщают системе кэширования процессора загружать определенные части памяти в свой кэш. Поскольку существует задержка между выдачей инструкции и заполнением строки кэша, инструкции должны быть размещены таким образом, чтобы данные были доступны тогда, когда они должны быть скопированы, а не раньше / позже.
Это означает размещение инструкций предварительной выборки в начале функции, а также внутри основного цикла копирования. С инструкциями предварительной выборки в середине цикла копирования извлекаются данные, которые будут скопированы за несколько итераций.
Я не могу вспомнить, но также может быть полезно предварительно получить адреса назначения, а также исходные.
Факторы
Основными факторами, влияющими на скорость копирования памяти, являются:
Поэтому, если вы хотите написать эффективную и быструю процедуру управления памятью, вам нужно много знать о процессоре и архитектуре, для которых вы пишете. Достаточно сказать, что если вы не пишете на какой-то встроенной платформе, было бы намного проще просто использовать встроенные процедуры копирования в память.
источник
b_src & 0x3
не компилируется, потому что вам не разрешено выполнять побитовые арифметические операции с типами указателей. Вы должны(u)intptr_t
memcpy
может копировать более одного байта одновременно в зависимости от архитектуры компьютера. Большинство современных компьютеров могут работать с 32 и более битами в одной инструкции процессора.Из одного примера реализации :
источник
Вы можете реализовать
memcpy()
любой из следующих методов, некоторые из которых зависят от вашей архитектуры для повышения производительности, и все они будут намного быстрее, чем ваш код:Используйте более крупные единицы, такие как 32-битные слова вместо байтов. Вы также можете (или, возможно, придется) иметь дело с выравниванием здесь. Вы не можете читать / писать 32-битное слово в нечетное место памяти, например, на некоторых платформах, а на других платформах вы платите огромную потерю производительности. Чтобы исправить это, адрес должен быть единицей, кратной 4. Вы можете увеличить это значение до 64 бит для 64-битных процессоров или даже выше, используя инструкции SIMD (одна инструкция, несколько данных) ( MMX , SSE и т. Д.)
Вы можете использовать специальные инструкции ЦП, которые ваш компилятор не сможет оптимизировать из C. Например, на 80386 вы можете использовать команду префикса «rep» + инструкция «movsb» для перемещения N байтов, продиктованных путем помещения N в счетчик. регистр. Хорошие компиляторы сделают это за вас, но, возможно, вы работаете на платформе, на которой отсутствует хороший компилятор. Обратите внимание, что этот пример, как правило, плохо демонстрирует скорость, но в сочетании с инструкциями по выравниванию + большим блоком он может быть быстрее, чем все остальное на определенных процессорах.
Развертывание цикла - ветки могут быть довольно дорогостоящими на некоторых процессорах, поэтому развертывание циклов может уменьшить количество ветвей. Это также хороший метод для объединения с инструкциями SIMD и модулями очень большого размера.
Например, http://www.agner.org/optimize/#asmlib имеет
memcpy
реализацию, которая превосходит все остальные (в очень малой степени). Если вы прочитаете исходный код, он будет полон тонны встроенного ассемблерного кода, который реализует все три вышеупомянутых метода, выбирая, какой из этих методов зависит от того, на каком процессоре вы работаете.Обратите внимание, есть аналогичные оптимизации, которые можно сделать и для поиска байтов в буфере.
strchr()
и друзья часто будут быстрее, чем эквивалент, брошенный вами вручную. Это особенно верно для .NET и Java . Например, в .NET встроеннаяString.IndexOf()
функция намного быстрее, чем даже строковый поиск Бойера – Мура , поскольку в ней используются описанные выше методы оптимизации.источник
Короткий ответ:
источник
Я не знаю, используется ли он на самом деле в каких-либо реальных реализациях
memcpy
, но я думаю, что устройство Даффа заслуживает упоминания здесь.Из Википедии :
send(to, from, count) register short *to, *from; register count; { register n = (count + 7) / 8; switch(count % 8) { case 0: do { *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while(--n > 0); } }
Обратите внимание, что приведенное выше не является,
memcpy
поскольку оно намеренно не увеличиваетto
указатель. Он реализует несколько иную операцию: запись в регистр с отображением в память. См. Статью в Википедии для подробностей.источник
*to
относится к регистру с отображением памяти и намеренно не увеличивается - см. Статью, на которую есть ссылка). Как я думал, я ясно дал понять, что мой ответ не пытается дать эффективныйmemcpy
, он просто упоминает довольно любопытную технику.Как и другие говорят, memcpy копирует блоки размером более 1 байта. Копирование кусками размером в слово происходит намного быстрее. Однако большинство реализаций идут дальше и перед зацикливанием запускают несколько инструкций MOV (word). Преимущество копирования, скажем, 8 блоков слов на цикл состоит в том, что сам цикл является дорогостоящим. Этот метод уменьшает количество условных переходов в 8 раз, оптимизируя копию для гигантских блоков.
источник
Ответы великов, но если вы все еще хотите осуществить быстрые
memcpy
себя, есть интересный блог о быстром тетсре, Fast тетсра в C .void *memcpy(void* dest, const void* src, size_t count) { char* dst8 = (char*)dest; char* src8 = (char*)src; if (count & 1) { dst8[0] = src8[0]; dst8 += 1; src8 += 1; } count /= 2; while (count--) { dst8[0] = src8[0]; dst8[1] = src8[1]; dst8 += 2; src8 += 2; } return dest; }
Даже лучше, если оптимизировать доступ к памяти.
источник
Потому что, как и многие библиотечные подпрограммы, он оптимизирован для архитектуры, в которой вы работаете. Другие опубликовали различные методы, которые можно использовать.
Если у вас есть выбор, используйте библиотечные подпрограммы, а не катайтесь самостоятельно. Это разновидность DRY, которую я называю DRO (Не повторяйте другие). Кроме того, библиотечные процедуры с меньшей вероятностью ошибаются, чем ваша собственная реализация.
Я видел, как средства проверки доступа к памяти жалуются на чтение за пределами границ памяти или строковых буферов, которые не были кратны размеру слова. Это результат используемой оптимизации.
источник
Вы можете посмотреть на реализацию memset, memcpy и memmove в MacOS.
Во время загрузки ОС определяет, на каком процессоре она работает. Он имеет встроенный специально оптимизированный код для каждого поддерживаемого процессора и во время загрузки сохраняет инструкцию jmp для нужного кода в фиксированном месте только для чтения.
Реализации C memset, memcpy и memmove - это всего лишь переход к этому фиксированному месту.
Реализации используют различный код в зависимости от выравнивания источника и назначения для memcpy и memmove. Очевидно, они используют все доступные векторные возможности. Они также используют варианты без кеширования при копировании больших объемов данных и имеют инструкции по минимизации ожидания таблиц страниц. Это не просто код ассемблера, это код ассемблера, написанный кем-то, кто очень хорошо знает архитектуру каждого процессора.
Intel также добавила инструкции ассемблера, которые могут ускорить строковые операции. Например, с инструкцией для поддержки strstr, которая выполняет сравнение 256 байт за один цикл.
источник