Какую настройку выполняет REP?

Указание справочного руководства по оптимизации архитектуры Intel® 64 и IA-32, §2.4.6 "Улучшение строки REP":

Характеристики производительности использования строки REP можно отнести к двум компонентам: накладные расходы на запуск и пропускную способность передачи данных.

[...]

Для строки REP более крупной передачи данных детализации, как значение ECX  увеличивается, стартовые служебные данные REP String показывают пошаговое увеличение:

  • Короткая строка (ECX <= 12): латентность REP MOVSW/MOVSD/MOVSQ составляет 20 циклов,
  • Быстрая строка (ECX >= 76: исключая REP MOVSB): реализация процессора обеспечивает аппаратное обеспечение оптимизации, перемещая как можно больше фрагментов данных по 16 байтам. Задержка латентности строки REP будет отличаться, если одна из 16-байтовых данных перенос охватывает границу линии кэша:

    • Без разделения: задержка состоит из стартовой стоимости около 40 циклов, и каждые 64 байта данных добавляют 4 цикла,
    • Разделение кэша: задержка состоит из запуска стоимость около 35 циклов, и каждые 64 байта данных добавляют 6 циклов.
  • Промежуточные длины строк: латентность REP MOVSW/MOVSD/MOVSQ имеет стартовая стоимость около 15 циклов плюс один цикл для каждой итерации перемещение данных в слове /dword/qword.

(акцент мой)

Больше не упоминается такая стоимость запуска. Что это? Что он делает и почему требуется больше времени?

Ответы

Ответ 1

Микрокод rep movs имеет несколько стратегий на выбор. Если src и dest не перекрываются, микрокод может передавать в 64b кусках больше. (Это так называемая функция "быстрых строк", введенная с P6 и иногда перенастроенная для более поздних процессоров, поддерживающих более широкие нагрузки/магазины). Но если dest - только один байт от src, rep movs должен получить тот же самый результат, который вы получите из этого множества отдельных инструкций movs.

Итак, микрокод должен проверять перекрытие и, возможно, для выравнивания (из src и dest отдельно или относительного выравнивания). Вероятно, он также выбирает что-то, основанное на значениях малых/средних/больших счетчиков.

В соответствии с комментариями Энди Глева на вопрос Почему сложнее memcpy/memset superior?, < сильные > условные ветки в микрокоде не подвержены ветвям-предсказаниям. Таким образом, существует значительный штраф в циклах запуска, если принятый по умолчанию путь не тот, который был фактически принят, даже для цикла, который использует тот же rep movs с тем же выравниванием и размером.

Он контролировал начальную реализацию строки rep в P6, поэтому он должен знать.:)

REP MOVS использует функцию протокола кеша, которая недоступна для обычный код. В основном, как SSE потоковые магазины, но в манере который совместим с обычными правилами упорядочивания памяти и т.д. // "большие накладные расходы для выбора и настройки правильного метода" главным образом из-за отсутствия прогноза ветвления микрокода. Я долго пожелал, чтобы я реализовал REP MOVS с помощью аппаратного конечного автомата а не микрокода, который мог бы полностью устранить накладные расходы.

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

Intel x86 имеет "быстрые строки" с Pentium Pro (P6) в 1996 году, который я контролировал. Быстрые строки P6 заняли REP MOVSB ​​и больше, и реализовали их с 64-разрядными нагрузками и хранилищами микрокода и без-RFO кэш-протокол. Они не нарушали порядок памяти, в отличие от ERMSB в IVB.

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

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

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

Так как Intel не публикует подробные данные, то мы можем делать только черные измерения циклов для различных размеров и выравнивания. К счастью, все, что нам нужно, чтобы сделать хороший выбор. руководство Intel и http://agner.org/optimize/, имеют хорошую информацию о как использовать rep movs.

Забавный факт: без ERMSB (IvB и выше): rep movsb оптимизирован для небольших экземпляров. Это займет больше времени, чем rep movsd или rep movsq для больших (более нескольких сотен байт, я думаю) копий, и даже после этого может не достичь той же пропускной способности.

Оптимальная последовательность для больших выровненных копий может быть rep movsq, а затем очищена чем-то другим (возможно, с некоторыми ветвями и movsd/movsw/movsb или rep movsb). Или неравнозначный SSE копия последних 16B).

Ответ 2

