SIMD инструкции для сравнения равенства с плавающей точкой (с NaN == NaN)

Какие инструкции будут использоваться для сравнения двух 128-битных векторов, состоящих из 4 * 32-битных значений с плавающей запятой?

Есть ли инструкция, которая считает значение NaN с обеих сторон равным? Если нет, то насколько велико влияние на производительность обходного пути, обеспечивающего рефлексивность (т. Е. NaN равен NaN)?

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

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

Требования

  • +0 а также -0 должен сравниваться как равный.
  • NaN должен сравниться с самим собой.
  • Различные представления NaN должны быть одинаковыми, но это требование может быть принесено в жертву, если влияние на производительность слишком велико.
  • Результат должен быть логическим, true если все четыре элемента с плавающей запятой одинаковы в обоих векторах, и false, если хотя бы один элемент отличается. куда true представлен скалярным целым числом 1 а также false от 0,

Контрольные примеры

(NaN, 0, 0, 0) == (NaN, 0, 0, 0) // for all representations of NaN
(-0,  0, 0, 0) == (+0,  0, 0, 0) // equal despite different bitwise representations
(1,   0, 0, 0) == (1,   0, 0, 0)
(0,   0, 0, 0) != (1,   0, 0, 0) // at least one different element => not equal 
(1,   0, 0, 0) != (0,   0, 0, 0)

Моя идея для реализации этого

Я думаю, что можно было бы объединить два NotLessThan сравнения (CMPNLTPS?) с помощью and достичь желаемого результата. Ассемблерный эквивалент AllTrue(!(x < y) and !(y < x)) или же AllFalse((x < y) or (y > x),

Фон

Фон для этого вопроса - план Microsoft по добавлению типа Vector в.NET. Где я спорю за рефлексив .Equals метод и нуждаемся в более четкой картине того, насколько велико влияние производительности этого рефлексива на IEEE. См. Следует Vector<float>.Equals быть рефлексивным или должно следовать семантике IEEE 754? на programmers.se для длинной истории.

2 ответа

Даже когда AVX VCMPPS доступен (с очень расширенным выбором предикатов), он менее эффективен, чем сравнение IEEE. Вы должны сделать как минимум два сравнения и объединить результаты. Это не так уж плохо.

  • различные кодировки NaN не равны: фактически 2 дополнительных insns (добавление 2 мопов). Без AVX: один дополнительный movaps сверх того.

  • различные кодировки NaN равны: фактически 4 дополнительных insns (добавление 4 мопов). Без AVX: два дополнительных movaps insn

Сравнение IEEE и ветвь - 3 мопа: cmpeqps / movmskps / тест-и-ветка. Intel и AMD объединяют тестирование и ветвление в одну операционную систему.

С AVX512: bitwise-NaN - это, вероятно, всего лишь одна дополнительная инструкция, поскольку при сравнении векторов нормалей и ветвлении, вероятно, используются vcmpEQ_OQps / ktest same,same / jcc, так что объединение двух разных режимов маски является бесплатным (просто измените аргументы на ktest). Единственной стоимостью является дополнительная vpcmpeqd k2, xmm0,xmm1,

AVX512 any-NaN - это всего лишь две дополнительные инструкции (2x VFPCLASSPS со вторым, используя результат первого в качестве нулевой маски. Увидеть ниже). Опять то ktest с двумя разными аргументами, чтобы установить флаг.


Моя лучшая идея на данный момент: ieee_equal || bitwise_equal

Если мы перестанем считать, что разные кодировки NaN равны друг другу:

  • Побитовое равенство ловит два идентичных NaN.
  • IEEE равных ловит +0 == -0 дело.

Нет случаев, когда сравнение дает ложный положительный результат (поскольку ieee_equal Значение false, если любой из операндов равен NaN: мы хотим просто равные, а не равные или неупорядоченные. AVX vcmpps предоставляет оба варианта, в то время как SSE обеспечивает только равные операции.)

Мы хотим знать, когда все элементы равны, поэтому мы должны начать с инвертированных сравнений. Проще проверить хотя бы один ненулевой элемент, чем проверить, что все элементы ненулевые. (т. е. горизонтальный И жесткий, горизонтальный ИЛИ легкий (pmovmskb / test, или же ptest). Принятие противоположного смысла сравнения бесплатно (jnz вместо jz).) Это тот же трюк, который использовал Пол Р.

; inputs in xmm0, xmm1
movaps    xmm2, xmm0    ; unneeded with 3-operand AVX instructions

cmpneqps  xmm2, xmm1    ; 0:A and B are ordered and equal.  -1:not ieee_equal.  predicate=NEQ_UQ in VEX encoding expanded notation
pcmpeqd   xmm0, xmm1    ; -1:bitwise equal  0:otherwise

; xmm0   xmm2
;   0      0   -> equal   (ieee_equal only)
;   0     -1   -> unequal (neither)
;  -1      0   -> equal   (bitwise equal and ieee_equal)
;  -1     -1   -> equal   (bitwise equal only: only happens when both are NaN)

andnps    xmm0, xmm2    ; NOT(xmm0) AND xmm2
; xmm0 elements are -1 where  (not bitwise equal) AND (not IEEE equal).
; xmm0 all-zero iff every element was bitwise or IEEE equal, or both
movmskps  eax, xmm0
test      eax, eax      ; it's too bad movmsk doesn't set EFLAGS according to the result
jz no_differences

Для двойной точности, ...PS а также pcmpeqQ будет работать так же.

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

С SSE4.1 PTEST Вы можете заменить andnps / movmskps / test-and-branch with:

ptest    xmm0, xmm2   ; CF =  0 == (NOT(xmm0) AND xmm2).
jc no_differences

Я ожидаю, что это первый раз, когда большинство людей видели CF Результат PTEST быть полезным для всего.:)

