1)有没有办法使用具有以下特征的SSE3(无SSE4)有效实施sign function?
__m128
。 __m128
,其值为[-1.0f,0.0f,1.0f] 我试过这个,但它没有用(虽然我认为应该):
inputVal = _mm_set_ps(-0.5, 0.5, 0.0, 3.0);
comp1 = _mm_cmpgt_ps(_mm_setzero_ps(), inputVal);
comp2 = _mm_cmpgt_ps(inputVal, _mm_setzero_ps());
comp1 = _mm_castsi128_ps(_mm_castps_si128(comp1));
comp2 = _mm_castsi128_ps(_mm_castps_si128(comp2));
signVal = _mm_sub_ps(comp1, comp2);
2)有没有办法创建“标志”功能(我不确定正确的名称)。即,如果A > B
结果为1
,则0
为__m128
。结果应该是浮点数(__m128 greatherThanFlag = _mm_and_ps(_mm_cmpgt_ps(valA, valB), _mm_set1_ps(1.0f));
__m128 lessThanFlag = _mm_and_ps(_mm_cmplt_ps(valA, valB), _mm_set1_ps(1.0f));
),就像它的输入一样。
更新:Cory Nelson的回答似乎在这里有效:
clrTextBk
答案 0 :(得分:5)
首先想到的可能是最简单的:
__m128 sign(__m128 x)
{
__m128 zero = _mm_setzero_ps();
__m128 positive = _mm_and_ps(_mm_cmpgt_ps(x, zero), _mm_set1_ps(1.0f));
__m128 negative = _mm_and_ps(_mm_cmplt_ps(x, zero), _mm_set1_ps(-1.0f));
return _mm_or_ps(positive, negative);
}
或者,如果您错过了并且打算获得整数结果:
__m128i sign(__m128 x)
{
__m128 zero = _mm_setzero_ps();
__m128 positive = _mm_and_ps(_mm_cmpgt_ps(x, zero),
_mm_castsi128_ps(_mm_set1_epi32(1)));
__m128 negative = _mm_cmplt_ps(x, zero);
return _mm_castps_si128(_mm_or_ps(positive, negative));
}
答案 1 :(得分:4)
如果sgn(-0.0f)
可以产生-0.0f
而不是+0.0f
的输出,那么与@Cory Nelson的版本相比,您可以保存一两条指令。请参阅下面的传播NaN的版本。
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时,根据NaN的符号,我认为它返回+/- 1.0f。 (当x为NaN时,_mm_cmpneq_ps()
为真:请参阅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
,例如,在Intel Haswell上为3 + 1 + 1个周期。 Cory的版本需要并行运行两个cmpps
指令才能实现延迟,这只能在Skylake上实现。其他CPU将产生资源冲突,导致额外的延迟周期。
传播NaN ,因此可能的输出为-1.0f
,-/+0.0f
,1.0f
和NaN
,我们可以利用事实上,全1位模式是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);
}
这有效地编译,并且几乎不会延长关键路径延迟。但是,在没有AVX的情况下复制寄存器需要更多的MOVAPS指令。
您可以使用SSE4.1 BLENDVPS执行一些有用的操作,但它不是所有CPU上最有效的指令。也很难避免将负零视为非零。
如果需要整数结果,可以使用SSSE3 _mm_sign_epi32(set1(1), x)
获得-1,0或1输出。如果-0.0f -> -1
过于草率,您可以通过与_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 :(得分:3)
如果您需要signum function float
个向量,其结果是int32_t
向量,并且您不关心NaN
s,那么a基于以下理论,可以使用整数指令实现更高效的版本。
如果您使用浮点数并将这些位重新解释为带符号的二进制补码整数,则可以得到3种不同的情况(其中X
是任意0
或1
,粗体MSB是符号位:
0
X X X X X X X X X X X X X X 1
,> 0
(或> 0.0f
为浮动)0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
,== 0
(或== 0.0f
为浮动)1
X X X X X X X X X X X X X X X
,< 0
(或<= 0.0f
为浮动)最后一种情况不明确,因为它可能是负零-0.0f
的特殊浮点情况:
1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
,== -0.0f == 0.0f
为浮动从这一点开始,浮点符号函数变为整数函数。
使用SSE3(不是SSSE3)提供的内在函数,可以实现为:
inline __m128i _mm_signum_ps(__m128 a)
{
__m128i x = _mm_castps_si128(a);
__m128i zero = _mm_setzero_si128();
__m128i m0 = _mm_cmpgt_epi32(x, zero);
__m128i m1 = _mm_cmplt_epi32(x, zero);
__m128i m2 = _mm_cmpeq_epi32(x, _mm_set1_epi32(0x80000000));
__m128i p = _mm_and_si128(m0, _mm_set1_epi32(+1));
// Note that since (-1 == 0xFFFFFFFF) in two's complement,
// n satisfies (n == m1), so the below line is strictly semantic
// __m128i n = _mm_and_si128(m1, _mm_set1_epi32(-1));
__m128i n = m1;
return _mm_andnot_si128(m2, _mm_or_si128(p, n));
}
优化版本是
inline __m128i _mm_signum_ps(__m128 a)
{
__m128i x = _mm_castps_si128(a);
__m128i zr = _mm_setzero_si128();
__m128i m0 = _mm_cmpeq_epi32(x, _mm_set1_epi32(0x80000000));
__m128i mp = _mm_cmpgt_epi32(x, zr);
__m128i mn = _mm_cmplt_epi32(x, zr);
return _mm_or_si128(
_mm_andnot_si128(m0, mn),
_mm_and_si128(mp, _mm_set1_epi32(1))
);
}
正如Peter在评论中所建议的,使用一个浮点比较_mm_cmplt_ps
而不是两个整数比较_mm_cmplt_epi32
/ _mm_cmpeq_epi32
来处理-0.0f
可以节省1个延迟,但它由于在浮点/整数域之间切换,可能会受到旁路延迟延迟的影响,因此最好坚持上面的仅整数实现。或不。由于您需要整数结果,因此您更有可能使用它并交换到整数域。所以:
inline __m128i _mm_signum_ps(__m128 a)
{
__m128i x = _mm_castps_si128(a);
__m128 zerops = _mm_setzero_ps();
__m128i mn = _mm_castps_si128(_mm_cmplt_ps(a, zerops));
__m128i mp = _mm_cmpgt_epi32(x, _mm_castps_si128(zerops));
return _mm_or_si128(mn, _mm_and_si128(mp, _mm_set1_epi32(1)));
}
在clang 3.9中使用-march=x86-64 -msse3 -O3
,这将编译为
_mm_signum_ps(float __vector(4)): # @_mm_signum2_ps(float __vector(4))
xorps xmm1, xmm1 # fp domain
movaps xmm2, xmm0 # fp domain
cmpltps xmm2, xmm1 # fp domain
pcmpgtd xmm0, xmm1 # int domain
psrld xmm0, 31 # int domain
por xmm0, xmm2 # int domain
ret
除cmpltps
以外,此处每条指令的延迟为1
,吞吐量为<= 1
。我认为这是一个非常有效的解决方案,可以使用SSSE3 _mm_sign_epi32
进一步改进。
如果您需要浮点结果,最好完全保留在浮点域中(而不是在浮点/整数域之间交换),因此请使用Peter's solutions之一。
答案 3 :(得分:1)
你很接近,但你的代码不能正常工作,因为你试图仅使用强制转换将0 / -1 int转换为float。
试试这个(未经测试):
inputVal = _mm_set_ps(-0.5, 0.5, 0.0, 3.0);
comp1 = _mm_cmpgt_ps(_mm_setzero_ps(), inputVal);
comp2 = _mm_cmpgt_ps(inputVal, _mm_setzero_ps());
comp1 = _mm_cvtepi32_ps(_mm_castps_si128(comp1)); // 0/-1 => 0.0f/-1.0f
comp2 = _mm_cvtepi32_ps(_mm_castps_si128(comp2));
signVal = _mm_sub_ps(comp1, comp2);
话虽如此,我认为Cory's solution可能更有效率。