Классификация инструкций x86 в соответствии с чувствительностью к режиму округления с плавающей запятой?

Я реализую режим рандомизированного округления для оценки числовой стабильности вычислений с плавающей запятой (FP) в коде C99. Это идея этой работы по вычислительной химии, но обновленная для обработки инструкций SSE и AVX, общих для последних архитектур x86_64. Рандомизированное округление вводит ассемблерные инструкции, чтобы задать режим округления либо округления до +inf, либо округления до -inf способом, характерным для каждой инструкции FP, случайным образом. При нескольких прогонах вы получаете образцы распределения возможных результатов.

Чтобы сделать это правильно, мне нужно знать, какие инструкции включают округление FP. Кажется, существует 4-сторонняя классификация:

  1. Ничего общего с FP, например. movq, addq, imulq
  2. С участием значений FP, но без вычисления FP, поэтому ничего общего с округлением, например. movss , movaps или выполнение некоторых вычислений значений FP, но без округления, например. орпы
  3. Вычисление FP, которое может включать округление, и режим округления указывается явно в инструкции, например. раундсс
  4. Вычисление FP, которое может включать округление, и режим округления является неявным (в соответствии с состоянием, ранее установленным ldmxcsr или fldcw), например. добавляет, sqrtss

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

Как проще всего, учитывая инструкцию x86, классифицировать ее, как указано выше? Где-то уже есть список с эквивалентной информацией? Или есть советы по поиску на страницах https://www.felixcloutier.com/x86/, которые могут автоматически определить эту классификацию?

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

fuz 21.11.2022 23:24

Было бы очень больно, если бы вы просто вставляли рандомизированные установщики режима округления перед каждой инструкцией, заканчивающейся на pd, sd, ps, ss?

chtz 21.11.2022 23:32

Я отредактировал конец своего вопроса, чтобы уточнить, что я специально не запрашиваю сторонние ресурсы; Я просто хочу знать, как классифицировать инструкции x86. Основываясь на вашем комментарии @fuz, я просмотрел ref.x86asm.net/geek.html, который основан на XML-описании всех инструкций, но я не уверен, как его использовать. cvtps2ps и roundss, например, оба находятся в "grp2" "convert", но очень разные в том, как они выбирают округление (неявное или явное, соответственно).

Gordon Kindlmann 21.11.2022 23:41

@chtz хорошая простая идея! Я все обдумаю. Но я обнаружил, что инструкции, чувствительные к округлению, составляют незначительное меньшинство инструкций FP в моей сборке (большинство из них представляют собой различные виды перемещений и сравнений), так что это может быть большим ударом по скорости, но я не знаю. пока не попробую. Также это не касается таких вещей, как roundss, которые требуют явного округления.

Gordon Kindlmann 21.11.2022 23:52

@GordonKindlmann Если производительность имеет значение, вы, конечно, все еще можете отфильтровать некоторые очень распространенные инструкции без округления (movaps, andps, ...). Если у вас осталось всего несколько ложных срабатываний, это не должно сильно повредить.

chtz 22.11.2022 09:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
92
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

TL:DR: Я думаю, что должен работать поиск инструкций, которые могут вызвать исключение «точности»: это означает, что они выполняют округление. Но вы можете не захотеть переопределять режим округления для инструкций, которые округляют или преобразовывают в целое число с текущим режимом округления, это может быть слишком большой ошибкой округления.


@chtz предлагает полезную эвристику изменения округления перед каждой инструкцией ss/sd/ps/pd, за исключением нескольких распространенных, которые вы заносите в черный список, таких как [v]movaps/d, [v]movups/d, [v]movss/d, [v]shuf*, [v]blend*, [v]perm*, побитовые логические значения и т. д. Также [v]ucomis/d / [v]comis/d и упакованный-SIMD сравнивается как [v]cmpps; Я думаю, что только денормалы равны нулю будут влиять на сравнение, а не на фактические режимы округления.

Возможно, вы захотите убедиться, что режим округления является ближайшим — даже для преобразования в целое число, например [v]cvtps2dq, или вообще для всех преобразований в / из целого числа, которые все начинаются с мнемоники cvt, хотя, возможно, все же рандомизированы для двойного числа с плавающей запятой ([v]cvtpd2ps). int->fp должен округляться для больших нечетных целых чисел, например больше 2^23 для числа с плавающей запятой.

Использование кодировки EVEX AVX-512 для переопределения режима округления для каждой инструкции без изменения mxcsr может значительно повысить производительность, если у вас есть процессор с поддержкой AVX-512. Смотри ниже.