Это все еще три мопа на процессорах Intel и AMD ( (2ptest + 1jcc) против (pandn + movmsk + fused-test&branch)), но меньше инструкций. Это более эффективно, если вы собираетесь setcc или же cmovcc вместо jcc так как те не могут слиться с test,

Это дает в общей сложности 6 мопов (5 с AVX) для рефлексивного сравнения и ветвления против 3 мопов для сравнения и ветвления IEEE. (cmpeqps / movmskps / test-and-branch.)

PTEST имеет очень высокую задержку на процессорах семейства AMD Bulldozer ( 14c на Steamroller). У них есть один кластер векторных исполнительных блоков, совместно используемых двумя целочисленными ядрами. (Это их альтернатива гиперпоточности.) Это увеличивает время, когда может быть обнаружен неверный прогноз ветки, или задержку цепочки зависимости от данных (cmovcc / setcc).

Наборы PTEST ZF когда 0==(xmm0 AND xmm2): установить, если не было ни одного элемента bitwise_equal И IEEE (neq или неупорядоченный). т.е. ZF не установлен, если какой-либо элемент был bitwise_equal в то же время будучи !ieee_equal, Это может произойти только тогда, когда пара элементов содержит поразрядно NaN s (но может случиться, когда другие элементы неравны).

    movaps    xmm2, xmm0
    cmpneqps  xmm2, xmm1    ; 0:A and B are ordered and equal.
    pcmpeqd   xmm0, xmm1    ; -1:bitwise equal

    ptest    xmm0, xmm2
    jc   equal_reflexive   ; other cases

...

equal_reflexive:
    setnz  dl               ; set if at least one both-nan element

Там нет условия, что тесты CF=1 И что-нибудь о ZF, ja тесты CF=0 and ZF=1, Вряд ли вы все равно захотите это проверить, поэтому jnz в jc Цель ветки работает нормально. (И если бы вы только хотели проверить equal_reflexive А ТАКЖЕ at_least_one_nan, другая установка может установить флаги соответственно).


Считая все NaN равными, даже если они не равны по битам:

Это та же идея, что и в ответе Пола Р., но с исправлением ошибки (объедините проверку NaN с проверкой IEEE, используя AND, а не OR.)

