Почему сложный memcpy / memset лучше? - PullRequest
22 голосов
/ 14 января 2012

При отладке я часто заходил в реализацию рукописных сборок memcpy и memset. Обычно они реализуются с использованием инструкций потоковой передачи, если они доступны, развернутых циклов, оптимизированного выравнивания и т. Д. Я также недавно столкнулся с этой «ошибкой» из-за оптимизации memcpy в glibc .

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

rep stos

и

rep movs

для распознавания как таковой, и самое быстрое заполнение и копирование, насколько это возможно, на их собственной архитектуре?

Ответы [ 6 ]

23 голосов
/ 14 января 2012

Стоимость.

Стоимость оптимизации memcpy в вашей C-библиотеке довольно минимальна, возможно, несколько недель времени разработчиков здесь и там. Вам придется создавать новую версию каждые несколько лет или около того, когда характеристики процессора изменятся настолько, чтобы оправдать переписывание. Например, GNU glibc и Apple libSystem оба имеют memcpy, который специально оптимизирован для SSE3.

Стоимость оптимизации аппаратного обеспечения намного выше. Мало того, что это дороже с точки зрения затрат разработчика (проектирование процессора намного сложнее, чем написание кода сборки пользовательского пространства), но это также увеличит количество транзисторов процессора. Это может иметь ряд негативных последствий:

  • Увеличенное энергопотребление
  • Увеличение стоимости единицы
  • Увеличенная задержка для определенных подсистем ЦП
  • Снижение максимальной тактовой частоты

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

Максим: Не делайте это аппаратно, если программное решение достаточно хорошо.

Примечание: Указанная вами ошибка на самом деле не является ошибкой в ​​glibc w.r.t. спецификация C. Это сложнее. По сути, люди glibc говорят, что memcpy ведет себя точно так, как указано в стандарте, а некоторые другие люди жалуются, что memcpy должен иметь псевдоним memmove.

Время для истории: Это напоминает мне жалобу, которая была у разработчика игр для Mac, когда он запускал свою игру на процессоре 603 вместо 601 (это из 1990-х). У 601 была аппаратная поддержка для невыровненных нагрузок и хранилищ с минимальным снижением производительности. 603 просто сгенерировал исключение; разгрузив ядро, я представляю, что модуль загрузки / хранения можно сделать намного проще, возможно, сделав процессор быстрее и дешевле в процессе. Наноядро Mac OS обработало исключение, выполнив требуемую операцию загрузки / сохранения и вернув управление процессу.

Но у этого разработчика была специальная процедура перетаскивания для записи пикселей на экран, которая выполняла выравнивание загрузки и сохранения. Производительность игры была хорошей на 601, но ужасной на 603. Большинство других разработчиков не заметили, использовали ли они функцию блицбола Apple, поскольку Apple могла бы просто переопределить ее для более новых процессоров.

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

В целом, тенденция, по-видимому, противоположна упомянутому виду аппаратных оптимизаций. В то время как в x86 легко писать memcpy в сборке, некоторые новые архитектуры переносят еще больше работы на программное обеспечение. Особого внимания заслуживают архитектуры VLIW: примерами являются Intel IA64 (Itanium), DSP TI TMS320C64x и Transmeta Efficeon. С VLIW программирование на ассемблере становится намного более сложным: вы должны явно выбирать, какие исполнительные блоки получают, какие команды и какие команды можно выполнять одновременно, что будет делать для вас современный x86 (если это не Atom). Так что писать memcpy внезапно становится намного, намного сложнее.

Эти архитектурные уловки позволяют вам вырезать огромную часть аппаратного обеспечения из ваших микропроцессоров, сохраняя при этом преимущества в производительности суперскалярного дизайна. Представьте себе, что у вас есть чип со следом ближе к Atom, но производительность ближе к Xeon. Я подозреваю, что сложность программирования этих устройств является основным фактором, препятствующим более широкому внедрению.

15 голосов
/ 07 февраля 2012

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

Обычно инструкция REP MOVS имеет большие издержки при выборе и настройка правильного метода. Следовательно, это не оптимально для небольшие блоки данных. Для больших блоков данных, это может быть довольно эффективен при соблюдении определенных условий выравнивания и т. д. Эти условия зависят от конкретного процессора (см. стр. 143). на Intel Nehalem и процессоры Sandy Bridge, это самый быстрый способ перемещения большие блоки данных , даже если данные не выровнены.

[Подсветка моя.] Ссылка: Agner Fog, Оптимизация подпрограмм при сборке язык Руководство по оптимизации для платформ x86. , стр. 156 (см. Также раздел 16.10, стр. 143) [версия 2011-06-08].

5 голосов
/ 14 января 2012

Общего назначения по сравнению со специализированным

Одним из факторов является то, что эти инструкции (префиксные / строковые инструкции) имеют общее назначение, поэтому они будут обрабатывать любое выравнивание, любое количество байтов илислова, и они будут иметь определенное поведение относительно кэша и / или состояния регистров и т. д., то есть четко определенные побочные эффекты, которые не могут быть изменены.

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

Рукописная сборка (либо в библиотеке, либо у одного разработчика может быть реализована самостоятельно) может опережать реализацию строковой инструкции для особых случаев, где она используется.Компиляторы часто имеют несколько реализаций memcpy для особых случаев, и тогда у разработчика может быть «очень особый» случай, когда они выполняют свои собственные.

Нет смысла заниматься этой специализацией на аппаратном уровне.Слишком большая сложность (= стоимость).