Цитата, которую вы указали, относится только к микроархитектуре Nehalem (процессоры Intel Core i5, i7 и Xeon, выпущенные в 2009 и 2010 годах), и Intel явно об этом говорит.

До Nehalem REP MOVSB ​​был еще медленнее. Intel молчала о том, что произошло в последующих микроархитектурах, но затем с микроархитектурой Ivy Bridge (процессоры выпущены в 2012 и 2013 годах) Intel представила Enhanced REP MOVSB ​​(нам все равно нужно проверить соответствующий бит CPUID), что позволило нам копировать память быстро.

Самые дешевые версии более поздних процессоров - Kaby Lake "Celeron" и "Pentium", выпущенные в 2017 году, не имеют AVX, которые могли быть использованы для быстрой копии памяти, но у них все еще есть расширенный REP MOVSB. Именно поэтому REP MOVSB ​​очень полезен для процессоров, выпущенных с 2013 года.

Удивительно, но процессоры Nehalem имели довольно быструю реализацию REP MOVSD/MOVSQ (но не REP MOVSW/MOVSB) для очень больших блоков - всего 4 цикла для копирования каждого последующего 64 байта данных (если данные выровнены по линии кэша границы) после того, как мы заплатили затраты на запуск 40 циклов - это отлично, когда мы копируем 256 байт и более, и вам не нужно использовать регистры XMM!

Таким образом, на микроархитектуре Nehalem REP MOVSB ​​/MOVSW практически бесполезна, но REP MOVSD/MOVSQ превосходна, когда нам нужно скопировать более 256 байт данных и данные выравниваются с границами строки кэша.

В предыдущих микроархитектурах Intel (до 2008 года) затраты на запуск еще выше.

Вот тесты REP MOVS *, когда источник и адресат были в кеше L1, блоков, достаточно больших, чтобы не пострадать от затрат на запуск, но не настолько больших, чтобы превысить размер кеша L1. Источник: http://users.atw.hu/instlatx64/

Yonah (2006-2008)

    REP MOVSB 10.91 B/c
    REP MOVSW 10.85 B/c
    REP MOVSD 11.05 B/c

Nehalem (2009-2010)

    REP MOVSB 25.32 B/c
    REP MOVSW 19.72 B/c
    REP MOVSD 27.56 B/c
    REP MOVSQ 27.54 B/c

Westmere (2010-2011)

    REP MOVSB 21.14 B/c
    REP MOVSW 19.11 B/c
    REP MOVSD 24.27 B/c

Ivy Bridge (2012-2013) - с расширенным REP MOVSB ​​

    REP MOVSB 28.72 B/c
    REP MOVSW 19.40 B/c
    REP MOVSD 27.96 B/c
    REP MOVSQ 27.89 B/c

SkyLake (2015-2016) - с расширенным REP MOVSB ​​

    REP MOVSB 57.59 B/c
    REP MOVSW 58.20 B/c
    REP MOVSD 58.10 B/c
    REP MOVSQ 57.59 B/c

Озеро Каби (2016-2017 гг.) - с расширенным REP MOVSB ​​

    REP MOVSB 58.00 B/c
    REP MOVSW 57.69 B/c
    REP MOVSD 58.00 B/c
    REP MOVSQ 57.89 B/c

Как вы видите, реализация REP MOVS значительно отличается от одной микроархитектуры к другой.

Согласно Intel, на Nehalem затраты на запуск REP MOVSB ​​для строк более 9 байтов составляют 50 циклов, но для REP MOVSW/MOVSD/MOVSQ они составляют от 35 до 40 циклов, поэтому REP MOVSB ​​имеет большие затраты на запуск; тесты показали, что общая производительность хуже для REP MOVSW, а не REP MOVSB ​​на Nehalem и Westmere.

На Ivy Bridge, SkyLake и Kaby Lake результаты противоположны этим инструкциям: REP MOVSB ​​быстрее, чем REP MOVSW/MOVSD/MOVSQ, хотя и немного. На Ivy Bridge REP MOVSW по-прежнему отстает, но на SkyLake и Kaby Lake REP MOVSW не хуже REP MOVSD/MOVSQ.

Обратите внимание, что я представил результаты тестов как для SkyLake, так и для Kaby Lake, взятых из сайта instaltx64 только для подтверждения - эти архитектуры имеют одинаковые данные для каждой инструкции.