; inputs in xmm0, xmm1
movaps      xmm2, xmm0
cmpordps    xmm2, xmm2      ; find NaNs in A.  (0: NaN.  -1: anything else).  Same as cmpeqps since src and dest are the same.
movaps      xmm3, xmm1
cmpordps    xmm3, xmm3      ; find NaNs in B
orps        xmm2, xmm3      ; 0:A and B are both NaN.  -1:anything else

cmpneqps    xmm0, xmm1      ; 0:IEEE equal (and ordered).  -1:unequal or unordered
; xmm0 AND xmm2  is zero where elements are IEEE equal, or both NaN
; xmm0   xmm2 
;   0      0     -> equal   (ieee_equal and both NaN (impossible))
;   0     -1     -> equal   (ieee_equal)
;  -1      0     -> equal   (both NaN)
;  -1     -1     -> unequal (neither equality condition)

ptest    xmm0, xmm2        ; ZF=  0 == (xmm0 AND xmm2).  Set if no differences in any element
jz   equal_reflexive
; else at least one element was unequal

;     alternative to PTEST:  andps  xmm0, xmm2 / movmskps / test / jz

Так что в этом случае нам не нужно PTEST "s CF результат в конце концов. Мы делаем при использовании PCMPEQD потому что у него нет обратного cmpunordps имеет cmpordps).

9 мопов с плавкими доменами для процессоров семейства Intel SnB. (7 с AVX: используйте неразрушающие инструкции с 3 операндами, чтобы избежать movaps.) Тем не менее, до-Skylake процессоры семейства SnB могут работать только cmpps на p1, так что это узкие места на приборе FP-add, если пропускная способность является проблемой. Skylake работает cmpps на р0/ р1.

andps имеет более короткую кодировку, чем pand и процессоры Intel от Nehalem до Broadwell могут работать только на 5-м порту. Это может быть желательно, чтобы предотвратить кражу цикла p0 или p1 из окружающего кода FP. Иначе pandn вероятно, лучший выбор. На AMD BD-семействе, andnps в любом случае работает в домене ive c, поэтому вы не избежите задержки обхода между векторами int и FP (что вы могли бы ожидать, если бы использовали movmskps вместо ptest в этой версии, которая использует только cmpps не pcmpeqd). Также обратите внимание, что здесь порядок команд выбран для удобства чтения. Помещение сравнения FP (A,B) раньше, до ANDPS, может помочь процессору быстрее начать цикл.

Если один операнд используется повторно, должна быть возможность повторно использовать результат самообнаружения NaN. Новый операнд все еще нуждается в проверке собственного NaN и сравнении с повторно используемым операндом, поэтому мы сохраняем только один movaps / cmpps,

Если векторы находятся в памяти, по крайней мере, один из них должен быть загружен с отдельной загрузкой insn. На другую можно просто ссылаться дважды по памяти. Это отстой, если он не выровнен или режим адресации не может микроплавиться, но может быть полезным. Если один из операндов vcmpps вектор, о котором известно, что он не имеет NaN (например, обнуленный регистр), vcmpunord_qps xmm2, xmm15, [rsi] найдете NaNs в [rsi],

Если мы не хотим использовать PTEST мы можем получить тот же результат, используя противоположные сравнения, но комбинируя их с противоположным логическим оператором (И против ИЛИ).

; inputs in xmm0, xmm1
movaps      xmm2, xmm0
cmpunordps  xmm2, xmm2      ; find NaNs in A (-1:NaN  0:anything else)
movaps      xmm3, xmm1
cmpunordps  xmm3, xmm3      ; find NaNs in B
andps       xmm2, xmm3      ; xmm2 = (-1:both NaN  0:anything else)
; now in the same boat as before: xmm2 is set for elements we want to consider equal, even though they're not IEEE equal

cmpeqps     xmm0, xmm1      ; -1:ieee_equal  0:unordered or unequal
; xmm0   xmm2 
;  -1      0     -> equal   (ieee_equal)
;  -1     -1     -> equal   (ieee_equal and both NaN (impossible))
;   0      0     -> unequal (neither)
;   0     -1     -> equal   (both NaN)

