Cla*_*dio 11 c++ sse image-processing simd sse2
我处理图像处理.我需要将16位整数SSE向量除以255.
我不能使用像_mm_srli_epi16()这样的移位运算符,因为255不是2的幂的倍数.
我当然知道可以将整数转换为浮点数,执行除法然后返回转换为整数.
但也许有人知道另一种解决方案......
Erm*_*mIg 14
有一个除以255的整数近似值:
inline int DivideBy255(int value)
{
return (value + 1 + (value >> 8)) >> 8;
}
Run Code Online (Sandbox Code Playgroud)
所以使用SSE2看起来像:
inline __m128i DivideI16By255(__m128i value)
{
return _mm_srli_epi16(_mm_add_epi16(
_mm_add_epi16(value, _mm_set1_epi16(1)), _mm_srli_epi16(value, 8)), 8);
}
Run Code Online (Sandbox Code Playgroud)
对于AVX2:
inline __m256i DivideI16By255(__m256i value)
{
return _mm256_srli_epi16(_mm256_add_epi16(
_mm256_add_epi16(value, _mm256_set1_epi16(1)), _mm256_srli_epi16(value, 8)), 8);
}
Run Code Online (Sandbox Code Playgroud)
对于Altivec(电力):
typedef __vector int16_t v128_s16;
const v128_s16 K16_0001 = {1, 1, 1, 1, 1, 1, 1, 1};
const v128_s16 K16_0008 = {8, 8, 8, 8, 8, 8, 8, 8};
inline v128_s16 DivideBy255(v128_s16 value)
{
return vec_sr(vec_add(vec_add(value, K16_0001), vec_sr(value, K16_0008)), K16_0008);
}
Run Code Online (Sandbox Code Playgroud)
对于NEON(ARM):
inline int16x8_t DivideI16By255(int16x8_t value)
{
return vshrq_n_s16(vaddq_s16(
vaddq_s16(value, vdupq_n_s16(1)), vshrq_n_s16(value, 8)), 8);
}
Run Code Online (Sandbox Code Playgroud)
如果您希望所有案例都得到完全正确的结果,请遵循Marc Glisse关于Anton链接的问题的建议:SSE整数除法?
使用GNU C本机向量语法来表达给定标量的向量分割,并查看它在Godbolt编译器资源管理器上的作用:
typedef unsigned short vec_u16 __attribute__((vector_size(16)));
vec_u16 divu255(vec_u16 x){ return x/255; } // unsigned division
#gcc5.5 -O3 -march=haswell
divu255:
vpmulhuw xmm0, xmm0, XMMWORD PTR .LC3[rip] # _mm_set1_epi16(0x8081)
vpsrlw xmm0, xmm0, 7
ret
Run Code Online (Sandbox Code Playgroud)
内在版本:
// UNSIGNED division with intrinsics
__m128i div255_epu16(__m128i x) {
__m128i mulhi = _mm_mulhi_epu16(x, _mm_set1_epi16(0x8081));
return _mm_srli_epi16(mulhi, 7);
}
Run Code Online (Sandbox Code Playgroud)
如果您在前端吞吐量或英特尔CPU上的端口0吞吐量方面遇到瓶颈,那么只有2 uop,这比@ ermlg的答案具有更好的吞吐量(但延迟更差).(与往常一样,当您将其用作更大功能的一部分时,它取决于周围的代码.) http://agner.org/optimize/
向量移位仅在英特尔芯片上的端口0上运行,因此@ ermlg的2个移位+ 1在端口0上添加瓶颈.(同样取决于周围的代码).这是3 uops vs. 2.
在Skylake上,pmulhuw/ pmulhw在端口0或1上运行,因此它可以与移位并行运行.(但是在Broadwell和之前,它们仅在端口0上运行,与移位相冲突.因此,英特尔预Skylake的唯一优势是前端的总uop和跟踪的无序执行.) pmulhuw在英特尔上有5个周期延迟,而在移位时有1个周期延迟,但是当你可以节省uops以获得更多吞吐量时,OoO exec通常可以隐藏几个周期更多的延迟.
Ryzen也只在P0上运行了pmulhuw,但在P2上进行了转换,所以它非常适合这一点.
typedef short vec_s16 __attribute__((vector_size(16)));
vec_s16 div255(vec_s16 x){ return x/255; } // signed division
; function arg x starts in xmm0
vpmulhw xmm1, xmm0, XMMWORD PTR .LC3[rip] ; a vector of set1(0x8081)
vpaddw xmm1, xmm1, xmm0
vpsraw xmm0, xmm0, 15 ; 0 or -1 according to the sign bit of x
vpsraw xmm1, xmm1, 7 ; shift the mulhi-and-add result
vpsubw xmm0, xmm1, xmm0 ; result += (x<0)
.LC3:
.value -32639
.value -32639
; repeated
Run Code Online (Sandbox Code Playgroud)
冒着臃肿的答案的风险,这里又是内在的:
// SIGNED division
__m128i div255_epi16(__m128i x) {
__m128i tmp = _mm_mulhi_epi16(x, _mm_set1_epi16(0x8081));
tmp = _mm_add_epi16(tmp, x); // There's no integer FMA that's usable here
x = _mm_srai_epi16(x, 15); // broadcast the sign bit
tmp = _mm_srai_epi16(tmp, 7);
return _mm_sub_epi16(tmp, x);
}
Run Code Online (Sandbox Code Playgroud)
在godbolt输出中,请注意gcc足够智能,可以在内存中使用相同的16B常量,set1以及它为自己生成的常量div255.AFAIK,这就像字符串常量合并一样.