Design: Документируйте, почему биты NaN не полностью детерминированы

Созданный на 22 мар. 2016  ·  15Комментарии  ·  Источник: WebAssembly/design

(В настоящее время я не защищаю это; это значит, что мы принимаем обоснованное решение и собираем материал для обоснования.)

Глядя на Nondeterminism.md , выделяются части с битами NaN. Конечно, потоки могут гоняться, ресурсы могут быть исчерпаны, а функции могут быть добавлены; таковы последствия общего дизайна. Но биты NaN? Могут ли виртуальные машины быть немного умнее и позаботиться об этом? Есть две проблемы:

Когда операция получает более одного операнда NaN, какой из них распространяется?

IEEE 754 оставляет это неопределенным, но по крайней мере x86, ARM и Power выбирают «первый» операнд, и это разумный выбор.

Однако исправление выбора на уровне wasm означало бы, что реализации wasm не могут коммутировать операции сложения и умножения с плавающей запятой, что иногда бывает полезной оптимизацией на до-VEX x86, где инструкции затирают один из своих входов. Кроме того, потребуется, чтобы любой, кто использует виртуальную машину на основе LLVM, научил ее тому, что сложение и умножение не коммутативны в wasm.

Можно резонно возразить, что это не шумиха, так что эту проблему теоретически можно исправить, если есть сильное желание.

Когда операция производит NaN и не имеет операндов NaN, какой бит знака?

x86 использует 1, ARM использует 0.

Самый простой способ исправить это - канонизировать после каждой операции с плавающей запятой. Это выполнимо, хотя хитрость в том, что и 0, и 1 являются возможными допустимыми значениями, когда есть операнд NaN для распространения, поэтому было бы недостаточно просто проверить результат NaN и канонизировать; нужно будет проверить результат NaN и отсутствие операндов NaN и только потом канонизировать.

Канонизация после _каждой_ операции была бы очень дорогой; другой вариант - просто канонизировать в точках «выхода» вычислений (путь канонизации тогда должен был бы по существу воспроизвести весь поток вычислений, чтобы определить правильный вывод NaN). Это улучшение, но, вероятно, все же добавляет значительные накладные расходы.

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

К сожалению, у этих подходов есть существенные недостатки. Если не появятся другие идеи или нет очень сильного желания, это кажется трудным исправить.


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

Есть ли у кого-нибудь еще мысли добавить?

clarification floating point

Самый полезный комментарий

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

Все 15 Комментарий

Еще одна вещь, которую следует отметить, это то, что биты NaN трудно случайно заметить.

Есть ли способ сделать их невозможными для наблюдения или хотя бы знаковый бит?

IMO недетерминизм NaN следует оставить в покое, учитывая, что программное обеспечение редко заботится о них. Почему нужно жертвовать производительностью, чтобы сделать их детерминированными?

Вот список способов наблюдения за битами NaN:

  • reinterpret конверсия
  • store в линейную память и загрузить биты с другой интерпретацией (или позволить битам наблюдать извне)
  • передать аргумент call импортированной функции или вернуть значение из экспортированной функции
  • copysign знаковый бит для не-NaN

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

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

Преимущества @qwertie могут включать немного большую переносимость (в мире действительно существует код, который неразумно использует, например, функцию IEEE 754 totalOrder), немного большую воспроизводимость и более сильные инварианты при выполнении симметричных вычислений на нескольких узлах.

store очень часто встречается на горячих путях, поэтому, вероятно, это будет довольно дорого.

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

@wanderer Это, по сути, то, что влечет за собой канонизация: проверьте, является ли значение NaN, и, если да, примените некоторые исправления. Обычно это дополнительное сравнение и ответвление на горячем пути.

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

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

Если wasm генерируется из языка высокого уровня, этот переводчик может предоставить возможность управления уровнем семантики FP, например, аналогично -ffast-math. Затем переводчик мог вставить любые дополнительные исправления wasm, необходимые для приведения nans к желаемому формату. С этой целью мы могли бы предоставить операторы isNan или normalizeNan, хотя я сейчас не сторонник этого.

Короче говоря, "исправление" лучше всего доверить инструменту более высокого уровня, IMO.

+1 @mbodart. Изменить : о, смотри, сейчас есть +1 штука :). Кстати, я лично считаю, что Wasm = мировое господство, и поэтому будущие процессоры не будут противодействовать ему.

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

Я согласен с @jfbastien. Прежде чем приступить к работе с семантикой, необходимо сначала количественно оценить влияние на производительность.

Чтобы быть ясным, я в настоящее время не выступаю за изменения здесь; Я собираю обосновательный материал. Битовый недетерминизм NaN проявляется и требует объяснения. И, насколько мне известно, никто не оценил влияние на производительность недетерминированности этих битов.

ARMv8 не всегда передает первый операнд, если оба операнда - NaN. Правило такое:

  • Если один операнд является тихим NaN, а другой - сигнальным NaN, распространить сигнальный NaN.
  • В противном случае распространите первый операнд.

Пока я читаю спецификацию, это относится как к режимам Aarch32, так и к Aarch64 ARMv8.

Это поведение отличается от SSE, который в обоих случаях распространяет первый операнд.

Обе архитектуры преобразуют sNaN в qNaN, установив бит молчания перед его распространением.

@stoklund Хорошее место! Я пропустил, что ARM, выбирая первый NaN, не происходит в случае, когда второй NaN сигнализирует. Это усложнило бы стратегию, которую я изложил для случая множественных NaN выше, поэтому мы можем упомянуть это в обосновании.

Теперь я создал # 973, чтобы предложить конкретный текст, резюмирующий вышеизложенное.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

rossberg picture rossberg  ·  68Комментарии

autumnontape picture autumnontape  ·  38Комментарии

jfbastien picture jfbastien  ·  94Комментарии

jfbastien picture jfbastien  ·  44Комментарии

verbessern picture verbessern  ·  42Комментарии