О масочных регистрах

Автор оригинала: Travis Downs
  • Перевод
В наборе команд AVX-512 были представлены восемь так называемых масочных регистров [1] – с k0 [2] по k7. Они пригодны для использования с большинством операций АЛУ и позволяют выполнять операции по маске над элементами векторов с обнулением или слиянием данных в регистре-приёмнике [3], тем самым ускоряя работу кода, что в наборе команд AVX2 и более ранних версиях потребовало бы дополнительных операций слияния.

Если написанного выше недостаточно, чтобы сделать из вас последователя культа масочных регистров, процитирую отрывок из статьи на Википедии, который, я надеюсь, поможет вам окончательно во всём разобраться:
Большинство команд AVX-512 могут использовать операнд-маску, соответствующий одному из 8 масочных регистров (k0–k7). Если масочный регистр используется в качестве маски операции, регистр k0 ведёт себя иначе, чем остальные масочные регистры: в этом случае он выполняет роль жёстко закодированной константы, указывающей, что с данной операцией маска не применяется. Однако в арифметических и логических операциях и при записи значения в масочные регистры k0 ведёт себя как обычный рабочий регистр. В большинстве команд масочные регистры используются в качестве маски, определяющей, какие элементы должны записываться в выходной регистр. Поведение операнда-маски зависит от флага: если он выставлен, все невыбранные элементы будут обнулены (режим «обнуления», zero), если нет – все невыбранные элементы сохраняют прежнее состояние (режим «слияния», merge). Режим слияния даёт тот же эффект, что команды слияния (blend instructions).

В общем масочные регистры [4] – важное нововведение, но о них редко вспоминают в отличие, скажем, от регистров общего назначения (eax, rsi и прочих) или SIMD-регистров (xmm0, ymm5 и т.д.). В презентациях Intel, где приводятся размеры ресурсов микроархитектуры, масочные регистры не упоминаются:

image

Насколько мне известно, информация о размере физического регистрового файла (physical register file, PRF) масочных регистров тоже никогда не публиковалась. Сейчас мы это исправим.
Я воспользовался доработанной версией инструмента для измерения размера буфера переупорядочивания команд (ROB), который был создан и описан Генри Вонгом [5] (далее просто Генри). С помощью этого инструмента он вычислял размер документированных и недокументированных структур внеочередного исполнения в предыдущих архитектурах. Если вы еще не прочитали заметку Генри, остановитесь и вернитесь к ней. А моя статья пока подождёт.

Ну что, прочитали? Для вредин привожу краткое изложение статьи Генри:

Краткое изложение методики измерения размера ROB


Между двумя командами чтения с кэш-промахом [6] вставляется некоторое количество балластных команд – точное их число будет зависеть от того, какой ресурс процессора мы хотим измерить. Если балластных команд не очень много, оба кэш-промаха будут обрабатываться параллельно, поэтому их задержки наложатся друг на друга и общее время выполнения составит примерно [7] столько же, сколько ушло бы на один кэш-промах.

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

Наконец, тест пишется так, что команды используют ровно одну единицу проверяемого ресурса – в этом случае вершина пика на графике будет указывать на его общий объем. Так, стандартные команды общего назначения, как правило, используют один физический регистр из PRF регистров общего назначения и потому идеально подходят для измерения объёма данного ресурса.

Размер физического регистрового файла масочных регистров


В данном тесте мы будем выполнять команды, записывающие значение в масочные регистры, чтобы узнать размер PRF этих регистров.

Начнём с серии команд kaddd k1, k2, k3 (показано 16 балластных команд):

mov    rcx,QWORD PTR [rcx]  ; первое чтение с кэш-промахом
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
kaddd  k1,k2,k3
mov    rdx,QWORD PTR [rdx]  ; второе чтение с кэш-промахом
lfence                      ; прекратить выдачу команд до тех пор, пока не  
                            ; завершится блок выше
; повторить данный блок ещё 16 раз

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

Именно это мы и наблюдаем:

image

Приблизим участок с подъёмом:

image

Как мы теперь видим, скачок не такой уж резкий: при количестве балластных команд от 130 до 134 скорость выполнения принимает промежуточные значения между минимальным и максимальным уровнями. Генри называет такое поведение неидеальным; я наблюдал его во многих из этих тестов, хотя и не во всех. Дело в том, что реализация аппаратной части не всегда разрешает полностью исчерпывать ресурс по мере приближения к лимиту [8] – в одних случаях это удаётся, в других до теоретического максимума не хватает всего нескольких команд.
В связи с этим нас интересует предпоследняя точка подъёма, в которой скорость всё ещё выше, чем в медленном режиме. Эта точка указывает на количество доступных нам единиц ресурса, а это означает, что физических регистров как минимум столько же. Как видим, в данном случае она находится на отметке 134.

