4
\$\begingroup\$

I'm learning SSE for the first time and trying to optimise some code. Using oprofile shows that the CPU usage in this function went down from 2.5% to 0.9% using the code I created. As I've only been reading up on SSE today, I'm sure my code can be improved. Any suggestions?

OProfile shows that time is most spent in:

  • .20% - _mm_set_ps
  • .10% - _mm_extract_epi32
  • .08% - _mm_store_ps
  • .08% - _mm_add_ps

My attempt at SSE:

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*4);
    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]);

        /* val1 + (val2-val1)*mu */
        const __m128 r0 = _mm_sub_ps(val2, val1);
        const __m128 mu = _mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4);
        const __m128 r1 = _mm_mul_ps(mu, r0);
        const __m128 out = _mm_add_ps(val1, r1);

        _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;
    frac = _mm_extract_epi32(frac4, 0);

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

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

Plain C version:

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;                                                 
    }                                                                         
}
\$\endgroup\$
  • \$\begingroup\$ similar Q from the same asker on stackoverflow.com/questions/23948232/…, where I made an attempt at answering. Not an easy thing to vectorize, due to the rounding-dependent horizontal data movement, I think. \$\endgroup\$ – Peter Cordes Jul 9 '15 at 20:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.