Вывод: вы можете использовать MOVSD/MOVSQ для очень больших блоков памяти, так как он дает достаточные результаты на всех микроархитектурах Intel от Yohan до Kaby Lake. Хотя, на архитектуре Yonan и ранее, SSE-копия может дать лучшие результаты, чем REP MOVSD, но, ради универсальности, REP MOVSD является предпочтительным. Кроме того, REP MOVS * может использовать разные алгоритмы для работы с кешем, что недоступно для обычных инструкций.

Что касается REP MOVSB ​​для очень маленьких строк (менее 9 байт или менее 4 байтов), я бы даже не рекомендовал его. На озере Каби одиночный MOVSB даже без REP - 4 цикла, на Йохане - 5 циклов. В зависимости от контекста вы можете сделать это лучше всего с помощью обычных MOV.

Затраты на запуск не увеличиваются с увеличением размера, как вы писали. Задержка общей инструкции для завершения всей последовательности байтов, которая увеличивается - это довольно обвиоус - больше байт, которые нужно скопировать, больше циклов, то есть общая латентность, а не только стоимость запуска. Intel не раскрывает стоимость запуска для небольших строк, она указала только для строки из 76 байтов и более для Nehalem. Например, возьмите эти данные о Nehalem:

  • Задержка для MOVSB ​​составляет 9 циклов, если ECX < 4. Таким образом, это означает, что для копирования любой строки требуется ровно 9 циклов, как только эта строка содержит 1 байт или 2 байта или 3 байта. Это не так уж плохо - например, если вам нужно скопировать хвост, и вы не хотите использовать orverlapping магазины. Всего 9 циклов, чтобы определить размер (между 1 и 3) и фактически скопировать данные - этого трудно добиться с помощью обычных инструкций и всего этого ветвления - и для 3-байтной копии, если вы не скопировали предыдущие данные, вы будете должны использовать 2 загрузки и 2 магазина (слово + байт), и поскольку у нас есть не более одного хранилища, мы не будем делать это намного быстрее с помощью обычных инструкций MOV.
  • Intel не говорит о том, что задержка имеет REP MOVSB, если ECX находится между 4 и 9
  • Короткая строка (ECX <= 12): латентность REP MOVSW/MOVSD/MOVSQ составляет около 20 циклов, чтобы скопировать всю строку, а не только стартовую стоимость в 20 циклов. Таким образом, для копирования всей строки <= 12 байтов требуется около 20 циклов, поэтому мы имеем более высокую скорость передачи по байту, чем с REP MOVSB ​​с ECX < 4.
  • ECX >= 76 с REP MOVSD/MOVSQ - да, здесь у нас есть стартовая стоимость 40 циклов, но это более чем разумно, так как позже мы будем копировать каждые 64 байта данных всего за 4 цикла. Я не инженер Intel, которому разрешено отвечать ПОЧЕМУ есть затраты на запуск, но я полагаю, что это связано с тем, что для этих строк используется REP MOVS * (согласно комментариям Энди Глева на ответ "Почему сложны memcpy/memset superior?" ответ Питера Кордеса), функция кеш-протокола, недоступная для обычного кода. И возникает объяснение по этой цитате: "Большие накладные расходы для выбора и настройки правильного метода в основном объясняются отсутствием предсказания ветвей микрокода". Также было интересно отметить, что Pentium Pro (P6) в 1996 году внедрил REP MOVS * с 64-битными нагрузками и хранилищами микрокодов и протоколом кэширования без RFO - они не нарушали порядок памяти, в отличие от ERMSB в Ivy Bridge.

Ответ 3

Просто из описания мне кажется, что существует оптимальный размер передачи 16 байт, поэтому, если вы переносите 79 байтов, которые составляют 4 * 16 + 15, чтобы не знать больше о выравнивании, что может означать, что есть стоимость для 15 байтов либо спереди, либо в конце (или расщеплении), а 4 16-байтовые передачи быстрее, чем фракции 16. Вид, как высокая передача в вашем автомобиле, и переход от передач к высокой передаче.

Посмотрите на оптимизированную memcpy в glibc или gcc или в других местах. Они передают до нескольких отдельных байтов, тогда они могут совершать 16-битные передачи до тех пор, пока не получат оптимальный выровненный размер 32-разрядного выровненного, свернутого на 64-разрядный 128-разрядный адрес, тогда они могут выполнять многословные передачи для основная часть копии, затем они понижают передачу, может быть, одна 32-битная вещь, может быть, одна 16 может быть 1 байт, чтобы покрыть отсутствие выравнивания на бэкэнд.

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