Если это нормально для sgn(-0.0f)
, для получения результата -0.0f
вместо +0.0f
, вы можете сохранить инструкцию или два по сравнению с версией @Cory Nelson. См. Ниже версию, которая также распространяет NaN.
- выбрать 0,0 или 1,0 на основе сравнения для
x != 0.0f
- скопировать знаковый бит
x
в этом.
// return -0.0 for x=-0.0, otherwise the same as Cory's (except for NaN which neither handle well)
__m128 sgn_fast(__m128 x)
{
__m128 negzero = _mm_set1_ps(-0.0f);
// using _mm_setzero_ps() here might actually be better without AVX, since xor-zeroing is as cheap as a copy but starts a new dependency chain
//__m128 nonzero = _mm_cmpneq_ps(x, negzero); // -0.0 == 0.0 in IEEE floating point
__m128 nonzero = _mm_cmpneq_ps(x, _mm_setzero_ps());
__m128 x_signbit = _mm_and_ps(x, negzero);
__m128 zeroone = _mm_and_ps(nonzero, _mm_set1_ps(1.0f));
return _mm_or_ps(zeroone, x_signbit);
}
Когда вход NaN, я думаю, что она возвращает +/- 1.0f, по знаку NaN. (Так как _mm_cmpneq_ps()
истинно, когда x является NaN: см. the table on the CMPPD
instruction).
Без AVX это на два меньше инструкций, чем версия Cory (with clang3.9 on the Godbolt compiler explorer). При вставке в цикл операнды источника памяти могут быть регистровыми операндами. gcc использует больше инструкций, делая отдельную нагрузку MOVAPS и сама покраску в угол, который требует дополнительного MOVAPS для получения возвращаемого значения в xmm0.
xorps xmm1, xmm1
cmpneqps xmm1, xmm0
andps xmm0, xmmword ptr [rip + .LCPI0_0] # x_signbit
andps xmm1, xmmword ptr [rip + .LCPI0_1] # zeroone
orps xmm0, xmm1
Задержка критического пути является cmpneqps
+ andps
+ orps
, что 3 + 1 + 1 циклов на Intel Haswell для примера. Версия Cory должна запускать две инструкции cmpps
параллельно для достижения этой задержки, что возможно только на Skylake. Другие процессоры будут иметь конфликт ресурсов, вызывающий дополнительный цикл задержки.
Для распространения NaN, поэтому возможные выходы бы -1.0f
, -/+0.0f
, 1.0f
и NaN
, мы могли бы воспользоваться тем фактом, что все-онов немного шаблон является NaN.
_mm_cmpunord_ps(x,x)
для получения NaN-маски. (Или, что то же самое, cmpneqps)
or
что на результат оставить его неизменным или заставить его NaN.
// return -0.0 for x=-0.0. Return -NaN for any NaN
__m128 sgn_fast_nanpropagating(__m128 x)
{
__m128 negzero = _mm_set1_ps(-0.0f);
__m128 nonzero = _mm_cmpneq_ps(x, _mm_setzero_ps());
__m128 x_signbit = _mm_and_ps(x, negzero);
__m128 nanmask = _mm_cmpunord_ps(x,x);
__m128 x_sign_or_nan = _mm_or_ps(x_signbit, nanmask); // apply it here instead of to the final result for better ILP
__m128 zeroone = _mm_and_ps(nonzero, _mm_set1_ps(1.0f));
return _mm_or_ps(zeroone, x_sign_or_nan);
}
компилируется эффективно, и едва удлиняет критическую задержку пути. Однако требуется больше инструкций MOVAPS для копирования регистров без AVX.
Вы могли бы быть в состоянии сделать что-то полезное с SSE4.1 BLENDVPS, но это не самый эффективный инструкции на всех процессорах. Также трудно избежать обработки отрицательного нуля как ненулевого.
Если вы хотите целочисленный результат, вы можете использовать SSSE3 _mm_sign_epi32(set1(1), x)
получить -1, 0 или 1 выход.Если -0.0f -> -1
слишком неаккуратно, вы можете исправить это вверх с операции AND результате _mm_cmpneq_ps(x, _mm_setzero_ps())
// returns -1 for x = -0.0f
__m128i sgn_verysloppy_int_ssse3(__m128 x) {
__m128i one = _mm_set1_epi32(1);
__m128i sign = _mm_sign_epi32(one, _mm_castps_si128(x));
return sign;
}
// correct results for all inputs
// NaN -> -1 or 1 according to its sign bit, never 0
__m128i sgn_int_ssse3(__m128 x) {
__m128i one = _mm_set1_epi32(1);
__m128i sign = _mm_sign_epi32(one, _mm_castps_si128(x));
__m128 nonzero = _mm_cmpneq_ps(x, _mm_setzero_ps());
return _mm_and_si128(sign, _mm_castps_si128(nonzero));
}
Для части 2, почему вы хотите 'флаг 1.0f', вместо того, чтобы просто и маска из' _mm_cmpgt_ps'? например чтобы условно добавить что-то, замаскируйте операнд с результатом сравнения с нулевыми элементами, где сравнение является ложным. all-zero bits = 0.0f, который является аддитивным тождеством. Или если вы просто хотите сделать что-то 0 или без изменений, И это с маской. –
@PeterCordes, я не уверен, что понимаю вас (я не так разбираюсь в битах :-)). Вы могли бы написать решение для части 2? Чем эффективнее, тем лучше :-). – Royi
Если это то, что вы хотите, это самый эффективный способ получить его. Но я хочу сказать, что получение результата «0.0» или «1.0», вероятно, не всегда полезно. Что вы хотите с этим делать? –