Таким образом, в SKX имеется 134 физических регистра, способных хранить спекулятивные (полученные при упреждающем выполнении) значения масочных регистров. Генри предполагает, что ещё 8 используются для хранения актуального архитектурного состояния восьми масочных регистров, поэтому полный объём их PRF можно оценить в 142. Это несколько меньше, чем размер файлов регистров общего назначения (180) и SIMD-регистров (168), но всё равно достаточно много (см. таблицу размеров ресурсов внеочередного исполнения для других платформ).

Скажем, этот файл достаточно велик, чтобы на практике мы не успевали полностью занять его: сложно представить реальный код, в котором почти 60% [9] команд осуществляют запись [10] в масочные регистры – а именно столько их потребуется, чтобы исчерпать этот ресурс.

А это разные регистровые файлы?


Как вы, должно быть, заметили, до сих пор я по умолчанию предполагал, что PRF масочных регистров – это отдельный файл, не пересекающийся с файлами регистров других типов. Я считаю, что это весьма вероятно, исходя из принципа работы масочных регистров и из того факта, что они являются частью отдельного домена переименования регистров [11]. Ещё одним доводом в пользу моего предположения служит тот факт, что наблюдаемый размер PRF масочных регистров не совпадает с размером файла регистров общего назначения или файла SIMD-регистров. Собственно, мы можем взять и проверить это с помощью теста!

Этот тест похож на предыдущий, но теперь команды kaddd будут чередоваться с командами, которые задействуют либо регистры общего назначения, либо SIMD-регистры. Если масочные регистры объединены с первыми или вторыми в одном регистровом файле, скачок на графике должен указать на размер соответствующего PRF. Если регистровые файлы не объединены, мы натолкнёмся на какой-то другой лимит, который не будет равен размеру ни одного из двух регистровых файлов, а будет равен, например, размеру ROB.

В тесте 29 чередуются команды kaddd и скалярные команды add:

mov    rcx,QWORD PTR [rcx]
add    ebx,ebx
kaddd  k1,k2,k3
add    esi,esi
kaddd  k1,k2,k3
add    ebx,ebx
kaddd  k1,k2,k3
add    esi,esi
kaddd  k1,k2,k3
add    ebx,ebx
kaddd  k1,k2,k3
add    esi,esi
kaddd  k1,k2,k3
add    ebx,ebx
kaddd  k1,k2,k3
mov    rdx,QWORD PTR [rdx]
lfence

Смотрим график:

image

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

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

Чтобы выяснить это, воспользуемся тестом 35, который идентичен тесту 29 с той разницей, что здесь команды kaddd чередуются с командами vxorps:

mov    rcx,QWORD PTR [rcx]
vxorps ymm0,ymm0,ymm1
kaddd  k1,k2,k3
vxorps ymm2,ymm2,ymm3
kaddd  k1,k2,k3
vxorps ymm4,ymm4,ymm5
kaddd  k1,k2,k3
vxorps ymm6,ymm6,ymm7
kaddd  k1,k2,k3
vxorps ymm0,ymm0,ymm1
kaddd  k1,k2,k3
vxorps ymm2,ymm2,ymm3
kaddd  k1,k2,k3
vxorps ymm4,ymm4,ymm5
kaddd  k1,k2,k3
mov    rdx,QWORD PTR [rdx]
lfence

График:

image

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

Нерешённая загадка


Тем не менее в обоих тестах окончание пика приходится примерно на отметку 212 команд, тогда как размер ROB для данной микроархитектуры составляет 224. Может, это просто неидеальное поведение, которое мы уже наблюдали ранее? Что ж, проверим это: сравним результаты этих двух тестов с результатами теста 4, в котором в качестве балластных команд используются только команды nop: кроме ROB они не должны потреблять никакие другие ресурсы. Сравним графики теста 4 (nop) и теста 29 (чередуются kaddd и скалярные add):

image