orps        xmm0, xmm2      ; 0: unequal.  -1:reflexive_equal
movmskps    eax, xmm0
test        eax, eax
jnz  equal_reflexive

Другие идеи: незавершенные, нежизнеспособные, сломанные или хуже, чем выше

Единственным результатом истинного сравнения является кодировка NaN, ( Попробуйте. Возможно, мы можем избежать использования POR или же PAND объединить результаты из cmpps на каждый операнд отдельно?

; inputs in A:xmm0 B:xmm1
movaps      xmm2, xmm0
cmpordps    xmm2, xmm2      ; find NaNs in A.  (0: NaN.  -1: anything else).  Same as cmpeqps since src and dest are the same.
; cmpunordps wouldn't be useful: NaN stays NaN, while other values are zeroed.  (This could be useful if ORPS didn't exist)

; integer -1 (all-ones) is a NaN encoding, but all-zeros is 0.0
cmpunordps  xmm2, xmm1
; A:NaN B:0   ->  0   unord 0   -> false
; A:0   B:NaN ->  NaN unord NaN -> true

; A:0   B:0   ->  NaN unord 0   -> true
; A:NaN B:NaN ->  0   unord NaN -> true

; Desired:   0 where A and B are both NaN.

cmpordps xmm2, xmm1 просто переворачивает конечный результат для каждого случая, с "нечетным человеком" все еще в 1-м ряду.

Мы можем получить только тот результат, который нам нужен (истинно, если A и B оба равны NaN), если оба входа инвертированы (NaN -> не-NaN и наоборот). Это означает, что мы могли бы использовать эту идею для cmpordps в качестве замены pand после выполнения cmpordps self,self на A и B. Это не полезно: даже если у нас есть AVX, но не AVX2, мы можем использовать vandps а также vandnps (а также vmovmskps поскольку vptest только AVX2). Битовые логические значения имеют только задержку одного цикла и не связывают порты выполнения vector-FP-add, которые уже являются узким местом для этого кода.


VFIXUPIMMPS

Я провел некоторое время с ручным управлением своей работой.

Он может изменить элемент назначения, если исходным элементом является NaN, но это не может быть обусловлено ничем об элементе dest.

Я надеялся, что смогу найти способ vcmpneqps и затем исправить этот результат, один раз с каждым исходным операндом (чтобы исключить логические инструкции, которые объединяют результаты 3 vcmpps инструкции). Теперь я почти уверен, что это невозможно, потому что, зная, что один операнд является NaN, недостаточно внести изменения в IEEE_equal(A,B) результат.

Я думаю, что единственный способ, которым мы могли бы использовать vfixupimmps предназначен для обнаружения NaN в каждом операнде источника отдельно, например vcmpunord_qps но хуже. Или как действительно глупая замена andps обнаружение 0 или всех единиц (NaN) в результатах маски предыдущих сравнений.


Регистры маски AVX512

Использование регистров маски AVX512 может помочь объединить результаты сравнений. Большинство инструкций сравнения AVX512 помещают результат в регистр маски вместо вектора маски в векторном регистре, поэтому нам действительно нужно поступать таким образом, если мы хотим работать с порциями 512b.

VFPCLASSPS k2 {k1}, xmm2, imm8 записывает в регистр маски, опционально маскируется другим регистром маски. Установив только биты QNaN и SNaN для imm8, мы можем получить маску того, где есть NaN в векторе. Установив все остальные биты, мы можем получить обратное.

Используя маску из A в качестве нулевой маски для vfpclassps на B мы можем найти позиции обоих NaN только с 2 инструкциями вместо обычной cmp / cmp / объединить. Таким образом, мы сохраняем or или же andn инструкция. Кстати, мне интересно, почему нет операции "ИЛИ-НЕ". Вероятно, это происходит даже реже, чем И-НЕ, или они просто не хотели porn в наборе инструкций.

Ни ясм, ни насм не могут собрать это, поэтому я даже не уверен, что у меня правильный синтаксис!

; I think this works