Закон убывающей доходности

Еще один способ думать об этом заключается в том, что когда появляются новые функции, например SSE, дизайнерывнесите архитектурные изменения, чтобы поддержать эти функции, например, интерфейс памяти с более широкой или более высокой пропускной способностью, изменения в конвейере, новые исполнительные блоки и т. д. На этом этапе разработчик вряд ли вернется к «устаревшей» части дизайна, чтобы попытатьсядовести его до скорости до последних функций.Это было бы неэффективно.Если вы следуете этой философии, вы можете спросить, зачем нам сначала нужна SIMD, может ли дизайнер просто заставить узкие инструкции работать так же быстро, как SIMD, в тех случаях, когда кто-то использует SIMD?Ответ обычно заключается в том, что оно того не стоит, потому что его проще добавить в новый исполнительный блок или инструкции.

1 голос
/ 14 января 2012

Когда-то rep movsb было оптимальным решением.

Оригинальный IBM PC имел процессор 8088 с 8-битной шиной данных и без кэшей.Тогда самой быстрой программой обычно была та, с наименьшим количеством байтов инструкций.Помогали специальные инструкции.

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

Intel и AMD хранят старые инструкции в основном для обратной совместимости.

1 голос
/ 14 января 2012

Если это не сломалось, не исправляйте это. Это не сломалось.

Основная проблема - это невыровненный доступ. Они переходят от плохого к действительно плохому, в зависимости от того, на какой архитектуре вы работаете. Во многом это связано с программистами, некоторые с компиляторами.

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

Гораздо проще сделать все это самостоятельно. Выровненный malloc, структуры, кратные размеру выравнивания. Ваш собственный memcpy, который выровнен и т. Д. При том, что это так просто, почему аппаратные ребята испортили бы свои проекты, компиляторы и пользователей? для этого нет бизнес-обоснования.

Другая причина в том, что кэши изменили картину. Ваш драм доступен только в фиксированном размере, 32 бита, 64 бита, что-то в этом роде, любые прямые обращения меньше, чем это огромный удар по производительности. Поместите кеш перед тем, что падение производительности идет вниз, любое чтение-изменение-запись происходит в кеше с возможностью изменения, позволяющим несколько модификаций для одного чтения и записи драм. Вы по-прежнему хотите уменьшить количество циклов памяти для кэша, да, и вы все еще можете увидеть выигрыш в производительности, сгладив его с помощью функции переключения передач (8-битная первая передача, 16-битная вторая передача, 32-битная третья передача, 64 крейсерская скорость, 32 бита вниз, 16 бит вниз, 8 бит вниз)

Я не могу говорить за Intel, но знаю, что такие люди, как ARM, сделали то, что вы просите

ldmia r0!,{r2,r3,r4,r5}
Например,

- это еще четыре 32-битных передачи, если ядро ​​использует 32-битный интерфейс. но для 64-битных интерфейсов, если выровнены по 64-битной границе, это становится 64-битной передачей с длиной два, один набор согласований между сторонами и два 64-битных слова перемещаются. Если он не выровнен по 64-битной границе, он становится тремя передачами: один 32-битный, один 64-битный, а затем 32-битный. Вы должны быть осторожны: если это аппаратные регистры, которые могут не работать в зависимости от конструкции логики регистра, если она поддерживает только 32-битные передачи, вы не можете использовать эту инструкцию для этого адресного пространства. Понятия не имею, почему вы все равно попробуете что-то подобное.

Последний комментарий ... мне больно, когда я это делаю ... ну, не делай этого. Не делайте ни единого шага в копиях памяти. следствием этого является то, что никто не сможет изменить конструкцию аппаратного обеспечения, чтобы упростить для пользователя пошаговую копию памяти, этот вариант использования настолько мал, что его не существует. Возьмите все компьютеры, использующие этот процессор, работающие на полной скорости днем ​​и ночью, по сравнению со всеми компьютерами, выполняющими пошаговое копирование мем-копий и другого оптимизированного по производительности кода. Это похоже на сравнение песчинки с шириной земли. Если вы один шаг, вам все равно придется пройти один шаг, каким бы ни было новое решение, если оно было. чтобы избежать огромных задержек прерываний, настроенный вручную memcpy по-прежнему будет начинаться с if-then-else (если слишком малая копия просто перейдет в небольшой набор развернутого кода или цикла копирования байтов), а затем перейдет в серию блочных копий в некоторая оптимальная скорость без ужасных задержек. Вам все равно придется пройти через это.

чтобы выполнить пошаговую отладку, вам нужно в любом случае скомпилировать запутанный, медленный, кодирующий код. Самый простой способ решения проблемы пошагового решения memcpy - это иметь компилятор и компоновщик, когда ему велено строить для отладки, сборки для и связывания против неоптимизированной memcpy или альтернативной неоптимизированной библиотеки в целом. GNU / GCC и llvm с открытым исходным кодом, вы можете заставить их делать все, что вы хотите.

1 голос
/ 14 января 2012

Во встраиваемых системах обычно имеется специализированное оборудование, которое выполняет memcpy / memset. Обычно это не специальная инструкция процессора, а периферийное устройство DMA, которое находится на шине памяти. Вы пишете пару регистров, чтобы сообщить адреса, а HW делает все остальное. Это на самом деле не требует специальных инструкций процессора, так как на самом деле это просто проблема с интерфейсом памяти, в которой нет необходимости задействовать процессор.

...