В тесте 4 начало медленного режима приходится ровно на отметку 224 (изображения векторные, так что вы можете увеличить их и сами убедиться). Получается, что 212 (с этой отметки начинается медленный режим при чередовании масочных регистров с регистрами общего назначения или SIMD-регистрами) – это лимит какого-то другого ресурса. Вообще-то с таким же ограничением мы сталкиваемся, даже когда чередуем регистры общего назначения и SIMD-регистры – сравните тест 4 и тест 21 (в нём чередуются команды сложения в регистрах общего назначения и SIMD-команды vxorps):

image

В своей статье в разделе под таким же заголовком («Нерешённая загадка») Генри описывает тот же самый эффект, но ещё более выраженный:
Параллельное выполнение кода, в котором чередуются команды AVS или SSE архитектуры Sandy Bridge и целочисленные команды ограничивается примерно 147 командами, и это число не равно размеру ROB. Испробовав разные варианты (например, переставляя команды в разном порядке, изменяя соотношение AVX-команд и целочисленных команд, добавляя NOP-команды), я пришёл к выводу, что и команды SSE/AVX, и целочисленные команды расходуют регистры, располагающиеся в каком-то общем пуле регистров, так как размер окна команд не превышает 147, сколько бы команд каждого типа ни использовалось – при условии, что их целевые регистры не заканчиваются раньше.
За подробностями отсылаю вас к статье Генри. Мы наблюдаем похожий эффект, но менее выраженный: нам хотя бы удаётся занять 95% объёма ROB, но мы всё равно так и не исчерпываем его полностью. Возможно, тот загадочный общий пул регистров связан с механизмом их освобождения, например, PRRT-таблицей [12], которая отслеживает регистры, доступные для освобождения после завершения команды.

Напоследок поговорим ещё о некоторых особенностях масочных регистров и проверим, применимы ли к ним механизмы оптимизации, доступные регистрам общего назначения и SIMD-регистрам.

Замена копирования


К командам общего назначения или SIMD-командам может применяться так называемая замена копирования (move elimination). При этой оптимизации механизм переименования регистров позволяет не исполнять команды, копирующие значение из одного регистра в другой, например mov eax, edx или vmovdqu ymm1, ymm2, – вместо этого регистр-приёмник «просто» [13] переназначается в RAT на регистр-источник, что позволяет обойтись без привлечения АЛУ.

Проверим, применима ли замена копирования, скажем, к команде kmov k1, k2. Для начала посмотрим на график теста 28, где балластной командой служит kmovd k1, k2:
image
Этот график выглядит точно так же, как в рассмотренном ранее тесте 27 с командами kaddd. Поэтому разумно предположить, что заполняются именно физические регистры, если только мы случайно не исчерпали какой-то другой ресурс, используемый при замене копирования, который ведёт себя так же и имеет такой же размер [14].

Дополнительное подтверждение находим на сайте uops.info: там написано, что все варианты команды копирования kmov между масочными регистрами занимают одну микрооперацию, исполняемую на порте p0. Если бы имела место замена копирования, мы бы не наблюдали активности на портах.
Из этого я делаю вывод, что команды копирования, в которых задействованы масочные регистры [15], не заменяются.

Идиомы устранения зависимостей


Лучший способ обнулить регистр общего назначения в архитектуре x86 – это использовать идиому с исключающим ИЛИ (xor): xor reg, reg. Её действие основано на том, что сравнение любого значения с самим собой с помощью этой операции даёт в результате ноль. Эта команда короче (занимает меньше байт), чем более очевидная mov eax, 0, а также быстрее, поскольку процессор понимает, что это идиома сброса, и производит необходимое переименование регистров [16], что позволяет обойтись без использования АЛУ и загрузки портов.

Более того, данная идиома позволяет устранить зависимости по данным: обычно результат команды xor reg1, reg2 зависит от значений в регистрах reg1 и reg2, но в том особом случае, когда reg1 и reg2 содержат одно и то же значение, зависимости нет, так как при любых входных значениях на выходе получится ноль. Все современные x86-процессоры распознают этот [17] особый случай. Сказанное верно и для тех версий xor-идиомы, что используют SIMD-регистры, а именно операции vpxor над целыми числами и операций vxorps и vxorpd над вещественными числами.
Тут любопытный читатель может спросить: работает ли эта идиома с аналогичными вариантами команды kxor? Например, будет ли команда kxorb k1, k1, k1 [18] считаться идиомой сброса?
На самом деле это два разных вопроса, поскольку эффект от применения идиомы сброса складывается из двух компонентов:

  • Исполнение с нулевой задержкой в обход модуля исполнения (замена исполнения (execution elimination))
  • Устранение зависимостей

Разберёмся с каждым вопросом по отдельности.