;  0x81 = CLASS_QNAN|CLASS_SNAN (first and last bits of the imm8)
VFPCLASSPS    k1,     zmm0, 0x81 ; k1 = 1:NaN in A.   0:non-NaN
VFPCLASSPS    k2{k1}, zmm1, 0x81 ; k2 = 1:NaNs in BOTH
;; where A doesn't have a NaN, k2 will be zero because of the zeromask
;; where B doesn't have a NaN, k2 will be zero because that's the FPCLASS result
;; so k2 is like the bitwise-equal result from pcmpeqd: it's an override for ieee_equal

vcmpNEQ_UQps  k3, zmm0, zmm1
;; k3= 0 only where IEEE equal (because of cmpneqps normal operation)

;  k2   k3   ; same logic table as the pcmpeqd bitwise-NaN version
;  0    0    ->  equal   (ieee equal)
;  0    1    ->  unequal (neither)
;  1    0    ->  equal   (ieee equal and both-NaN (impossible))
;  1    1    ->  equal   (both NaN)

;  not(k2) AND k3 is true only when the element is unequal (bitwise and ieee)

KTESTW        k2, k3    ; same as PTEST: set CF from 0 == (NOT(k2) AND k2)
jc .reflexive_equal

Мы могли бы повторно использовать один и тот же регистр маски как нулевую маску и пункт назначения для 2-го vfpclassps insn, но я использовал разные регистры на случай, если я хочу различить их в комментарии. Этот код требует минимум двух регистров маски, но не требует дополнительных векторных регистров. Мы могли бы также использовать k0 вместо k3 в качестве пункта назначения для vcmpps, поскольку нам не нужно использовать его как предикат, только как dest и src. (k0 это регистр, который нельзя использовать в качестве предиката, потому что вместо этого кодирование означает "без маскировки".)

Я не уверен, что мы могли бы создать одну маску с reflexive_equal результат для каждого элемента, без k... Инструкция по объединению двух масок в какой-то момент (например, kandnw вместо ktestw). Маски работают только как нулевые маски, а не как единые маски, которые могут навязать результат одному, поэтому объединение vfpclassps Результаты работают только как AND. Поэтому я думаю, что мы застряли с 1-значит-оба-NaN, что является неправильным смыслом использовать его в качестве нулевой маски с vcmpps, дела vcmpps сначала, а затем с помощью регистра маски в качестве пункта назначения и предиката для vfpclassps тоже не помогает. Маскирование слиянием вместо маскирования нуля сделает свое дело, но недоступно при записи в регистр маски.

;;; Demonstrate that it's hard (probably impossible) to avoid using any k... instructions
vcmpneq_uqps  k1,    zmm0, zmm1   ; 0:ieee equal   1:unequal or unordered

vfpclassps    k2{k1}, zmm0, 0x81   ; 0:ieee equal or A is NaN.  1:unequal
vfpclassps    k2{k2}, zmm1, 0x81   ; 0:ieee equal | A is NaN | B is NaN.  1:unequal
;; This is just a slow way to do vcmpneq_Oqps: ordered and unequal.

vfpclassps    k3{k1}, zmm0, ~0x81  ; 0:ieee equal or A is not NaN.  1:unequal and A is NaN
vfpclassps    k3{k3}, zmm1, ~0x81  ; 0:ieee equal | A is not NaN | B is not NaN.  1:unequal & A is NaN & B is NaN
;; nope, mixes the conditions the wrong way.
;; The bits that remain set don't have any information from vcmpneqps left: both-NaN is always ieee-unequal.

Если ktest в конечном итоге 2 мопа, как ptest, и не может макро-предохранитель, то kmov eax, k2 / test-and-branch, вероятно, будет дешевле, чем ktest k1,k2 / JCC. Надеемся, что это будет только один моп, поскольку регистры маски больше похожи на целочисленные регистры и могут быть спроектированы с самого начала как "близкие" к флагам. ptest был добавлен только в SSE4.1, после многих поколений проектов без взаимодействия между векторами и EFLAGS,

