Как может команда rep stosb выполняться быстрее, чем эквивалентный цикл?

Как инструкция rep stosb может выполняться быстрее, чем этот код?

    Clear: mov byte [edi],AL       ; Write the value in AL to memory
           inc edi                 ; Bump EDI to next byte in the buffer
           dec ecx                 ; Decrement ECX by one position
           jnz Clear               ; And loop again until ECX is 0

Гарантированно ли это справедливо для всех современных процессоров? Всегда ли я предпочитаю использовать rep stosb вместо написания цикла вручную?


person Promod Sampath Elvitigala    schedule 02.11.2015    source источник
comment
Какого ответа вы ожидаете? rep stosb - это оптимизированная инструкция для этой цели.   -  person Jester    schedule 02.11.2015
comment
Привет, Шут, большое спасибо за оперативный ответ. Хорошо, я так скажу ... в CPU есть сумматор для добавления. Аналогично для инструкции rep stosb есть ли отдельная схема в ЦП?   -  person Promod Sampath Elvitigala    schedule 02.11.2015


Ответы (2)


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

(Обратите внимание, что это только применимо к stos и movs, а не repe cmpsb или repne scasb. К сожалению, они все еще медленные, например, в лучшем случае 2 цикла на байт по сравнению со Skylake, что жалко против AVX2 vpcmpeqb для реализации memcmp или memchr. См. инструкции https://agner.org/optimize/ таблицы и другие ссылки perf в вики-странице тегов x86.

См. Почему этот код в 6,5 раза медленнее с включенной оптимизацией? - пример того, как gcc неразумно встраивает repnz scasb или менее плохой скалярный битовый хак для strlen, который становится большим, и простая альтернатива SIMD.)


rep stos/movs имеет значительные накладные расходы при запуске, но хорошо увеличивается для больших memset / memcpy. (См. Руководства по оптимизации Intel / AMD для обсуждения того, когда использовать rep stos по сравнению с векторизованным циклом для небольших буферов.) Однако без функции ERMSB rep stosb настроен для средних и малых наборов памяти, и оптимально использовать rep stosd или rep stosq (если вы не собираетесь использовать петлю SIMD).

При пошаговом режиме с отладчиком rep stos выполняет только одну итерацию (один декремент ecx / rcx), поэтому реализация микрокода никогда не запускается. Не позволяйте этому обмануть вас, думая, что это все, что он может.

См. Какую настройку выполняет REP? для получения некоторых сведений о том, как Intel P6 / SnB -семейная реализация микроархитектур rep movs.

См. Enhanced REP MOVSB ​​для memcpy, чтобы узнать о пропускной способности памяти с rep movsb vs. цикл SSE или AVX, на процессорах Intel с функцией ERMSB. (Обратите особое внимание на то, что многоядерные процессоры Xeon не могут насыщать полосу пропускания DRAM только одним потоком из-за ограничений на количество одновременных промахов кеша, а также из-за протоколов хранения RFO и не-RFO.)


Современный процессор Intel должен запускать цикл asm в вопросе на одной итерации за такт, но ядро ​​семейства бульдозеров AMD, вероятно, не может даже управлять одним хранилищем за такт. (Узкое место на двух целочисленных портах выполнения, обрабатывающих инструкции inc / dec / branch. Если условие цикла было cmp / jcc на edi, ядро ​​AMD могло бы выполнить макрос-слияние сравнений-и-ветвлений.)