Замена исполнения


Так что же, команды с xor, например kxorb k1, k1, k1, могут заменяться переназначением регистров без помещения в модуль исполнения?

Нет.

Мне даже не нужно ничего делать самому, чтобы доказать это: вся информация есть на сайте uops.info, поскольку они проводили такой тест и показали, что данная команда выполняется с задержкой 1 такт и занимает одну микрооперацию на порте p0. Из этого следует, что xor-идиомы сброса для масочных регистров не работают.

Устранение зависимостей


А что если идиомы сброса с kxor всё-таки устраняют зависимости по данным, пусть даже и требуют помещения в модуль исполнения?

Тут уже uops.info нам не поможет. Команда kxor имеет задержку 1 такт и исполняется на одном-единственном порте (p0), поэтому здесь наблюдается интересная (?) ситуация, при которой цепочка команд kxor исполняется с одной и той же скоростью независимо от того, есть между ними зависимости или нет: пропускная способность 1 команда/такт даёт такое же снижение производительности, что и задержка 1 команда/такт!

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

Все эти тесты можно найти в бенчмарке uarch bench, но ключевые моменты я приведу ниже.

Для начала измерим стандартное время копирования из регистра общего назначения и обратно:

kmovb k0, eax
kmovb eax, k0
; повторить ещё 127 раз

Пара этих команд выполняется [19] за 4 такта. Неизвестно, сколько именно времени приходится на каждую из них: по 2 такта или на одну 1 такт, а на другую – 3 такта [20]? Впрочем, для нашей задачи это неактуально, потому как нас интересует общее время копирования туда и обратно. Примечательно, что пропускная способность данной последовательности составляет 1 такт, что в 4 раза быстрее задержки, так как каждая команда выполняется на своём порте (p5 и p0, соответственно). Это означает, что мы можем отделить влияние задержки от влияния пропускной способности.

Далее в нашу цепочку мы включаем команду kxor, которая гарантированно не приводит к сбросу регистра:

kmovb k0, eax
kxorb k0, k0, k1
kmovb eax, k0
; повторить ещё 127 раз

Поскольку мы знаем, что kxorb имеет задержку 1 такт, общее время выполнения должно вырасти до 5 тактов – именно это и показывает тест (показаны результаты первых двух тестов):

** Группы команд avx512 : функционал AVX512 **
Бенчмарк                              Время в тактах     Время в нс
mov из GP в kreg и обратно                  4.00         1.25
mov из GP в kreg и обратно + 
kxorb без сброса                            5.00         1.57

И, наконец, главный тест:
kmovb k0, eax
kxorb k0, k0, k0
kmovb eax, k0
; повторить ещё 127 раз

На этот раз мы используем команду kxorb со сбросом регистра: kxorb k0, k0, k0. Если при этом исчезнет зависимость от значения в регистре k0, это будет означать, что команда kmovb eax, k0 больше не зависит от предшествующей команды kmovb k0, eax и что цепочка распалась, а общее время исполнения должно уменьшиться.

Барабанная дробь…

У нас получились всё те же 5.0 тактов – как в предыдущем примере:

** Группы команд avx512 : функционал AVX512 **
Бенчмарк                             Время в тактах    Время в нс
mov из GP в kreg и обратно                 4.00         1.25
mov из GP в kreg и обратно + 
kxorb без сброса                           5.00         1.57
mov из GP в kreg и обратно + 
kxorb со сбросом                           5.00         1.57

Предварительный вывод таков: идиомы сброса не распознаются процессором, если они применяются к масочным регистрам.

В заключение проведём ещё один тест, чтобы убедиться в правильности наших рассуждений: заменим команду kxor на команду kmov, которая, как известно, всегда устраняет зависимости:

kmovb k0, eax
kmovb k0, ecx
kmovb eax, k0
; повторить ещё 127 раз

Окончательный ответ представлен ниже. Последний тест гораздо быстрее – всего 2 такта, а узким местом является порт p5 (обе команды kmov k, r32 исполняются только на этом порте):

** Группы команд avx512 : функционал AVX512 **
Бенчмарк                              Время в тактах     Время в нс
mov из GP в kreg и обратно                 4.00            1.25
mov из GP в kreg и обратно + 
kxorb без сброса                           5.00            1.57
mov из GP в kreg и обратно + 
kxorb со сбросом                           5.00            1.57
mov из GP в kreg и обратно + 
mov из GP                                  2.00            0.63

Выходит, наше предположение верно.