kmov все же настроил вас на popcnt, bsf или bsr. (bsf/ jcc это не макрослияние, поэтому в цикле поиска вы, вероятно, все равно захотите проверить / jcc и только bsf, когда найден ненулевой. Дополнительный байт для кодирования tzcnt ничего не купит, если вы не делаете что-то без веток, потому что bsf по-прежнему устанавливает ZF на нулевой вход, хотя регистр dest не определен. lzcnt дает 32 - bsr тем не менее, это может быть полезно, даже если вы знаете, что входные данные не равны нулю.)

Мы также можем использовать vcmpEQps и комбинируем наши результаты по-разному:

VFPCLASSPS      k1,     zmm0, 0x81 ; k1 = set where there are NaNs in A
VFPCLASSPS      k2{k1}, zmm1, 0x81 ; k2 = set where there are NaNs in BOTH
;; where A doesn't have a NaN, k2 will be zero because of the zeromask
;; where B doesn't have a NaN, k2 will be zero because that's the FPCLASS result
vcmpEQ_OQps     k3, zmm0, zmm1
;; k3= 1 only where IEEE equal and ordered (cmpeqps normal operation)

;  k3   k2
;  1    0    ->  equal   (ieee equal)
;  1    1    ->  equal   (ieee equal and both-NaN (impossible))
;  0    0    ->  unequal (neither)
;  0    1    ->  equal   (both NaN)

KORTESTW        k3, k2  ; CF = set iff k3|k2 is all-ones.
jc .reflexive_equal

Этот способ работает только тогда, когда есть размер kortest это точно соответствует количеству элементов в наших векторах. например, вектор 256b элементов двойной точности имеет только 4 элемента, но kortestb по-прежнему устанавливает CF в соответствии с младшими 8 битами регистров входной маски.


Использование только целочисленных операций

Кроме NaN, +/-0 - единственный раз, когда IEEE_equal отличается от bitwise_equal. (Если я что-то упустил. Дважды проверьте это предположение перед использованием!) +0 а также -0 все биты равны нулю, кроме -0 имеет установленный бит знака (MSB).

Если мы игнорируем разные кодировки NaN, тогда bitwise_equal - это результат, который мы хотим получить, за исключением случая +/- 0. A OR B будет 0 везде, кроме знакового бита, если A и B равны +/- 0. Сдвиг влево на единицу делает его равным нулю или не все нулю в зависимости от того, нужно ли нам переопределить битовый тест,

Это использует еще одну инструкцию, чем cmpneqps потому что мы эмулируем необходимую нам функциональность por / paddD, (или же pslld на один, но это на один байт длиннее. Он работает на другом порту, чем pcmpeq, но вам нужно учитывать распределение портов окружающего кода, чтобы учесть это в решении.)

Этот алгоритм может быть полезен на разных архитектурах SIMD, которые не предоставляют одинаковые векторные тесты FP для обнаружения NaN.

;inputs in xmm0:A  xmm1:B
movaps    xmm2, xmm0
pcmpeqd   xmm2, xmm1     ; xmm2=bitwise_equal.  (0:unequal -1:equal)

por       xmm0, xmm1
paddD     xmm0, xmm0     ; left-shift by 1 (one byte shorter than pslld xmm0, 1, and can run on more ports).

; xmm0=all-zero only in the +/- 0 case (where A and B are IEEE equal)

; xmm2     xmm0          desired result (0 means "no difference found")
;  -1       0        ->      0          ; bitwise equal and +/-0 equal
;  -1     non-zero   ->      0          ; just bitwise equal
;   0       0        ->      0          ; just +/-0 equal
;   0     non-zero   ->      non-zero   ; neither

ptest     xmm2, xmm0         ; CF = ( (not(xmm2) AND xmm0) == 0)
jc  reflexive_equal

Задержка ниже, чем cmpneqps Версия выше, на один или два цикла.

Мы действительно используем все преимущества PTEST здесь: использование его ANDN между двумя разными операндами и сравнение с нулем всего этого. Мы не можем заменить его pandn / movmskps потому что нам нужно проверить все биты, а не только знаковый бит каждого элемента.