Базы инструкций:

Если вы хотите использовать что-то подобное, я думаю, это должно сработать, чтобы проверить возможность вызвать исключение FP Precision, также известное как inexact. Это возможно только в том случае, если инструкция может иногда давать результат, который необходимо округлить. (Обычно все исключения FP маскируются, поэтому они только устанавливают бит в MXCSR для записи того, что они произошли. Мы по-прежнему говорим о «возбуждении» исключения FP независимо от того, маскированы ли они, с пониманием того, что обычно это просто установка фиксированного бит в среде FP, фактически не перехватывая и не запуская обработчик исключений и не заставляя ОС убивать ваш процесс с помощью SIGFPE.)

Несколько инструкций могут округлять, но не вызывают исключений по этому поводу, например roundsd, когда режим округления переопределяется его немедленным. Но округление до целочисленного значения double обычно является частью алгоритма, который, как вы ожидаете, сломается, если вы замените nearbyint на floor, ceil или trunc. Обычно roundsd используется с определенным режимом округления, указанным в непосредственном, вообще не используя MXCSR, но можно использовать текущий режим по умолчанию. (В этом случае он может вызвать исключение точности, поэтому оно будет отображаться в соответствии с этой эвристикой.)

Возможно, также оставьте cvtsd2si и cvtps2dq в покое, убедившись, что они используют ближайший-даже, как программа, ожидаемая для (int)nearbyint или lrint. (Предположим, что ваши программы не используют функции fenv для установки режимов округления.)

Я не думаю, что есть какие-либо инструкции, затронутые режимом округления, которые никогда не могут вызвать исключение точности.

Рассмотрение того, возможны ли какие-либо исключения FP, - это еще один способ определить, является ли это «математической FP» инструкцией по сравнению с просто побитовой или перетасовкой или чем-то еще. Такие инструкции, как ucomisd скалярное сравнение с двойной точностью, не могут вызвать много исключений (U для неупорядоченного означает, что один или оба операнда, являющиеся NaN, не вызывают беспокойства). Но он не подавляет SNaN, а только QNaN, и может вызвать денормальное исключение.

Я думаю, что именно неточное исключение, известное как точность, может быть полезной лакмусовой бумажкой. например

  • списки movaps Исключения SIMD с плавающей запятой: нет, потому что он вообще не выполняет математику.

  • списки addps Исключения SIMD с плавающей запятой: Переполнение, недополнение, недействительное, точное, денормальное.

  • cvtpd2dq списки Исключения SIMD с плавающей запятой ¶ Недействительно, точность. Упакованное преобразование из FP в целое число с использованием текущего режима округления. Вы, вероятно, захотите занести его в белый список как незащищенный, так как +-1 целое число намного дальше, чем +-1ulp.

  • списки ucomisd Исключения SIMD с плавающей запятой ¶ Недопустимый (если операнды SNaN), денормализованный. (И не более того; сравнения не округляются и не переполняются/не занижаются. А «u» означает неупорядоченный, что означает, что он не рассматривает тихий NaN как заслуживающий исключения, а только сигнализирует о NaN. Другие математические инструкции, такие как sqrtsd, производят QNaN для недопустимых значений. входы, вы получите SNaN, только если создадите его вручную.)

«SIMD с плавающей запятой» означает, что он заботится о MXCSR, а не о регистрах управления/статуса x87. addss также использует их; это скалярная инструкция, использующая регистры SIMD.