Одна из основных особенностей так называемых операций Fast String (rep movs и rep stos на процессорах семейства Intel P6 и SnB заключается в том, что они избегают трафика когерентности кэша чтения для владения при сохранении в ранее не кэшированную память. Так что это похоже на использование NT хранилища для записи целых строк кэша, но все же строго упорядоченные (функция ERMSB действительно использует слабо упорядоченные хранилища).

IDK, насколько хороша реализация AMD.


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

См. Ресурсы по оптимизации (особенно руководства Агнера Фога), связанные с x86 тег вики.


Intel IvyBridge, а затем и ERMSB, который позволяет rep stos[b/w/d/q] и rep movs[b/w/d/q] использовать хранилища со слабой упорядоченностью (например, movnt), позволяя хранилищам фиксировать кеширование вне очереди. Это преимущество, если не все места назначения уже загружены в кэш L1. Я считаю, из формулировки документов, что есть неявный барьер памяти в конце быстрой строки op, поэтому любое переупорядочение видно только между сохранениями, сделанными строкой op, а не между ней и другими хранилищами. т.е. вам все еще не нужно sfence после rep movs.

Таким образом, для больших выровненных буферов на Intel IvB и более поздних версиях rep stos реализация memset может превзойти любую другую реализацию. Тот, который использует movnt хранилищ (которые не оставляют данные в кеше), также должен быть близок к насыщению пропускной способности записи в основную память, но на практике может не совсем успевать. См. Комментарии для обсуждения этого, но я не смог найти никаких цифр.

Для небольших буферов разные подходы имеют очень разные накладные расходы. Благодаря микробенчмаркам циклы копирования SSE / AVX выглядят лучше, чем они есть, потому что выполнение копии с одинаковым размером и выравниванием каждый раз позволяет избежать неверных прогнозов ветвлений в коде запуска / очистки. IIRC, рекомендуется использовать векторизованный цикл для копий менее 128B на процессорах Intel (не rep movs). Порог может быть выше, в зависимости от процессора и окружающего кода.

В руководстве Intel по оптимизации также есть некоторые обсуждения накладных расходов для различных реализаций memcpy, и что rep movsb имеет больший штраф за несовпадение, чем movdqu.


См. Код оптимизированной реализации memset / memcpy для получения дополнительной информации о том, что делается на практике. (например, библиотека Агнера Фога).

person Peter Cordes    schedule 02.11.2015
comment
Отличный ответ. Некоторое время назад я действительно сравнивал различные подходы, см. Здесь: stackoverflow.com/questions/27940150/ К сожалению, pastebin удалил полный код. - person zx485; 03.11.2015
comment
Почему SNB ограничен одним байтом на два такта? Что еще более важно, я не уверен, что ваше утверждение о rep stos и movnt на IVB и более поздних версиях является правильным. Я бы сказал, что это наоборот. См. Мой ответ на whats-missing-sub-optimal-in-this-memcpy-implementation, а также прочтите комментарии к моей отвечать. - person Z boson; 03.11.2015
comment
@Zboson: моя ошибка, по какой-то причине мне показалось, что я вспомнил, как обнаружил, что SnB не может выполнять циклы с одной итерацией за такт. Может быть, мой тест страдал от какой-то странной вещи, связанной с выравниванием? Во всяком случае, я только что повторно протестировал, и в конце концов он может запускать крошечный цикл с одной итерацией за такт. - person Peter Cordes; 04.11.2015
comment
дерьмо, мне придется пойти и исправить все мои недавние ответы, где я включил эту ошибку о пропускной способности цикла SnB. - person Peter Cordes; 04.11.2015
comment
@PeterCordes, меня больше беспокоит ваше заявление об Intel IvB и более поздних версиях, репутация реализации memset может превзойти любую реализацию, кроме той, которая использует хранилища movnt. Я считаю это неверным. На IVB и более поздних версиях Intel есть то, что она называет Enhanced stosb. Это должно побить даже movnt. См. Раздел 3.7.6 руководства по оптимизации Intel. См. Ссылку в моем комментарии выше для получения более подробной информации. - person Z boson; 04.11.2015
comment
@Zboson: я имел в виду асимптотически, когда пропускная способность записи в основную память является узким местом, я почти уверен, что rep stos и movnt должны быть связаны друг с другом. Я не тестировал это и не помню никаких подробных тестов, которые я видел, так что поправьте меня, если я ошибаюсь. rep stos - гораздо лучший выбор для буферов меньшего размера, потому что он оставляет данные в кеше. На процессорах без включенной функции Fast String movnt memset должен выигрывать для больших буферов. - person Peter Cordes; 04.11.2015
comment
Из ссылки, на которую я указал вам, я написал: Так вы имеете в виду, что я могу лучше, чем movntdqa, для моего корпуса 1 ГБ? а затем Стивен Канон написал: Да, rep movsb значительно быстрее, чем movntdqa при потоковой передаче в память на Ivybridge и Haswell (но имейте в виду, что до Ivybridge это было медленно!) - person Z boson; 04.11.2015
comment
Так что я думаю, поскольку репутация IVB должна быть быстрее, чем movnt, даже для больших размеров. Но я не проверял это на практике. Я знаю только то, что написал Стивен Кэнон. Я согласен с тем, что до IVB лучше movnt. - person Z boson; 04.11.2015
comment
@Zboson: Я читал. Я нашел кое-что в собственном руководстве Intel по оптимизации, но в основном речь идет о размерах до 4k. Я предположил, не проверяя, что movntdqa может превысить пропускную способность записи в основную память. Если Стивен Кэнон говорит, что операции на быстрой струне быстрее, то я полагаю, что мое предположение было неверным. Однако обратите внимание, что я говорю о memset (stosb), а не о memcpy (movsb) смешанном чтении и записи, с потенциальными ложными зависимостями и т. Д. И т. Д. Intel говорит, что rep movsb, по-видимому, замедляет больше, чем SSE с смещенными адресами (я думаю, говоря о movdqu , поскольку movnt только выровнен.) - person Peter Cordes; 05.11.2015
comment
Intel также говорит об улучшенном stosb, поэтому я думаю, что это применимо как к memset, так и к memcpy. Но так как я никогда не тестировал это, я могу только догадываться, основываясь на заявлении Стивена Кэнона. Я не уверен, почему выравнивание имеет значение. Если вы устанавливаете 1 ГБ, то незначительно добавлять небольшой код, пока он не выровнен по 16 байт. - person Z boson; 05.11.2015
comment
@Zboson: да, это относится к stosb. Моя точка зрения заключалась в том, что цикл movnt memset будет легче насыщать полосу пропускания памяти, чем цикл memcpy, потому что здесь нет опасностей, связанных с ложной зависимостью. Фактически, никакого смешивания чтения и записи. Обновил свой ответ. Я не уверен, что выравнивание для rep stos/movs имеет большое значение для больших буферов или оно просто вызывает большие накладные расходы при запуске (что имеет значение для небольших буферов), что хуже, чем SSE с использованием невыровненных операций. - person Peter Cordes; 05.11.2015
comment
Примечание: быстрые строки и возможность работы со строками кэша существуют с 1990-х годов (в Pentium II, если не раньше), и не ограничиваются современными процессорами. Со временем Intel перестала оптимизировать его для каждого конкретного процессора, поэтому она отстала. ERMSB - это Intel, говорящая о том, что они наконец-то добрались до оптимизации быстрых строк в современных процессорах. - person Brendan; 08.05.2017
comment
@Brendan: Хм, ты прав насчет терминологии. Я не должен использовать быстрые строки в качестве синонима ERMSB в этом ответе. И да, Fast Strings (широкие магазины для реализации микрокода rep stos и rep movs (но не операций сравнения) восходят к PPro (первое ядро ​​P6, предок Pentium II). Энди Глю (ведущий архитектор Intel для быстрых строк) исправил меня по этому поводу только через неделю после того, как я опубликовал этот ответ, при обсуждении некоторые детали реп-мов: P - person Peter Cordes; 08.05.2017

Если ваш ЦП имеет бит CPUID ERMSB, то команды rep movsb и rep stosb выполняются иначе, чем на старых процессорах.

См. Справочное руководство по оптимизации, раздел 3.7.6. Расширенная работа REP MOVSB ​​и REP STOSB (ERMSB).

Как руководство, так и мои тесты показывают, что преимущества rep stosb по сравнению с обычным перемещением 32-битных регистров на 32-битном процессоре микроархитектуры Skylake проявляются только в больших блоках памяти, превышающих 128 байт. На меньших блоках, таких как 5 байтов, код, который вы показали (mov byte [edi],al; inc edi; dec ecx; jnz Clear), будет намного быстрее, поскольку затраты на запуск rep stosb очень высоки - около 35 циклов.

Чтобы получить преимущества rep stosb на новых процессорах с битом CPUID ERMSB, должны быть выполнены следующие условия:

  • буфер назначения должен быть выровнен по 16-байтовой границе;
  • если длина кратна 64, производительность может быть еще выше;
  • бит направления должен быть установлен вперед (установлен инструкцией cld).

Согласно руководству по оптимизации Intel, ERMSB начинает превосходить хранилище памяти через обычный регистр на Skylake, когда длина блока памяти составляет не менее 128 байт, потому что, как я писал, в ERMSB высокий внутренний запуск - около 35 циклов. ERMSB начинает явно превосходить другие методы, включая копирование и заполнение AVX, когда длина превышает 2048 байт. Однако это в основном относится к микроархитектуре Skylake и не обязательно относится к другим микроархитектурам ЦП.

На некоторых процессорах, но не на других, когда буфер назначения выровнен по 16 байтов, REP STOSB с использованием ERMSB может работать лучше, чем подходы SIMD, то есть при использовании регистров MMX или SSE. Когда целевой буфер не выровнен, производительность memset () при использовании ERMSB может снизиться примерно на 20% по сравнению с выровненным регистром для процессоров, основанных на кодовом имени микроархитектуры Intel Ivy Bridge. В отличие от этого, реализация REP STOSB на SIMD будет испытывать более незначительное ухудшение, если пункт назначения смещен, согласно руководству Intel по оптимизации.

Контрольные точки

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

Skylake

На процессоре Intel Core i5 6600, выпущенном в сентябре 2015 года и основанном на четырехъядерной микроархитектуре Skylake-S (базовая частота 3,30 ГГц, максимальная частота в турбо-режиме 3,90 ГГц) с кэш-памятью L1 4 x 32 КБ, кэш-памятью 4 x 256 КБ и кэш-памятью L3 6 МБ, Я мог получить ~ 100 ГБ / сек на REP STOSB с блоками 32 КБ.

Реализация memset (), использующая REP STOSB:

  • 1297920000 блоков по 16 байт: 13,6022 секунды 1455,9909 мегабайт / с
  • 0648960000 блоки по 32 байта: 06,7840 сек 2919,3058 мегабайт / сек
  • 1622400000 блоков по 64 байта: 16,9762 секунды 5833,0883 Мегабайт / с
  • 817587402 блока по 127 байтов: 8,5698 секунд 11554,8914 мегабайт / с
  • 811200000 блоков по 128 байт: 8,5197 сек. 11622,9306 Мегабайт / сек.
  • 804911628 блоков по 129 байтов: 9,1513 сек. 10820,6427 Мегабайт / сек.
  • 407190588 блоков по 255 байт: 5,4656 сек 18117,7029 Мегабайт / сек
  • 405600000 блоков по 256 байт: 5,0314 сек 19681,1544 Мегабайт / сек
  • 202800000 блоков по 512 байт: 2.7403 секунды 36135.8273 мегабайт / сек
  • 101400000 блоков по 1024 байта: 1,6704 сек 59279,5229 мегабайт / сек
  • 3168750 блоков по 32768 байт: 0,9525 секунды 103957,8488 мегабайт / с (!), То есть 10 ГБ / с
  • 2028000 блоков по 51200 байт: 1,5321 сек 64633,5697 мегабайт / сек
  • 413878 блоков по 250880 байт: 1,7737 сек. 55828,1341 Мегабайт / сек.
  • 19805 блоков по 5242880 байт: 2.6009 сек. 38073.0694 мегабайт / сек.

Реализация memset (), использующая MOVDQA [RCX],XMM0:

  • 1297920000 блоков по 16 байт: 3,5795 сек 5532,7798 Мегабайт / сек
  • 0648960000 блоки по 32 байта: 5,5538 сек 3565,9727 Мегабайт / сек
  • 1622400000 блоков по 64 байта: 15,7489 сек 6287,6436 Мегабайт / сек
  • 817587402 блока по 127 байтов: 9,6637 секунд 10246,9173 мегабайт / с
  • 811200000 блоков по 128 байт: 9,6236 сек 10289,6215 мегабайт / сек
  • 804911628 блоков по 129 байтов: 9,4852 секунды 10439,7473 мегабайт / с
  • 407190588 блоков по 255 байт: 6,6156 сек 14968,1754 мегабайт / сек
  • 405600000 блоков по 256 байт: 6,6437 сек 14904,9230 мегабайт / сек
  • 202800000 блоков по 512 байт: 5,0695 с 19533,2299 Мегабайт / с
  • 101400000 блоков по 1024 байта: 4,3506 сек 22761,0460 мегабайт / сек
  • 3168750 блоков по 32768 байт: 3,7269 секунды 26569,8145 мегабайт / с (!), То есть 26 ГБ / с
  • 2028000 блоков по 51200 байт: 4,0538 сек. 24427,4096 мегабайт / сек.
  • 413878 блоков по 250880 байт: 3,9936 сек. 24795,5548 Мегабайт / сек.
  • 19805 блоков по 5242880 байт: 4,5892 сек 21577,7860 Мегабайт / сек

Обратите внимание, что недостатком использования регистра XMM0 является то, что он составляет 128 бит (16 байтов), в то время как я мог бы использовать регистр YMM0 из 256 бит (32 байта). В любом случае stosb использует протокол, не относящийся к RFO. Intel x86 имеет быстрые строки со времен Pentium Pro (P6) в 1996 году. Быстрые строки P6 использовали REP MOVSB ​​и больше и реализовали их с загрузкой и сохранением 64-битного микрокода и протоколом кэширования без RFO. Они не нарушали порядок памяти, в отличие от ERMSB в Ivy Bridge. См. https://stackoverflow.com/a/33905887/6910868 для получения дополнительных сведений и источника.

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

Следовательно, для REP STOSB максимальная скорость составляла 103957 (сто три тысячи девятьсот пятьдесят семь) мегабайт в секунду, в то время как с MOVDQA [RCX], XMM0 она составляла всего 26569 (двадцать шесть тысяч пятьсот шестьдесят девять) двадцать шесть тысяч пятьсот шестьдесят девять.

Как видите, самая высокая производительность была на блоках размером 32 КБ, что соответствует 32 КБ кеш-памяти L1 процессора, на котором я проводил тесты.

Ледяное озеро

REP STOSB против магазина AVX-512

Я также провел тесты на процессоре Intel i7 1065G7, выпущенном в августе 2019 года (микроархитектура Ice Lake / Sunny Cove), базовая частота: 1,3 ГГц, максимальная частота Turbo 3,90 ГГц. Он поддерживает набор инструкций AVX512F. Он имеет 4 кэша инструкций L1 по 32 Кбайт и кэш данных 4 x 48 Кбайт, кэш второго уровня 4 x 512 Кбайт и кэш L3 8 Мбайт.

Выравнивание по назначению

На блоках 32K, обнуленных с помощью rep stosb, производительность составляла от 175231 МБ / с для адресата, смещенного на 1 байт (например, $ 7FF4FDCFFFFF), и быстро увеличивалась до 219464 МБ / с для выравнивания по 64 байтам (например, $ 7FF4FDCFFFC0), а затем постепенно повышалась до 222424 МБ / с для адресатов, выровненных по 256 байтам (выровненных по 256 байтам, т. Е. $ 7FF4FDCFFF00). После этого скорость не увеличивалась, даже если пункт назначения был выровнен на 32 КБ (например, 7FF4FDD00000), и все еще составлял 224850 МБ / с.

Разницы в скорости между rep stosb и rep stosq не было.

На буферах, выровненных по 32 КБ, скорость хранилища AVX-512 была точно такой же, как и для rep stosb, для циклов, начинающихся с 2-х хранилищ в цикле (227777 МБ / с), и не увеличивалась для циклов, развернутых для 4-х и даже 16-ти хранилищ. . Однако для цикла из 1 магазина скорость была немного ниже - 203145 МБ / с.

Однако, если целевой буфер был смещен всего на 1 байт, скорость хранилища AVX512 резко упала, то есть более чем в 2 раза, до 93811 МБ / с, в отличие от rep stosb на аналогичных буферах, которая давала 175231 МБ / с.

Размер буфера

  • Для блоков размером 1 КБ (1024 байта) AVX-512 (205039 КБ / с) был в 3 раза быстрее, чем rep stosb (71817 МБ / с)
  • А для блоков размером 512 байт производительность AVX-512 всегда была такой же, как и для блоков большего размера (194181 МБ / с), тогда как rep stosb упала до 38682 МБ / с. У этого типа блока разница была в 5 раз в пользу AVX-512.
  • Для блоков 2K (2048) у AVX-512 было 210696 МБ / с, а для rep stosb - 123207 МБ / с, почти вдвое медленнее. Опять же, не было разницы между rep stosb и rep stosq.
  • Для блоков 4K (4096) AVX-512 имел 225179 МБ / с, в то время как rep stosb: 180384 МБ / с, почти догоняя.
  • Для блоков 8K (8192) у AVX-512 было 222259 МБ / с, а у rep stosb: 194358 МБ / с, близко!
  • Для блоков размером 32 КБ (32768) у AVX-512 было 228432 МБ / с, rep stosb: 220515 МБ / с - наконец-то! Мы приближаемся к размеру кэша данных L0 моего процессора - 48 КБ! Это 220 Гигабайт в секунду!
  • Для блоков размером 64К (65536) у AVX-512 было 61405 МБ / с, rep stosb: 70395 МБ / с!
  • Такое огромное падение, когда у нас закончился кеш L0! И было очевидно, что с этого момента rep stosb начинает превосходить магазины AVX-512.
  • Теперь проверим размер кеша L1. Для блоков 512 КБ AVX-512 сделал 62907 МБ / с, а rep stosb сделал 70653 МБ / с. Вот где rep stosb начинает превосходить AVX-512. Разница пока не значительна, но чем больше буфер, тем больше разница.
  • Теперь возьмем огромный буфер размером 1 ГБ (1073741824). С AVX-512 скорость была 14319 МБ / с, rep stosb это как 27412 МБ / с, то есть вдвое быстрее, чем у AVX-512!

Я также пробовал использовать невременные инструкции для заполнения буферов 32K vmovntdq [rcx], zmm31, но производительность была примерно в 4 раза ниже, чем просто vmovdqa64 [rcx], zmm31. Как я могу воспользоваться преимуществами vmovntdq при заполнении буферов памяти? Должен ли быть какой-то определенный размер буфера, чтобы vmovntdq можно было воспользоваться преимуществом?

Кроме того, если буферы назначения выровнены по крайней мере на 64 бита, разница в производительности между vmovdqa64 и vmovdqu64 отсутствует. Поэтому у меня есть вопрос: а разве инструкция vmovdqa64 нужна только для отладки и безопасности, когда у нас vmovdqu64?

Рисунок 1. Скорость итеративного сохранения в тот же буфер, МБ / с

block     AVX   stosb
-----   -----  ------
 0.5K  194181   38682
   1K  205039  205039
   2K  210696  123207
   4K  225179  180384
   8K  222259  194358 
  32K  228432  220515 
  64K   61405   70395 
 512K   62907   70653 
   1G   14319   27412

Сводная информация о производительности многократной очистки одного и того же блока памяти в кэше

rep stosb на процессорах Ice Lake начинает превосходить хранилища AVX-512 только для многократной очистки одного и того же буфера памяти, превышающего размер кеш-памяти L0, т.е. 48 КБ на процессоре Intel i7 1065G7. А на небольших буферах памяти хранилища AVX-512 работают намного быстрее: для 1 КБ - в 3 раза, для 512 байт - в 5 раз.

Однако хранилища AVX-512 чувствительны к смещению буферов, а rep stosb не так чувствительны к смещению.

Таким образом, я понял, что rep stosb начинает превосходить хранилища AVX-512 только в буферах, которые превышают размер кэша данных L0, или 48 КБ, как в случае с процессором Intel i7 1065G7. Этот вывод справедлив по крайней мере для процессоров Ice Lake. Более ранняя рекомендация Intel о том, что копирование строк начинает превосходить копию AVX, начиная с буферов 2 КБ, также следует повторно протестировать для новых микроархитектур.

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

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

В этом сценарии между хранилищами rep stosb и AVX-512 нет большой разницы. Единственная разница заключается в том, что в 64-разрядной версии Windows 10 все данные не приближаются к пределу физической памяти. В следующих тестах общий размер данных был ниже 8 ГБ при общей физической памяти 16 ГБ. Когда я выделял около 12 ГБ, производительность упала примерно в 20 раз, независимо от метода. Windows начала сбрасывать очищенные страницы памяти и, вероятно, делала что-то еще, когда память была близка к заполнению. Размер кэша L3 8 МБ на процессоре i7 1065G7, похоже, не имел никакого значения для тестов. Важно только то, что вам не нужно приближаться к пределу физической памяти, и это зависит от вашей операционной системы, как она обрабатывает такие ситуации. Как я уже сказал, в Windows 10, если я использовал только половину физической памяти, все было в порядке, но если я использовал 3/4 доступной памяти, мой тест замедлился в 20 раз. Я даже не пробовал брать больше 3/4. Как я уже сказал, общий объем памяти составляет 16 ГБ. Доступный объем, по словам диспетчера задач, составил 12 ГБ.

Вот эталон скорости заполнения различных блоков памяти общим объемом 8 ГБ нулями (в МБ / с) на процессоре i7 1065G7 с общим объемом памяти 16 ГБ, в однопоточном режиме. Под AVX я имею в виду обычные магазины AVX-512, а под стосб я имею в виду реп стосб.

Рисунок 2. Скорость сохранения в несколько буферов, один раз каждый, МБ / с

block    AVX  stosb
-----   ----   ----
 0.5K   3641   2759
   1K   4709   3963
   2K  12133  13163
   4K   8239  10295
   8K   3534   4675
  16K   3396   3242
  32K   3738   3581
  64K   2953   3006
 128K   3150   2857
 256K   3773   3914
 512K   3204   3680
1024K   3897   4593
2048K   4379   3234
4096K   3568   4970
8192K   4477   5339

Вывод по очистке памяти внутри кеша

Если ваша память не существует в кэше, то производительность хранилищ AVX-512 и rep stosb примерно одинакова, когда вам нужно заполнить память нулями. Важен именно кеш, а не выбор между этими двумя методами.

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

Я обнулял 6-10 ГБ памяти, разделенных последовательностью буферов, выровненных по 64 байта. Никакие буферы не обнулялись дважды. Меньшие буферы имели некоторые накладные расходы, а у меня было только 16 ГБ физической памяти, поэтому я обнулял в целом меньше памяти с меньшими буферами. Я использовал различные тесты для буферов от 256 байт до 8 ГБ на буфер. Я взял 3 разных метода:

  1. Обычное сохранение AVX-512 на vmovdqa64 [rcx+imm], zmm31 (цикл из 4-х записей и затем сравнение счетчика);
  2. Невременное хранилище AVX-512 по vmovntdq [rcx+imm], zmm31 (тот же цикл из 4-х магазинов);
  3. rep stosb.

Для небольших буферов победителем был обычный магазин AVX-512. Затем, начиная с 4 КБ, вневременной запас вышел вперед, а rep stosb все еще отставал.

Затем из 256 КБ rep stosb превзошел AVX-512, но не вневременное хранилище, и с тех пор ситуация не изменилась. Победителем стал невременной магазин AVX-512, затем был rep stosb, а затем - обычный магазин AVX-512.

Рис. 3. Скорость сохранения в несколько буферов, один раз каждый, МБ / с, тремя разными способами: обычное хранилище AVX-512, временное хранилище AVX-512 и хранилище репликации.

Zeroized 6.67 GB: 27962026 blocks of 256 bytes for 2.90s, 2.30 GB/s by normal AVX-512 store
Zeroized 6.67 GB: 27962026 blocks of 256 bytes for 3.05s, 2.18 GB/s by nontemporal AVX-512 store
Zeroized 6.67 GB: 27962026 blocks of 256 bytes for 3.05s, 2.18 GB/s by rep stosb

Zeroized 8.00 GB: 16777216 blocks of 512 bytes for 3.06s, 2.62 GB/s by normal AVX-512 store
Zeroized 8.00 GB: 16777216 blocks of 512 bytes for 3.02s, 2.65 GB/s by nontemporal AVX-512 store
Zeroized 8.00 GB: 16777216 blocks of 512 bytes for 3.66s, 2.18 GB/s by rep stosb

Zeroized 8.89 GB: 9320675 blocks of 1 KB for 3.10s, 2.87 GB/s by normal AVX-512 store
Zeroized 8.89 GB: 9320675 blocks of 1 KB for 3.37s, 2.64 GB/s by nontemporal AVX-512 store
Zeroized 8.89 GB: 9320675 blocks of 1 KB for 4.85s, 1.83 GB/s by rep stosb

Zeroized 9.41 GB: 4934475 blocks of 2 KB for 3.45s, 2.73 GB/s by normal AVX-512 store
Zeroized 9.41 GB: 4934475 blocks of 2 KB for 3.79s, 2.48 GB/s by nontemporal AVX-512 store
Zeroized 9.41 GB: 4934475 blocks of 2 KB for 4.83s, 1.95 GB/s by rep stosb

Zeroized 9.70 GB: 2542002 blocks of 4 KB for 4.40s, 2.20 GB/s by normal AVX-512 store
Zeroized 9.70 GB: 2542002 blocks of 4 KB for 3.46s, 2.81 GB/s by nontemporal AVX-512 store
Zeroized 9.70 GB: 2542002 blocks of 4 KB for 4.40s, 2.20 GB/s by rep stosb

Zeroized 9.85 GB: 1290555 blocks of 8 KB for 3.24s, 3.04 GB/s by normal AVX-512 store
Zeroized 9.85 GB: 1290555 blocks of 8 KB for 2.65s, 3.71 GB/s by nontemporal AVX-512 store
Zeroized 9.85 GB: 1290555 blocks of 8 KB for 3.35s, 2.94 GB/s by rep stosb

Zeroized 9.92 GB: 650279 blocks of 16 KB for 3.37s, 2.94 GB/s by normal AVX-512 store
Zeroized 9.92 GB: 650279 blocks of 16 KB for 2.73s, 3.63 GB/s by nontemporal AVX-512 store
Zeroized 9.92 GB: 650279 blocks of 16 KB for 3.53s, 2.81 GB/s by rep stosb

Zeroized 9.96 GB: 326404 blocks of 32 KB for 3.19s, 3.12 GB/s by normal AVX-512 store
Zeroized 9.96 GB: 326404 blocks of 32 KB for 2.64s, 3.77 GB/s by nontemporal AVX-512 store
Zeroized 9.96 GB: 326404 blocks of 32 KB for 3.44s, 2.90 GB/s by rep stosb

Zeroized 9.98 GB: 163520 blocks of 64 KB for 3.08s, 3.24 GB/s by normal AVX-512 store
Zeroized 9.98 GB: 163520 blocks of 64 KB for 2.58s, 3.86 GB/s by nontemporal AVX-512 store
Zeroized 9.98 GB: 163520 blocks of 64 KB for 3.29s, 3.03 GB/s by rep stosb

Zeroized 9.99 GB: 81840 blocks of 128 KB for 3.22s, 3.10 GB/s by normal AVX-512 store
Zeroized 9.99 GB: 81840 blocks of 128 KB for 2.49s, 4.01 GB/s by nontemporal AVX-512 store
Zeroized 9.99 GB: 81840 blocks of 128 KB for 3.26s, 3.07 GB/s by rep stosb

Zeroized 10.00 GB: 40940 blocks of 256 KB for 2.52s, 3.97 GB/s by normal AVX-512 store
Zeroized 10.00 GB: 40940 blocks of 256 KB for 1.98s, 5.06 GB/s by nontemporal AVX-512 store
Zeroized 10.00 GB: 40940 blocks of 256 KB for 2.43s, 4.11 GB/s by rep stosb

Zeroized 10.00 GB: 20475 blocks of 512 KB for 2.15s, 4.65 GB/s by normal AVX-512 store
Zeroized 10.00 GB: 20475 blocks of 512 KB for 1.70s, 5.87 GB/s by nontemporal AVX-512 store
Zeroized 10.00 GB: 20475 blocks of 512 KB for 1.81s, 5.53 GB/s by rep stosb

Zeroized 10.00 GB: 10238 blocks of 1 MB for 2.18s, 4.59 GB/s by normal AVX-512 store
Zeroized 10.00 GB: 10238 blocks of 1 MB for 1.50s, 6.68 GB/s by nontemporal AVX-512 store
Zeroized 10.00 GB: 10238 blocks of 1 MB for 1.63s, 6.13 GB/s by rep stosb

Zeroized 10.00 GB: 5119 blocks of 2 MB for 2.02s, 4.96 GB/s by normal AVX-512 store
Zeroized 10.00 GB: 5119 blocks of 2 MB for 1.59s, 6.30 GB/s by nontemporal AVX-512 store
Zeroized 10.00 GB: 5119 blocks of 2 MB for 1.54s, 6.50 GB/s by rep stosb

Zeroized 10.00 GB: 2559 blocks of 4 MB for 1.90s, 5.26 GB/s by normal AVX-512 store
Zeroized 10.00 GB: 2559 blocks of 4 MB for 1.37s, 7.29 GB/s by nontemporal AVX-512 store
Zeroized 10.00 GB: 2559 blocks of 4 MB for 1.47s, 6.81 GB/s by rep stosb

Zeroized 9.99 GB: 1279 blocks of 8 MB for 2.04s, 4.90 GB/s by normal AVX-512 store
Zeroized 9.99 GB: 1279 blocks of 8 MB for 1.51s, 6.63 GB/s by nontemporal AVX-512 store
Zeroized 9.99 GB: 1279 blocks of 8 MB for 1.56s, 6.41 GB/s by rep stosb

Zeroized 9.98 GB: 639 blocks of 16 MB for 1.93s, 5.18 GB/s by normal AVX-512 store
Zeroized 9.98 GB: 639 blocks of 16 MB for 1.37s, 7.30 GB/s by nontemporal AVX-512 store
Zeroized 9.98 GB: 639 blocks of 16 MB for 1.45s, 6.89 GB/s by rep stosb

Zeroized 9.97 GB: 319 blocks of 32 MB for 1.95s, 5.11 GB/s by normal AVX-512 store
Zeroized 9.97 GB: 319 blocks of 32 MB for 1.41s, 7.06 GB/s by nontemporal AVX-512 store
Zeroized 9.97 GB: 319 blocks of 32 MB for 1.42s, 7.02 GB/s by rep stosb

Zeroized 9.94 GB: 159 blocks of 64 MB for 1.85s, 5.38 GB/s by normal AVX-512 store
Zeroized 9.94 GB: 159 blocks of 64 MB for 1.33s, 7.47 GB/s by nontemporal AVX-512 store
Zeroized 9.94 GB: 159 blocks of 64 MB for 1.40s, 7.09 GB/s by rep stosb

Zeroized 9.88 GB: 79 blocks of 128 MB for 1.99s, 4.96 GB/s by normal AVX-512 store
Zeroized 9.88 GB: 79 blocks of 128 MB for 1.42s, 6.97 GB/s by nontemporal AVX-512 store
Zeroized 9.88 GB: 79 blocks of 128 MB for 1.55s, 6.37 GB/s by rep stosb

Zeroized 9.75 GB: 39 blocks of 256 MB for 1.83s, 5.32 GB/s by normal AVX-512 store
Zeroized 9.75 GB: 39 blocks of 256 MB for 1.32s, 7.38 GB/s by nontemporal AVX-512 store
Zeroized 9.75 GB: 39 blocks of 256 MB for 1.64s, 5.93 GB/s by rep stosb

Zeroized 9.50 GB: 19 blocks of 512 MB for 1.89s, 5.02 GB/s by normal AVX-512 store
Zeroized 9.50 GB: 19 blocks of 512 MB for 1.31s, 7.27 GB/s by nontemporal AVX-512 store
Zeroized 9.50 GB: 19 blocks of 512 MB for 1.42s, 6.71 GB/s by rep stosb

Zeroized 9.00 GB: 9 blocks of 1 GB for 1.76s, 5.13 GB/s by normal AVX-512 store
Zeroized 9.00 GB: 9 blocks of 1 GB for 1.26s, 7.12 GB/s by nontemporal AVX-512 store
Zeroized 9.00 GB: 9 blocks of 1 GB for 1.29s, 7.00 GB/s by rep stosb

Zeroized 8.00 GB: 4 blocks of 2 GB for 1.48s, 5.42 GB/s by normal AVX-512 store
Zeroized 8.00 GB: 4 blocks of 2 GB for 1.07s, 7.49 GB/s by nontemporal AVX-512 store
Zeroized 8.00 GB: 4 blocks of 2 GB for 1.15s, 6.94 GB/s by rep stosb

Zeroized 8.00 GB: 2 blocks of 4 GB for 1.48s, 5.40 GB/s by normal AVX-512 store
Zeroized 8.00 GB: 2 blocks of 4 GB for 1.08s, 7.40 GB/s by nontemporal AVX-512 store
Zeroized 8.00 GB: 2 blocks of 4 GB for 1.14s, 7.00 GB/s by rep stosb

Zeroized 8.00 GB: 1 blocks of 8 GB for 1.50s, 5.35 GB/s by normal AVX-512 store
Zeroized 8.00 GB: 1 blocks of 8 GB for 1.07s, 7.47 GB/s by nontemporal AVX-512 store
Zeroized 8.00 GB: 1 blocks of 8 GB for 1.21s, 6.63 GB/s by rep stosb

Как избежать штрафов за переход AVX-SSE

Для всего кода AVX-512 я использовал регистр ZMM31, потому что регистры SSE имеют значения от 0 до 15, поэтому регистры AVX-512 с 16 по 31 не имеют своих аналогов SSE, поэтому не влекут за собой штраф за переход.

person Maxim Masiutin    schedule 07.05.2017
comment
чем другие методы - ну, по сравнению с тем одним другим методом, который вы тестировали, который, по-видимому, сохранял только 16 байтов каждый второй тактовый цикл. (~ 104 ГБ / с - я предполагаю, что 32B / c на ЦП с частотой ~ 3,3 ГГц.) Ручной цикл memset должен обеспечивать одно сохранение за цикл с попаданиями в кеш L1d, поэтому ваш тестовый цикл заставляет SSE выглядеть плохо. И если вы использовали AVX, вы должны иметь возможность сопоставить memset для блоков малого и среднего размера. (Попадания в кеш, когда мы не хотим использовать movnt или какой-либо другой протокол без RFO.) - person Peter Cordes; 16.05.2021
comment
@PeterCordes - Я добавил раздел про магазины AVX-512, просмотрите. - person Maxim Masiutin; 16.05.2021
comment
Как я могу воспользоваться преимуществами vmovntdq при заполнении буферов памяти? Должен ли быть какой-то определенный размер буфера, чтобы vmovntdq мог воспользоваться преимуществами? - да, в реальных реализациях memcpy / memset, таких как glibc, есть настраиваемая переменная, которую они сравнивают с размером, чтобы решить, использовать ли хранилища NT для огромных копий. - person Peter Cordes; 16.05.2021
comment
@PeterCordes Я не знаю, в какой именно настройке мне следует использовать vmovntdq, но во всех моих тестах с многократным заполнением буфера размером до 32 КБ он был примерно в 3 раза медленнее. Я не тестировал другие настройки. - person Maxim Masiutin; 16.05.2021
comment
Да, конечно, он медленнее для размеров, которые помещаются в кеш L1d! Это заставляет магазины полностью перейти на DRAM. Я думаю, что порог настройки для использования хранилищ NT обычно примерно равен размеру кэша L3. - person Peter Cordes; 16.05.2021
comment
@PeterCordes Спасибо! Мы обсуждали AVX-512 около трех лет назад, когда единственным вариантом был Knight Landing, которого у меня не было. Затем, в июне 2017 года, стал доступен первый основной процессор Core i9-7900X, и в июле 2017 года мне удалось получить возможность попробовать инструкции AVX-512. Теперь даже ноутбуки с i7 1065G7 имеют AVX-512, так что с тех пор все изменилось, и стратегии оптимизации также изменились. Так что я стараюсь не отставать от этих изменений. - person Maxim Masiutin; 16.05.2021
comment
Обратите внимание, что Ice Lake (i7 1065G7) имеет функцию быстрого короткого повторения, которая, по-видимому, ускоряет только rep movsb для 1..128 байт, к сожалению, не stosb. phoronix.com/. В остальном SKX - это серверный чип с межсетевым соединением (и меньшей пропускной способностью одноядерной памяти), а ICL - это клиентский чип. Тем не менее, я не думаю, что хранилища NT когда-либо были хороши для небольших буферов, особенно если что-то скоро будет читать буфер. (Избегание NT-хранилищ может привести к попаданию в кеш, если буфер достаточно мал). - person Peter Cordes; 16.05.2021
comment
@PeterCordes - Спасибо! Я сделаю больше тестов. Я также проверю быстрое короткое повторение. Кажется, что быстрое короткое повторение - хорошая альтернатива движениям AVX, особенно когда размер кода rep movsb микроскопичен по сравнению с движениями AVX, и, в среднем, он должен обеспечивать хорошую производительность! - person Maxim Masiutin; 16.05.2021
comment
Обратите внимание, что использование 512-битных регистров вообще накладывает штраф max-turbo, поэтому это не то, что вы хотите делать только для memset / memcpy в программе, которая в противном случае ничего / ничего не делает с 512-битными векторами. - person Peter Cordes; 17.05.2021
comment
Ваш последний набор тестов, показывающий скорость около 3 ГБ / с для буферов, которые помещаются в кэш L1d, намного медленнее, чем можно было бы ожидать. IDK, если во время этих тестов у вас возникали ошибки страниц (например, отключение и повторное сопоставление их?), Но ваши ранние тесты показали в 60 раз лучшую производительность (например, вы упомянули 219464 МБ / с = 219 ГБ / с, выровненный репозиторий на том же процессоре Ice Lake. Итак, очевидно, что вы делаете что-то не так, если эта производительность не проявляется ни при каком размере. - person Peter Cordes; 17.05.2021
comment
Кроме того, в последнем разделе упоминаются временные хранилища. Это странный способ описания обычных магазинов; вы знаете, что невременные средства не будут перечитываться в ближайшее время, верно? Временное в общем означает относящееся ко времени. В любом случае, я бы посоветовал вам избегать использования термина временный где угодно и использовать нормальный против невременного, поскольку я не уверен на 100%, имели ли вы в виду нормальный или временный был опечаткой для невременного - person Peter Cordes; 17.05.2021
comment
@PeterCordes: эти более ранние случаи повышения производительности в 60 раз были в одном и том же буфере снова и снова, в то время как в более поздних случаях каждый буфер использовался только один раз. Вот почему разница в производительности. - person Maxim Masiutin; 17.05.2021
comment
@PeterCordes - Спасибо! Я исправил временную опечатку - person Maxim Masiutin; 17.05.2021