Я на самом деле не проверял это, так что это может быть неправильно, даже если мой вывод о том, что +/- 0 - единственный раз, когда IEEE_equal отличается от bitwise_equal (кроме NaN).


Обработка не битовых идентичных NaN с целочисленными операциями, вероятно, не стоит. Кодировка настолько похожа на +/-Inf, что я не могу вспомнить ни одной простой проверки, которая бы не заняла несколько инструкций. В Inf установлены все биты экспоненты и мантисса со всеми нулями. NaN имеет все установленные биты экспоненты, с ненулевым mantissa, также значимым (таким образом, есть 23 бита полезной нагрузки). MSB мантиссы интерпретируется как is_quiet флаг для различения сигнализации / тихих NaNs. Также см. Руководство Intel vol1, таблица 4-3 (Floating-Point Number and NaN Encodings).

Если бы не было -Inf с использованием кодировки набора верхних 9 битов, мы могли бы проверить NaN с помощью сравнения без знака для A > 0x7f800000, (0x7f800000 с одинарной точностью +Inf). Тем не менее, обратите внимание, что pcmpgtd / pcmpgtq целые числа со знаком сравниваются. AVX512F VPCMPUD сравнение без знака (dest = регистр маски).


Идея ОП: !(a<b) && !(b<a)

Предложение ОП о !(a<b) && !(b<a) не может работать, и ни один не может изменить его. Вы не можете отличить один NaN от двух NaN только из двух сравнений с обращенными операндами. Даже смешение предикатов не может помочь: нет VCMPPS Предикат отличает один операнд, являющийся NaN, от обоих операндов, являющихся NaN, или зависит от того, является ли это первый или второй операнд, который является NaN. Таким образом, для их комбинации невозможно получить эту информацию.

Решение Пола Р. сравнения вектора с самим собой позволяет нам определять, где есть NaN, и обрабатывать их "вручную". Нет комбинации результатов из VCMPPS между двумя операндами достаточно, но с использованием операндов, отличных от A а также B действительно помогает (Либо известный не-NaN вектор, либо один и тот же операнд дважды).


Без инверсии битовый код NaN находит, когда хотя бы один элемент равен. (Там нет обратного для pcmpeqd, поэтому мы не можем использовать разные логические операторы и все равно получить тест на все-равно):

; inputs in xmm0, xmm1
movaps   xmm2, xmm0
cmpeqps  xmm2, xmm1    ; -1:ieee_equal.  EQ_OQ predicate in the expanded notation for VEX encoding
pcmpeqd  xmm0, xmm1    ; -1:bitwise equal
orps     xmm0, xmm2
; xmm0 = -1:(where an element is bitwise or ieee equal)   0:elsewhere

movmskps eax, xmm0
test     eax, eax
jnz at_least_one_equal
; else  all different

PTEST это бесполезно, так как объединение с OR - единственная полезная вещь.


// UNFINISHED start of an idea
bitdiff = _mm_xor_si128(A, B);
signbitdiff = _mm_srai_epi32(bitdiff, 31);   // broadcast the diff in sign bit to the whole vector
signbitdiff = _mm_srli_epi32(bitdiff, 1);    // zero the sign bit
something = _mm_and_si128(bitdiff, signbitdiff);

Вот одно из возможных решений - однако оно не очень эффективно и требует 6 инструкций:

__m128 v0, v1; // float vectors

__m128 v0nan = _mm_cmpeq_ps(v0, v0);                   // test v0 for NaNs
__m128 v1nan = _mm_cmpeq_ps(v1, v1);                   // test v1 for NaNs
__m128 vnan = _mm_or_si128(v0nan, v1nan);              // combine
__m128 vcmp = _mm_cmpneq_ps(v0, v1);                   // compare floats
vcmp = _mm_and_si128(vcmp, vnan);                      // combine NaN test
bool cmp = _mm_testz_si128(vcmp, vcmp);                // return true if all equal

Обратите внимание, что вся логика выше инвертирована, что может затруднить выполнение кода (ORс эффективно ANDс и наоборот).

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