我需要知道__m128中存储了最大绝对值的值的符号。这是我现在的解决方案:
int getMaxSign(__m128 const& vec) {
static const __m128 SIGN_BIT_MASK =
_mm_castsi128_ps(_mm_set1_epi32(0x80000000));
// This creates an int, where sign(a) is 1 if a is negative, 0 o.w.:
// sign(a3)<<3 | sign(a2)<<2 | sign(a1)<<1 | sign(a0)
const int signMask = _mm_movemask_ps(vec);
// Get the absolute value of the vector;
__m128 absValsMMX = _mm_andnot_ps(SIGN_BIT_MASK, vec);
// Figure out the horizontal max
__declspec(align(16)) float absVals[4];
_mm_store_ps(absVals, absValsMMX);
const float maxVal = std::max(std::max(absVals[0], absVals[1]), absVals[2]);
return (maxVal == absVals[0] ? signMask & 0x1 :
(maxVal == absVals[1] ? signMask & 0x2 : signMask & 0x4));
}
在这种情况下,如果具有最大绝对值的值为负,则sign将为1,否则为0,但我实际上并不关心约定是什么。另一件事是我使用这些__m128表示同源向量,所以我知道最后一个值总是为0.
对于相对简单的任务来说,这似乎要做很多工作。我怎么能更快地做到这一点?
谢谢!
答案 0 :(得分:4)
这是一种可能的实现(在C中):
int getMaxSign(const __m128 v)
{
__m128 v1, vmax, vmin, vsign;
float sign;
v1 = (__m128)_mm_alignr_epi8((__m128i)v, (__m128i)v, 4); // v1 = v rotated by 1 element
vmax = _mm_max_ps(v, v1); // generate horizontal max/min
vmin = _mm_min_ps(v, v1);
vmax = _mm_max_ps(vmax, (__m128)_mm_alignr_epi8((__m128i)vmax, (__m128i)vmax, 8));
vmin = _mm_min_ps(vmin, (__m128)_mm_alignr_epi8((__m128i)vmin, (__m128i)vmin, 8));
vsign = _mm_add_ps(vmax, vmin); // add max and min to get sign of abs max
sign = _mm_extract_ps(vsign, 0);
return (int)(sign < 0.0f); // return 1 for negative
}
虽然这看起来像很多代码,但它只有大约9个SSE指令,并且没有内存访问,没有分支和非常少的标量代码。
请注意,上面使用了SSSE3和SSE4.1指令。
这是第二个版本,只需要SSSE3:
int getMaxSign(const __m128 v)
{
__m128 v1, vmax, vmin, vsign;
int mask;
v1 = (__m128)_mm_alignr_epi8((__m128i)v, (__m128i)v, 4); // v1 = v rotated by 1 element
vmax = _mm_max_ps(v, v1); // generate horizontal max/min
vmin = _mm_min_ps(v, v1);
vmax = _mm_max_ps(vmax, (__m128)_mm_alignr_epi8((__m128i)vmax, (__m128i)vmax, 8));
vmin = _mm_min_ps(vmin, (__m128)_mm_alignr_epi8((__m128i)vmin, (__m128i)vmin, 8));
vsign = _mm_add_ps(vmax, vmin); // add max and min to get sign of abs max
mask = _mm_movemask_epi8((__m128i)vsign);
return (mask & 8) != 0; // return 1 for negative
}
这会产生12条指令:
pshufd $57, %xmm0, %xmm1
movdqa %xmm0, %xmm2
minps %xmm1, %xmm2
pshufd $78, %xmm2, %xmm3
minps %xmm3, %xmm2
maxps %xmm1, %xmm0
pshufd $78, %xmm0, %xmm1
maxps %xmm1, %xmm0
addps %xmm2, %xmm0
pmovmskb %xmm0, %eax
shrl $3, %eax
andl $1, %eax
注意编译器如何巧妙地将palignr
更改为pshufd
,并且仅使用shrl
和andl
来实现最终的标量测试。
对于Visual Studio C / C ++的注意事项:要在__m128
和__m128i
之间进行投射,您需要使用_mm_castps_si128
和_mm_castsi128_ps
,例如
mask = _mm_movemask_epi8((__m128i)vsign);
需要更改为:
mask = _mm_movemask_epi8(_mm_castps_si128(vsign));
答案 1 :(得分:0)
m = min(a,b,c);
M = max(a,b,c);
// return abs(m)>abs(M) ? sign(m): sign(M); // was
return sign(m+M);
正如Paul_R正确注意到的那样,符号来自于最小值和最大值的总和。赢得更大(相反签名)的绝对值。
但是这个想法可以被更多地利用:最小/最大的总和是相同的,所有元素的总和减去中间的一个,可以通过最多3次比较找到。
return sign(a+b+c - middle(a,b,c)); // or
return sign(a*aw + b*bw + c*cw); // where aw,bw,cw = [0,1]
aw,bw,cw可以从赢得比较的数量得出(我认为必须仔细计划这个案例,当有2或3个相等的值时。)
进一步说:
x = abs(b)>abs(a)?b:a;
return sign(x+c);
可能更进一步:
s = sign(a + b); // store the sign of larger of a or b
a = abs(a); b=abs(b);
a = max(a,b) | s; // somehow copy the sign.
return sign(a+c);
答案 2 :(得分:0)
如果您的数字是离散的,并且间距适当,并且从有限的子集中绘制,则还有其他可能性。
如果你保证a,b和c是整数,那么你可以将矢量乘以它以获得奇数幂,然后用&lt; 1,1,1&gt;点对它。例如,如果我们将它自己乘以4次,它会给你&lt; a ^ 5,b ^ 5,c ^ 5&gt;。如果| a |是最大的|和| = = 2,那么我们知道b和c将是1或0,因此a ^ 3的值将占主导地位并且点积将具有其符号。例如,如果X =&lt; a = -2,b = 1,c = 0&gt;然后X ^ 5 =&lt; -32,1,0&gt;。当你用&lt; 1,1,1&gt;点缀它时你得到-31,其标志反映出最大绝对值。随着最大数量的绝对值增加,它与其他项之间的差异将倾向于收敛 - 例如,如果我们具有&lt; -8,7,7&gt;,则上述算法给出X ^ 5 =&lt; -32768,16807,16807&gt;,用&lt; 1,1,1&gt;表示点。并且得到846,因此算法以指数5失败。如果我们将指数推高到7,我们得到&lt; -2097152,823543,823543&gt;,点缀为&lt; 1,1,1&gt;给我们-450066,这是正确的答案。最终的舍入错误也会破坏这种方法。但我希望如果您了解数据集的限制,它可能会对其他替代方案提供一些见解。
作为脚注,记住X ^ 5 =(X * X)*(X * X)* X,所以你做一个乘法得到X ^ 2,乘以它得到X ^ 4,然后乘以X - 三次乘以总数。你需要一个奇数指数来保留符号。