用于 C++/SSE 代码的高效 NEON 内在函数

Posted

技术标签:

【中文标题】用于 C++/SSE 代码的高效 NEON 内在函数【英文标题】:Efficient NEON intrinsics for C++/SSE code 【发布时间】:2015-05-18 23:55:58 【问题描述】:

如何高效地将以下代码 sn-p 转换为 NEON 内部函数?

C++

int diff_scale, c0, c1;
cost = (short)(cost + std::min(c0, c1) >> diff_scale));

上海证券交易所

__m128i ds = _mm_cvtsi32_si128(diff_scale);
__m128i c0 = _mm_load_si128((__m128i*)(cost));
__m128i c1 = _mm_load_si128((__m128i*)(cost + 8));
__m128i z = _mm_setzero_si128();    
_mm_store_si128((__m128i*)(cost), _mm_adds_epi16(c0, _mm_srl_epi16(_mm_unpacklo_epi8(diff, z), ds)));
_mm_store_si128((__m128i*)(cost + 8), _mm_adds_epi16(c1, _mm_srl_epi16(_mm_unpackhi_epi8(diff, z), ds)));

【问题讨论】:

首先对纯 C++ 代码进行基准测试 - 您可能会发现它足够快,您不需要 SIMD (Neon) 版本。 我首先在 SSE 中写这个的原因是因为 C++ 很慢(比 SIMD 版本慢得多)。现在,我只是试图将其扩展到 ARM 设备。换句话说,是的,C++ 版本很慢,我确实需要 SIMD 版本。 :) 如果您认为 Neon 会产生与 SSE 一样大的影响,那么您可能会有些失望。 你能解释一下为什么吗?将此线性代码转换为 SIMD 可以极大地加快 Intel 架构的速度——那么为什么不在 ARM 上呢?请注意,这行代码虽然看起来很简单,但却被调用了数千次。 ARM 是一个更加简单的架构 - Neon 会提高一些速度,但它可能不会像 SSE 的改进那么显着。 【参考方案1】:

SSE 看起来应该是这样的:

int8x16_t ds = vdupq_n_s8(-diff_scale);
int16x8_t c0 = vld1q_s16(cost);
int16x8_t c1 = vld1q_s16(cost + 8);
uint8x16_t diff_ds = vshlq_u8(diff, ds);
#if defined(MAY_SATURATE)
vst1q_s16(cost,     vqaddq_s16(c0, vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(diff_ds)))));
vst1q_s16(cost + 8, vqaddq_s16(c1, vreinterpretq_s16_u16(vmovl_u8(vget_high_s8(diff_ds)))));
#else
vst1q_s16(cost,     vreinterpretq_s16_u16(vaddw_u16(vreinterpretq_u16_s16(c0), vget_low_u8(diff_ds))));
vst1q_s16(cost + 8, vreinterpretq_s16_u16(vaddw_u16(vreinterpretq_u16_s16(c1), vget_high_s8(diff_ds))));
#endif

C++ 需要对向量进行某种外推......也许是这样的?:

int diff_scale, c0[8], c1[8];
int32x4_t ds = vdupq_n_s32(-diff_scale);
int32x4_t c0lo = vld1q_s32(c0);
int32x4_t c0hi = vld1q_s32(c0 + 4);
int32x4_t c1lo = vld1q_s32(c1);
int32x4_t c1hi = vld1q_s32(c1 + 4);
int16x8_t c = vld1q_s16(cost);
c0lo = vshlq_s32(vaddw_s32(vminq_s32(c0lo, c1lo), vget_low_s16(c), ds);
c0hi = vshlq_s32(vaddw_s32(vminq_s32(c0hi, c1hi), vget_high_s16(c), ds);
vst1q_s16(cost, vcombine_s16(vmovn_s16(c0lo), vmovn_s16(c0hi)));

如果diff_scale 是一个常数,那么改为:

const int diff_scale = 1;
int c0[8], c1[8];
int32x4_t c0lo = vld1q_s32(c0);
int32x4_t c0hi = vld1q_s32(c0 + 4);
int32x4_t c1lo = vld1q_s32(c1);
int32x4_t c1hi = vld1q_s32(c1 + 4);
int16x8_t c = vld1q_s16(cost);
c = vcombine_s16(vshrn_n_s32(vaddw_s32(vminq_s32(c0lo, c1lo), vget_low_s16(c), diff_scale),
                 vshrn_n_s32(vaddw_s32(vminq_s32(c0hi, c1hi), vget_high_s16(c), diff_scale));
vst1q_s16(cost, c);

虽然这些看起来都可以通过更多的上下文变得更简单。

【讨论】:

以上是关于用于 C++/SSE 代码的高效 NEON 内在函数的主要内容,如果未能解决你的问题,请参考以下文章

使用 ARM NEON 执行比 C 代码需要更长的时间

使用 sse 和 avx 内在函数将一组打包的单曲添加到一个值中

用于灰度到 ARGB 转换的 C++ SSE2 或 AVX2 内在函数

SSE 内在函数优化

如何在 ARM NEON SIMD 内在函数上编写“a[i]=b[c[i]]”

用于比较 (_mm_cmpeq_ps) 和赋值操作的 SSE 内在函数