65.9K
CodeProject 正在变化。 阅读更多。
Home

内积实验:CPU、FPU 与 SSE*

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.57/5 (16投票s)

2007年10月25日

GPL3

2分钟阅读

viewsIcon

72932

downloadIcon

860

本文演示了使用 shorts、ints、floats 和 doubles 进行内积运算,并与 CPU/FPU 和 SSE 进行比较。

引言

内积(或点积、标量积)运算是数字信号处理领域中的主要运算。它被广泛应用于傅里叶变换(FFT、DCT)、小波分析、滤波运算等。借助 SSE 技术的进步,您可以并行化此运算,以便立即对多个数字执行乘法和加法运算。但是,在计算中选择哪种精度,整数、浮点数还是双精度浮点数? 在本文中,我演示了对 shorts、ints、floats、doubles 进行内积运算,分别使用 CPU 和 SSE/SSE2/SSE3 优化的版本。

背景(可选)

您需要了解内积运算和 SSE 技术。我喜欢维基百科,因为它提供了所有问题的答案,请参阅 内积。 简而言之,您需要获取 2 个 std::vector 数组(floats、ints、shorts、doubles),长度相等,逐元素相乘,然后将结果向量中的条目相加,生成一个数字。对于 SSE 编程,codeproject 上有一篇不错的文章 SSE 编程入门

使用代码

只需运行控制台应用程序,并将数组的长度作为第一个参数传递给它。它会创建 2 个相同长度且包含随机条目的向量,并计算它们的内积,打印结果和处理时间,分别针对 chars、shorts/shorts SSE2、ints、floats/floats SSE/floats SSE3、doubles/doubles SSE2。

>inner.exe 5000000
 chars          processing time: 13 ms
 -119
 shorts         processing time: 17 ms
 3241
 shorts sse2    processing time: 6 ms
 3241
 ints           processing time: 16 ms
 786540
 floats         processing time: 30 ms
 1339854
 sse2 intrin    processing time: 13 ms
 1339854
 sse3 assembly  processing time: 12 ms
 1339854
 doubles        processing time: 22 ms
 1107822
 doubles sse2   processing time: 25 ms
 1107822

这是对大小为 5000000 的 2 个向量的内积提供的结果。每种类型后的第二行表示运算的四舍五入结果。我是在我的 AMD turion 64 2.2Ghz 处理器上运行的。我们可以看到,使用 SSE2 的 short 精度是最快的。SSE3 中的新型 haddps 指令允许比 SSE2 更快的处理。使用 FPU 执行的双精度运算优于 FPU 浮点数。但是,SSE2 优化双精度浮点数会降低计算速度,与浮点数相比,使用 SSE 可以提高超过 2 倍的速度。

如果您对图像数据执行 DSP,请使用 SSE2 和 shorts,使用整数滤波器,或使用浮点数。不要优化双精度浮点数,这会导致性能下降。如果您不打算使用 SSE 优化,请使用双精度浮点数,它比浮点数更快。

我没有在其他处理器上尝试过,如果您的结果不同,请告诉我。下面是执行内积运算的代码

在浮点数上使用 FPU

std::vector<float< v1;
std::vector<float> v2;
v1.resize(size);
v2.resize(size);
float* pv1 = &v1[0];
float* pv2 = &v2[0];

for (unsigned int i = 0; i < size; i++) {
        pv1[i] = float(rand() % 64 - 32);
        pv2[i] = float(rand() % 64 - 32);
}

float sum = 0;
for (unsigned int i = 0; i < size; i++)
    sum += pv1[i] * pv2[i];        
wprintf(L" %d\n", (int)sum);

SSE2 优化的 shorts

short sse2_inner_s(const short* p1, const short* p2, unsigned int size)
{
        __m128i* mp1 = (__m128i *)p1;
        __m128i* mp2 = (__m128i *)p2;
        __m128i mres = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, 0);
        
        for(unsigned int i = 0; i < size/8; i++) {                 
                __m128i mtmp = _mm_mullo_epi16(_mm_loadu_si128(mp1),
                    _mm_loadu_si128(mp2)); 
                mres = _mm_add_epi16(mres, mtmp);
                mp1++;
                mp2++;
        }

        short res[8];
        __m128i* pmres = (__m128i *)res;
        _mm_storeu_si128(pmres, mres);

        return res[0]+res[1]+res[2]+res[3]+res[4]+res[5]+res[6]+res[7];
}

SSE 优化的浮点数

float sse_inner(const float* a, const float* b, unsigned int size)
{
        float z = 0.0f, fres = 0.0f;
        __declspec(align(16)) float ftmp[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
        __m128 mres;

        if ((size / 4) != 0) {
                mres = _mm_load_ss(&z);
                for (unsigned int i = 0; i < size / 4; i++)
                        mres = _mm_add_ps(mres, _mm_mul_ps(_mm_loadu_ps(&a[4*i]),
                        _mm_loadu_ps(&b[4*i])));

                //mres = a,b,c,d
                __m128 mv1 = _mm_movelh_ps(mres, mres);     //a,b,a,b
                __m128 mv2 = _mm_movehl_ps(mres, mres);     //c,d,c,d
                mres = _mm_add_ps(mv1, mv2);                //res[0],res[1]

                _mm_store_ps(ftmp, mres);                

                fres = ftmp[0] + ftmp[1];
        }

        if ((size % 4) != 0) {
                for (unsigned int i = size - size % 4; i < size; i++)
                        fres += a[i] * b[i];
        }

        return fres;
}

SSE3 优化的浮点数

float sse3_inner(const float* a, const float* b, unsigned int size)
{
        float z = 0.0f, fres = 0.0f;
        
        if ((size / 4) != 0) {
                const float* pa = a;
                const float* pb = b;
                __asm {
                        movss   xmm0, xmmword ptr[z]
                }
                for (unsigned int i = 0; i < size / 4; i++) {
                        __asm {
                                mov     eax, dword ptr[pa]
                                mov     ebx, dword ptr[pb]
                                movups  xmm1, [eax]
                                movups  xmm2, [ebx]
                                mulps   xmm1, xmm2
                                addps   xmm0, xmm1
                        }
                        pa += 4;
                        pb += 4;
                }  
                __asm {
                        haddps  xmm0, xmm0
                        haddps  xmm0, xmm0
                        movss   dword ptr[fres], xmm0                        
                }                
        }

        return fres;
}

SSE 优化的双精度浮点数

double sse_inner_d(const double* a, const double* b, unsigned int size)
{
        double z = 0.0, fres = 0.0;
        __declspec(align(16)) double ftmp[2] = { 0.0, 0.0 };
        __m128d mres;
        
        if ((size / 2) != 0) {
                mres = _mm_load_sd(&z);
                for (unsigned int i = 0; i < size / 2; i++)
                        mres = _mm_add_pd(mres, _mm_mul_pd(_mm_loadu_pd(&a[2*i]),
                        _mm_loadu_pd(&b[2*i])));                

                _mm_store_pd(ftmp, mres);                

                fres = ftmp[0] + ftmp[1];
        }

        if ((size % 2) != 0) {
                for (unsigned int i = size - size % 2; i < size; i++)
                        fres += a[i] * b[i];
        }

        return fres;
}
© . All rights reserved.