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
с и наоборот).