Надеюсь, ваша программа вообще не будет использовать какие-либо устаревшие инструкции x87, если вы скомпилируете для x86-64 и не используете 80-битную long double. Но если вы это сделаете, режим округления и контроль точности в управляющем слове x87 (fldcw) будут полностью отделены от режима округления SSE/AVX в MXCSR. (http://www.ray.masmcode.com/tutorial/index.html).


AVX-512 Встроенное переопределение округления

AVX-512 может переопределить округление для каждой инструкции, используя пару битов внутри префикса EVEX. Это намного эффективнее, чем частое изменение MXCSR, поэтому я рекомендую его, если у вас есть доступ к машине, поддерживающей AVX-512.

Инструкции по первому сохранению, хоть магазин + ldmxcsr, а ldmxcsr недешево, вроде 4 мкп на Intel SKX с пропускной способностью 3 такта. (по сравнению с пропускной способностью 0,5c для математики FP). Или на Zen4, 6 мкп с пропускной способностью 21 такт. Кроме того, если ЦП не переименует весь MXCSR, включая управление округлением, загрузка MXCSR должна будет сериализовать выполнение математических инструкций FP относительно. к этому. Я не уверен, что современные процессоры Intel, такие как Skylake или Alder Lake, переименовывают MXCSR; Я нашел пару ответов SO, в которых упоминается концепция:

  • Наблюдение за зависимостями регистров x86 цитирует некоторый документ Intel, в котором упоминается событие производительности для некоторой микроархитектуры: Циклы остановки переименования MXCSR — остановки из-за переименования регистра MXCSR, происходящего слишком близко к предыдущему переименованию MXCSR. Так что это плохо для вашего варианта использования, когда вы очень часто меняете его в коде, интенсивно использующем FP.

  • Что может означать resource_stall.other — некоторые упоминания о прилавках ресурсов MXCSR на Whiskey Lake, но IDK, если это подразумевает переименование или просто ожидание сброса старых инструкций при изменении режима округления.

Из-за ограничений кодирования встроенное округление доступно только для скалярных и 512-битных векторов, а не для 128 или 256. И только когда операнды являются регистрами, а не источником памяти.

Переопределение режима округления также подразумевает SAE, подавление всех исключений.

# GAS AT&T syntax
vaddss {rn-sae}, %xmm0, %xmm1, %xmm1   # nearest-even
vaddss {rz-sae}, %xmm0, %xmm1, %xmm1   # toward zero
vaddss {rd-sae}, %xmm0, %xmm1, %xmm1   # down
vaddss {ru-sae}, %xmm0, %xmm1, %xmm1   # up

vaddps {ru-sae}, %zmm0, %zmm1, %zmm1   # also available for 512-bit vectors 
#vaddps {ru-sae}, %ymm0, %ymm1, %ymm1   # but not 128 or 256

#vaddss {rz-sae}, (%rsi), %xmm31, %xmm31   # nope, memory source operand
# vaddps {rz-sae}, (%rsi), %zmm31, %zmm31  # nope

Обратите внимание на необязательное {er} в форме AVX-512 EVEX addss (https://www.felixcloutier.com/x86/addss ), но только для 512-битной формы vaddps , а не EVEV xmm /ymm формы. См. также Длина вектора AVX512 и контроль SAE

Учитывая эти ограничения на то, когда его можно использовать, вам могут понадобиться некоторые запасные регистры для его использования. например загрузить во временный регистр, например x/zmm31, а затем использовать скалярную 512-битную инструкцию.

Так что, возможно, скомпилируйте с AVX2+FMA, оставив вам zmm16..31 доступными в качестве временных. (Которое можно испачкать, не беспокоясь vzeroupper)

 vsubps (%rsi), %xmm1, %xmm0       # 128-bit SIMD original

                                   # round-down replacement
 vmovups  (%rsi), %xmm31
 vsubps  {rd-sae}, %zmm31, %zmm1, %zmm31   # write a 512-bit temporary
 vmovaps   %xmm31, %xmm0                   # write the original destination with original width

  ----
 vsubps %xmm2, %xmm1, %xmm0       # 128-bit SIMD original

                                   # round-down replacement
 vsubps  {rd-sae}, %zmm2, %zmm1, %zmm31   # write a 512-bit temporary
 vmovaps   %xmm31, %xmm0                  # write the original destination with original width

Я не уверен на 100%, что важно избегать записи %zmm0 на любых процессорах, если вы уже используете старшие половины ymm16..31, но я думаю, что это может зависеть от того, как именно выполняются переходы SSE/AVX и сохраненные верхние значения. реализовано.

Если оригинал использовал 256-битные векторы YMM, он будет использовать vzeroupper в какой-то момент перед выполнением инструкций устаревшего SSE. В таком случае:

 vsubps (%rsi), %ymm1, %ymm0       # original already using YMM upper halves

 vmovups  (%rsi), %ymm31           # replacement
 vsubps  {rd-sae}, %zmm31, %zmm1, %zmm0

Для скаляра это проще и с меньшими затратами на производительность: нам не нужно использовать какие-либо 512-битные векторные операции ZMM, поэтому у нас нет таких эффектов, как SIMD-инструкции, снижающие частоту процессора или отключение порта 1 для SIMD uops на процессорах Intel. Или для Zen4 512-битные uops занимают исполнительный блок за 2 такта. (Но по-прежнему являются однооперативными для внешнего интерфейса, что отлично подходит для узких мест внешнего интерфейса и неупорядоченного выполнения, позволяющего видеть далеко.)

 vsubsd (%rsi), %xmm1, %xmm0       # scalar original

 vmovsd  (%rsi), %xmm31            # replacement still only needs scalar
 vsubsd  {rd-sae}, %xmm31, %xmm1, %xmm0

Это значительно увеличивает размер кода, например. objdump Разборка синтаксиса Intel:

   0:   c5 f3 5c 06             vsubsd xmm0,xmm1,QWORD PTR [rsi]  # orig: 4 bytes, or 5 with some registers or instructions

   4:   62 61 ff 08 10 3e       vmovsd xmm31,QWORD PTR [rsi]      # replacement, 2x 6B
   a:   62 91 f7 38 5c c7       vsubsd xmm0,xmm1,xmm31{rd-sae}

Наследие x87

x87 сложнее, потому что даже fld / fstp (или версия любой другой инструкции с источником памяти) выполняет преобразование на лету между 80-битным внутренним форматом и 32/64-битным числом с плавающей запятой/двойным числом в памяти. Это связано с округлением при сохранении, но не при загрузке, поэтому на самом деле вам нужно рандомизировать режим округления x87 перед fst / fstp, если только пунктом назначения не является m80, сохраняя внутренний формат без изменений.

Я не уверен, что произойдет, если биты контроля точности будут установлены на 24-битную точность мантиссы (32-битное число с плавающей запятой) при загрузке 64-битного двойного числа (53-битная мантисса). В руководстве об этом не упоминается, поэтому, вероятно, биты точности не вводят новые места для округления, а только влияют на то, какая точность требуется в первую очередь для ускорения вычислений, контролируя округление в тех случаях, когда округление потребуется даже с ними. установить с полной точностью.

  • x87 списки fld Исключения с плавающей запятой ¶
    • #IS Произошло опустошение или переполнение стека. (может произойти с любой инструкцией FPU, которая перемещает стек регистров.)
    • #IA (Исходный операнд — это SNaN. Не происходит, если исходный операнд находится в формате двойной расширенной точности с плавающей запятой (FLD m80fp или FLD ST(i). Таким образом, очевидно fld не рассматривает QNaN как заслуживающую исключения.
    • #D Исходный операнд является денормальным значением. Не происходит, если исходный операнд имеет формат с плавающей запятой двойной расширенной точности.
  • x87 fsincos перечисляет как fld, но #IA перечисляет «Исходный операнд — это значение SNaN, ∞ или неподдерживаемый формат». Плюс:
    • #U (недополнение) Результат слишком мал для целевого формата.
    • #P (точность) Значение не может быть точно представлено в целевом формате.

Спасибо @PeterCordes за все это. Ключевым моментом является проверка исключений точности. Кстати, статья по вычислительной химии 2011 года (ссылка на мой вопрос), вдохновляющая мой подход, специфична для инструкций x87 в конце вашего ответа. Их подход генерирует большую таблицу контрольных слов (инициализируемых один раз в соответствии со случайным начальным числом, полученным во время выполнения) для динамического задания направления округления для каждой инструкции. может быть, глупый вопрос: с вашим подходом к префиксу EVEX, специфичным для AVX, разве это не требует перекомпиляции для изменения направления округления?

Gordon Kindlmann 22.11.2022 14:06

@GordonKindlmann: Да, со встроенным округлением AVX-512 режим округления жестко запрограммирован в машинном коде. Я предполагал, что это то, что вы делаете, постобрабатывая вывод gcc -S (плюс любые другие варианты, такие как -O3 -march=native -mno-avx512f -fno-trapping-math -fno-math-errno). Но теперь, когда вы упомянули об этом, динамическая рандомизация для выполнения инструкции, вероятно, была бы численно более интересной, чем статическая рандомизация для каждой инструкции.

Peter Cordes 22.11.2022 14:17

@GordonKindlmann: Да, вы можете создать большую таблицу и зациклиться на ней или запустить дешевый PRNG, такой как xorshift+, и объединить 2 его бита в фиксированное значение MXCSR, где биты RC уже очищены, так что просто и/или / хранит / ldmxcsr. Будем надеяться, что exec не по порядку сможет запустить эти инструкции, пока выполнение работы FP остановлено. IDK, который будет работать лучше. Основное преимущество запуска PRNG заключается в том, чтобы избежать промахов в кеше и уменьшить объем кеша, если код FP не настолько замедлен, что ему никогда не приходится ждать памяти.

Peter Cordes 22.11.2022 14:24

Другие вопросы по теме