Воспроизведение результатов


Все представленные в этой статье результаты вы можете воспроизвести самостоятельно, запустив исполняемый файл robsize на Linux или Windows (под WSL). Они также доступны в репозитории, как и скрипты для их сбора и построения графиков.

Выводы


  • Масочные регистры архитектуры SKX располагаются в отдельном физическом регистровом файле; 134 из них предназначены для хранения спекулятивных значений, общее же количество масочных регистров – 142
  • Это число сопоставимо с размерами регистровых файлов других типов, а также буфера ROB, и оно достаточно велико, чтобы не испытывать снижения производительности при работе с масочными регистрами
  • Команды копирования с масочными регистрами не заменяются
  • Идиомы сброса [21] с масочными регистрами не распознаются процессором и не приводят к замене исполнения или устранению зависимостей

  1. Их часто называют k-регистрами или просто kregs – по букве-префиксу. Как гласит молва, буква k была выбрана случайным образом – и то лишь после долгой и кровопролитной борьбы между сторонниками «масок» (m) и сторонниками «флагов» (f).
  2. Иногда ошибочно полагают (в том числе до недавнего времени так было написано даже в статье про AVX-512 на Википедии), что k0 – это не обычный масочный регистр, а всего лишь жёстко заданный индикатор, означающий, что маска не применяется. Но это не так: k0 – полноценный масочный регистр, и в него можно записывать, как и считывать из него, используя команды с префиксом k, а также SIMD-команды, записывающие в масочные регистры (например, любые команды сравнения из набора AVX-512). Однако если в SIMD-команде регистр k0 указать как маску записи, то он сработает как индикатор немаскированной операции и его содержимое не будет воспринято в качестве маски.
  3. Различие между режимами обнуления и слияния заключается в том, что в первом случае все элементы в регистре-приёмнике, для которых соответствующий бит в маске равен 0, затираются нулями, тогда как во втором случае такие элементы сохраняют своё значение. Как следствие, команда, выполняемая в режиме слияния, становится разрушающей, а регистр-источник служит также регистром-приёмником и порождает зависимость по входным данным.
  4. Далее я постараюсь везде использовать полный термин «масочный регистр», но если встретите слово kreg – ещё одно популярное название регистров этого типа (по меткам k0, k1 и т.д.), – просто мысленно замените «kreg» на «масочный регистр» (и наоборот).
  5. H. Wong, Measuring Reorder Buffer Capacity, May, 2013. [Online]. (Г. Вонг, «Измерение размера буфера переупорядочивания команд», май 2013. Онлайн-издание.) Ссылка: blog.stuffedcow.net/2013/05/measuring-rob-capacity
  6. На выполнение каждой из них обычно требуется от 100 до 300 тактов. Такой большой разброс объясняется тем, что в зависимости от платформы и особенностей микроархитектуры фактическое время выполнения команд с кэш-промахом изменяется с коэффициентом 2 и обычно составляет от 50 до 100 нс, а тактовая частота ЦП – с коэффициентом 2,5 (например, от 2 ГГц до 5 ГГц). Однако при схожем соотношении TLB-промахов/попаданий и их длительностей на разных машинах время выполнения тоже должно быть более или менее одинаковым.
  7. Слово «примерно» я написал не просто так. Как можно заметить, графики не идеально ровные ни до скачка, ни после. Скорее мы наблюдаем отдельные участки со своим уникальным поведением и прочими артефактами. Так, в тесте 29 график очень ровный примерно до отметки 104, затем следует небольшое повышение, а после этого – плавный подъём, заканчивающийся скачком за отметкой 200. Некоторые из этих эффектов можно объяснить, если мысленно (или на самом деле) смоделировать конвейер процессора – это поможет понять, что в какой-то момент балластные команды внесут свой вклад в общее время исполнения (хотя это и будет всего один такт или около того), однако другие особенности пока что не поддаются объяснению.
  8. Это объясняется, например, тем, что один слот переименования регистров может работать лишь с некоторым диапазоном записей в таблице псевдонимов регистров (register alias table, RAT), при этом используется первая доступная запись. Когда RAT почти заполнена, доступных записей может не оказаться, и тогда таблица будет считаться заполненной до конца, даже если в ней ещё остались свободные записи, так как они предназначены для других микроопераций. Поскольку обращение к записям RAT на каждой итерации осуществляется случайным образом, мы наблюдаем более или менее плавный подъём от низкого уровня к высокому на том участке графика, который соответствует неидеальному поведению.
  9. Оценка 60% получилась делением 134 на 224, т.е. размера спекулятивной части PRF масочных регистров на размер ROB. Дело в том, что если мы собираемся исчерпать ROB с его 224 активными командами, то сперва нам придётся исчерпать лимит масочных регистров, а это означает, что команды записи в них [10] должны составить 60% команд, размещённых в ROB. Разумеется, ещё раньше мы можем упереться в какой-то другой лимит, поэтому даже 60% может оказаться недостаточно, но нижняя граница определяется именно размером ROB, поскольку он актуален всегда.
  10. Важно уяснить, что физический масочный регистр расходуют только те команды, которые пишут в него. Команды, которые просто читают из него (например, SIMD-команды с маской записи), нового физического регистра не расходуют. [2]
  11. Чем больше доменов переименования регистров, тем легче блоку переименования работать с данным числом входных регистров. Иными словами, легче переименовать 2 входных регистра общего назначения и 2 входных SIMD-регистра (которые относятся к разным доменам), чем 4 регистра общего назначения.
  12. Эта аббревиатура расшифровывается как Physical Register Reclaim Table (таблица освобождения физических регистров) или Post Retirement Reclaim Table (таблица освобождения регистров после завершения команд).
  13. На самом деле это вовсе не так просто – хотя бы потому, что необходимо отслеживать «группы замен копирования», т.е. группы архитектурных регистров, отображающихся на один и тот же физический регистр, чтобы после очистки группы его можно было освободить. Эти группы сами по себе являются ограниченным ресурсом, который также должен отслеживаться. Дополнительно ситуацию осложняет наличие флагов: поскольку они хранятся вместе с выходным регистром, их наличие и состояние также приходится отслеживать.
  14. В частности, график соответствующего теста для регистров общего назначения (тест 7) выглядит совсем иначе, поскольку замена копирования сводит загрузку PRF практически к нулю и нас ограничивает только размер ROB.
  15. Обратите внимание, что я говорю о копировании не только между двумя масочными регистрами, но и между двумя регистрами, из которых масочный только один. Иными словами, копирование между регистром общего назначения и масочным регистром также не будет заменяться (что очевидно, если учесть, что это два разных типа регистров, которые размещаются в разных регистровых файлах – замена копирования при этом просто невозможна).
  16. Для этого он может отобразить соответствующий регистр в RAT на фиксированный, общий для всех регистровых файлов нулевой регистр или установить в RAT специальный флаг, указывающий, что данный регистр содержит ноль.
  17. Хотя для тестирования надёжней всего использовать xor, некоторые процессоры могут воспринимать в качестве идиом сброса или устранения зависимостей и другие команды, например sub reg,reg и даже sbb reg, reg. Последняя не является идиомой сброса, а просто устанавливает значение регистра reg в 0 или -1 (выставлены все биты) в зависимости от флага переноса. При этом не важно, какое значение хранится в reg, – важно лишь значение флага переноса, и некоторые процессоры знают эту идиому и при её выполнении устраняют зависимости. В своём руководстве Агнер очень подробно рассказывает о поддержке таких идиом на разных микроархитектурах.
  18. Обратите внимание, что совпадать должны только входные регистры: если команда kxorb k1, k1, k1 считается идиомой сброса, то логично ожидать того же от команды kxorb k1, k2, k2.
  19. Все тесты, описанные в данном разделе, можно запустить с помощью команды

    ./uarch-bench.sh --test-name=avx512/*. 

  20. Вот почему uops.info и для kmov r32, k, и для kmov k, 32 указывает задержку исполнения <= 3. Авторы знают, что пара этих команд выполняется за 4 такта. Так как на выполнение одной команды требуется как минимум 1 такт, значит, время выполнения каждой из них не может превышать 3 тактов.
  21. Да, фактически я проверил только команду xor, но поскольку это самая базовая идиома, то, по-моему, очевидно, что никакая другая идиома сброса также не будет работать. Буду рад, если вы докажете обратное: код находится в открытом доступе, так что можете переписать его под любую другую идиому и протестировать её.
PVS-Studio
Static Code Analysis for C, C++, C# and Java

Похожие публикации

Комментарии 2

    0
    Интересно было бы узнать, насколько хорошо умеют компиляторы работать с масочными регистрами?
      0
      А вообще AVX512 очень полезен тем, что есть предикаты, многие вещи делаются значительно проще. Не плохо бы тиснуть блог про это: например, в сравнении с AVX2.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое