【问题标题】:Can you tell me whats wrong with this SSE code and how to do it better?你能告诉我这个 SSE 代码有什么问题以及如何做得更好吗?
【发布时间】:2014-07-19 20:06:31
【问题描述】:

我是第一次学习 SSE 并尝试优化一些代码。我有两个问题。

  1. SSE 逻辑似乎有点不对劲。该代码用于重新采样音频,但它只会产生噪音,因此它在某处显然是错误的。

  2. 我认为没有我的帮助编译器会做得更好。

更新:我意识到 pos4 和 frac4 的初始值是错误的,我正在更新下面的代码。但是在其他地方仍然存在逻辑错误。

//我在SSE的尝试

typedef unsigned int ALuint;
typedef float ALfloat;

static __inline __m128 lerp_SSE( const __m128 mu, const __m128 val1, const __m128 val2 ) {
    /* val1 + (val2-val1)*mu */
    const __m128 r0 = _mm_sub_ps( val2, val1 );
    const __m128 r1 = _mm_mul_ps( mu, r0 );
    const __m128 r2 = _mm_add_ps( val1, r1 );

    return r2;
}


void Resample_lerp32_SSE(const ALfloat *data, ALuint frac,
  ALuint increment, ALfloat *RESTRICT OutBuffer, ALuint BufferSize)
{
    ALuint i;
    ALuint pos;
    ALuint pos0_tmp = 0;
    ALuint frac0_tmp = frac;

    ALuint frac0_incr = frac0_tmp + increment;
    ALuint pos1_tmp = frac0_incr>>FRACTIONBITS;
    ALuint frac1_tmp = (frac0_incr & FRACTIONMASK);

    ALuint frac1_incr = frac1_tmp + increment;
    ALuint pos2_tmp = pos1_tmp + (frac1_incr>>FRACTIONBITS);
    ALuint frac2_tmp = (frac1_incr & FRACTIONMASK);

    ALuint frac2_incr = frac2_tmp + increment;
    ALuint pos3_tmp = pos2_tmp + (frac2_incr>>FRACTIONBITS);
    ALuint frac3_tmp = (frac2_incr & FRACTIONMASK);

    __m128i frac4 = _mm_set_epi32(frac3_tmp, frac2_tmp, frac1_tmp, frac0_tmp);
    const __m128i increment4 = _mm_set1_epi32(increment);
    const __m128 fracOne4 = _mm_set1_ps(1.0f/FRACTIONONE);
    const __m128i fracMask4 = _mm_set1_epi32(FRACTIONMASK);

    __m128i pos4 = _mm_set_epi32(pos3_tmp, pos2_tmp, pos1_tmp, pos0_tmp);

    for(i = 0;i < BufferSize-3;i += 4)
    {
        __m128 val1 = _mm_set_ps(data[pos3_tmp], data[pos2_tmp], data[pos1_tmp], data[pos0_tmp]);
        __m128 val2 = _mm_set_ps(data[pos3_tmp+1], data[pos2_tmp+1], data[pos1_tmp+1], data[pos0_tmp+1]);

        __m128 mu = _mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4);
        __m128 out = lerp_SSE(mu, val1, val2);

        _mm_store_ps(&OutBuffer[i], out);

        frac4 = _mm_add_epi32(frac4, increment4);
        pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, FRACTIONBITS));
        frac4 = _mm_and_si128(frac4, fracMask4);

        pos0_tmp = _mm_extract_epi32(pos4, 0);
        pos1_tmp = _mm_extract_epi32(pos4, 1);
        pos2_tmp = _mm_extract_epi32(pos4, 2);
        pos3_tmp = _mm_extract_epi32(pos4, 3);
    }

    pos = pos0_tmp;

    for(;i < BufferSize;i++)
    {
        const ALfloat *vals = data + pos;
        OutBuffer[i] = lerp(vals[0], vals[1], frac * (1.0f/FRACTIONONE));

        frac += increment;
        pos  += frac>>FRACTIONBITS;
        frac &= FRACTIONMASK;
    }
}

//纯C版本

void Resample_lerp_C(const ALfloat *data, ALuint frac,
  ALuint increment, ALfloat *RESTRICT OutBuffer, ALuint BufferSize)
{                                                                             
    ALuint pos = 0;
    ALuint i;                                                                 

    for(i = 0;i < BufferSize+1;i++)                                           
    {                                                                         
        OutBuffer[i] = lerp32(data + pos, frac);                             

        frac += increment;                                                    
        pos  += frac>>FRACTIONBITS;                                           
        frac &= FRACTIONMASK;                                                 
    }                                                                         
}

【问题讨论】:

  • 这样的帖子适合codereview.stackexchange.com
  • @RSahu: codereview.stackexchange.com 用于 working 代码 - OP 说他的代码无法正常工作(优化是次要问题,因为没有必要尝试优化损坏的代码)。
  • @PaulR 感谢您指出不同之处。我没有意识到这一点。
  • 只是为了澄清非 SSE 代码工作正常。
  • 问题解决了我忙于仔细检查我使用了正确的内在函数,我没有注意到我没有将值增加正确的数量 const __m128i increment4 = _mm_set1_epi32(increment);需要为: const __m128i increment4 = _mm_set1_epi32(increment*4);

标签: c optimization assembly x86 sse


【解决方案1】:

如果您不尝试矢量化 pos 部分,您的代码可能会更快,因为您需要它来进行寻址。一次将数据输入和输出一个元素可能比仅以标量方式进行操作要慢。或者,如果您需要向量中的循环计数器,则使用标量循环计数器会更快,并且还可以更新向量循环计数器。 (例如,将一个向量 int 初始化为 { 0, 1, 2, 3 },然后在循环中添加 { 4, 4, 4, 4 }。)

这是一个非常老的问题,所以我不会花更多时间在这个问题上,但是循环中的两个_mm_set_ps 和 4 个_mm_extract_epi32 是一个大问题的明显迹象。

您的非向量 C 版本在迭代之间具有循环携带的依赖关系,但 dep 链实际上并不依赖于输入数据。所以你应该能够将frac的前4个值放入一个向量中,并在每次循环中添加4* increment

就从输入缓冲区收集数据而言,您可能可以使用向量加载,然后使用由pos 向量构造的掩码进行可变掩码洗牌。否则,您最好的选择可能是使用标量 movss / insertps 从可能不是 4 个连续浮点数的内存位置设置向量元素。

另外,这可能不是一个非常复杂的重采样算法。下采样时不会完全忽略一些输入数据值吗?周围有几个重采样库,包括我知道很好的 ​​librubberband。如果它们有适合您的许可证,请使用它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-06
    • 1970-01-01
    • 2020-02-21
    相关资源
    最近更新 更多