【问题标题】:Short to Float and viceversa conversion using NEON SIMD使用 NEON SIMD 进行短至浮点和反之亦然的转换
【发布时间】:2017-10-17 12:28:21
【问题描述】:

我在 Android 中处理音频缓冲区,我的设置如下:

  1. 使用短缓冲区获取系统回调
  2. 将短缓冲区转换为浮动缓冲区
  3. 用浮点缓冲区做一些 DSP
  4. 将浮点缓冲区转换为短缓冲区
  5. 向系统传递短缓冲区

我想减少第 2 步和第 4 步的延迟,即 short to float 和 float to short 转换。 (暂且不提 3-DSP 中的延迟,因为我稍后会处理)。

所以,我想使用 NEON SIMD 一次计算多个值。

我目前对 2 和 4 的代码如下:

#define CONV16BIT 32768
#define CONVMYFLT (1./32768.)
static int i;
float * floatBuffer;
short * shortInBuffer;
short * shortOutBuffer;

...(malloc and init buffers method)

...(inside callback) 
//2- short to float
for(i = 0; i < bufferSize; i++) {
    floatBuffer[i] = (float) (shortInBuffer[i] * CONVMYFLT);
}

...(do dsp)

//4- float to short
for(i = 0; i < bufferSize; i++) {
    shortOutBuffer[i] = (short) (floatBuffer[i] * CONV16BIT);
}

我认为利用 NEON 所需的步骤是:

(短到浮动部分)

  1. 从短缓冲区加载 16 位短裤
  2. 将它们转换为 32 位整数
  3. 将它们转换为浮动
  4. 将它们乘以 CONVMYFLT
  5. 将它们存储到浮动缓冲区中

post 中找到此信息(选择答案)

__m128 factor = _mm_set1_ps(1.0f / value);
for (int i = 0; i < W*H; i += 8)
{
    //  Load 8 16-bit ushorts.
    //  vi = {a,b,c,d,e,f,g,h}
    __m128i vi = _mm_load_si128((const __m128i*)(source + i));

    //  Convert to 32-bit integers
    //  vi0 = {a,0,b,0,c,0,d,0}
    //  vi1 = {e,0,f,0,g,0,h,0}
    __m128i vi0 = _mm_cvtepu16_epi32(vi);
    __m128i vi1 = _mm_cvtepu16_epi32(_mm_unpackhi_epi64(vi,vi));

    //  Convert to float
    __m128 vf0 = _mm_cvtepi32_ps(vi0);
    __m128 vf1 = _mm_cvtepi32_ps(vi1);

    //  Multiply
    vf0 = _mm_mul_ps(vf0,factor);
    vf1 = _mm_mul_ps(vf1,factor);

    //  Store
    _mm_store_ps(destination + i + 0,vf0);
    _mm_store_ps(destination + i + 4,vf1);
}

但是,这是英特尔 SSE4.1 的 SIMD,而不是 NEON。

NEON 在 Android 中的等效实现是什么?(很难理解 NEON 内在函数)

更新 1 根据 fsheikh 的回答,我能够构建这个: - 我能够从系统回调中获取 int16_t - 我所有的缓冲区大小都是 8 的倍数:

int16x8_t i16v;
int32x4_t i32vl, i32vh;
float32x4_t f32vl, f32vh;
for(i = 0; i < bufferSize; i += 8) {
    //load 8 16-bit lanes on vector
    i16v = vld1q_s16((const int16x8_t*) int16_t_inBuffer[i]);
    // convert into 32-bit signed integer
    i32vl = vmovl_s16 (i16v);
    i32vh = vmovl_s16 (vzipq_s16(i16v, i16v).val[0]);
    //convert to 32-bit float
    f32vl = vcvtq_f32_s32(i32vl);
    f32vh = vcvtq_f32_s32(i32vh);
    //multiply by scalar
    f32vl = vmulq_n_f32(f32vl, CONVMYFLT);
    f32vh = vmulq_n_f32(f32vh, CONVMYFLT);
    //store in float buffer
    vst1q_f32(floatBuffer[i], f32vl);
    vst1q_f32(floatBuffer[i + 4], f32vh);
}

这应该可行吗? 我怀疑我应该使用 vmovl_s16 返回的交错向量的低部分还是高部分:

i32vh = vmovl_s16 (vzipq_s1 6(i16v, i16v).val[0]);或

i32vh = vmovl_s16 (vzipq_s16(i16v, i16v).val[1]);

【问题讨论】:

  • 当一个称职的编译器可以轻松地自动向量化时,为什么还要麻烦呢?
  • @EOF 因为我有一个瓶颈我想测量 c 实现和 NEON 内在函数之间的循环速度,我已经阅读了各种帖子,其中建议一个或另一个应该更快或不,我想检查一下。

标签: android arm simd neon


【解决方案1】:

获得 SSE 版本后,您可以使用 GCC ARM NEON 内部函数列表将 SSE 宏移植到 NEON。 https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/ARM-NEON-Intrinsics.html

例如:

// Load unsigned short
uint16x4_t vld1_u16 (const uint16_t *)

// Convert to unsigned int
uint32x4_t vmovl_u16 (uint16x4_t) 

// Convert to float
float32x4_t vcvtq_f32_s32 (int32x4_t)

// Multiply floats with a scalar
float32x4_t vmulq_n_f32 (float32x4_t, float32_t) 

// Store results into a float buffer
void vst1q_f32 (float32_t *, float32x4_t)

【讨论】:

  • 根据您的回答我更新了问题,您会检查一下吗? *因为我的缓冲区是 8 的倍数并且已签名,所以我决定使用 int16x8_t
  • @alexm 我记得有类似 vmov_s16(vget_low_s16(i16v)) 的东西,您可以使用它来消除交错向量元素的歧义。 PS:如果原始问题的答案有用,您可以将其标记为已接受。谢谢!
  • 没错,我刚刚更新了我的代码。 (将阅读更多关于 NEON 的内在函数)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-15
相关资源
最近更新 更多