为什么我的SSE代码没有比C/C++代码更快?

3

我刚开始使用 SSE 优化我的代码,用于一个计算机视觉项目,旨在检测图像中的肤色。以下是我的函数。该函数接受一张彩色图像,并查看每个像素并返回一个概率映射。注释掉的代码是我的原始 C++ 实现,其余部分是 SSE 版本。我对它们都进行了定时,奇怪的是发现 SSE 比我的原始 C++ 代码更快。有关发生了什么或如何进一步优化函数的任何建议吗?

void EvalSkinProb(const Mat& cvmColorImg, Mat& cvmProb)
{
    std::clock_t ts = std::clock();  
    Mat cvmHSV = Mat::zeros(cvmColorImg.rows, cvmColorImg.cols, CV_8UC3);
    cvtColor(cvmColorImg, cvmHSV, CV_BGR2HSV);
    std::clock_t te1 = std::clock(); 

    float fFG, fBG;
    double dp;

    __declspec(align(16)) int frgb[4] = {0};
    __declspec(align(16)) int fBase[4] = {g_iLowHue, g_iLowSat, g_iLowVal, 0};
    __declspec(align(16)) int fIndx[4] = {0};
    __m128i* pSrc1 = (__m128i*) frgb;
    __m128i* pSrc2 = (__m128i*) fBase;
    __m128i* pDest = (__m128i*) fIndx;
    __m128i m1;

    for (int y = 0; y < cvmColorImg.rows; y++)
    {
        for (int x = 0; x < cvmColorImg.cols; x++)
        {
            cv::Vec3b hsv = cvmHSV.at<cv::Vec3b>(y, x);
            frgb[0] = hsv[0];hsv[1] = hsv[1];hsv[2] =hsv[2];
            m1 = _mm_sub_epi32(*pSrc1, *pSrc2);
            *pDest = _mm_srli_epi32(m1, g_iSValPerbinBit); 

            // c++ code
            //fIndx[0] = ((hsv[0]-g_iLowHue)>>g_iSValPerbinBit);
            //fIndx[1] = ((hsv[1]-g_iLowSat)>>g_iSValPerbinBit);
            //fIndx[2] = ((hsv[2]-g_iLowVal)>>g_iSValPerbinBit);

            fFG = m_cvmSkinHist.at<float>(fIndx[0], fIndx[1], fIndx[2]);
            fBG = m_cvmBGHist.at<float>(fIndx[0], fIndx[1], fIndx[2]);
            dp = (double)fFG/(fBG+fFG);
            cvmProb.at<double>(y, x) = dp;          
        }
    }
    std::clock_t te2 = std::clock();  
    double dSecs1 = (double)(te1-ts)/(CLOCKS_PER_SEC);
    double dSecs2 = (double)(te2-te1)/(CLOCKS_PER_SEC);
}

你对这段代码进行了性能分析吗(在修改前和修改后)?我强烈怀疑你所更改的部分对总成本几乎没有贡献(我怀疑那3个函数调用更加耗费资源)。 - Walter
3个回答

8
这里的第一个问题是,你在处理大量数据移动时没有进行足够的SSE操作。你会花费大部分时间在SSE寄存器中打包/解包数据,仅用了2个指令...

其次,这段代码存在非常微妙的性能损失。

你正在使用缓冲区在变量和SSE寄存器之间传输数据。这是一个严重的错误

原因在于CPU的加载/存储单元。当你将数据写入内存位置,并立即尝试以不同的字长读取它时,通常会强制刷新并重新读取数据。这可能会导致20多个周期的惩罚。

这是因为CPU的加载/存储单元不适合这种不寻常的访问方式。

你正在使用缓冲区在变量和SSE寄存器之间传输数据。这是一个大忌讳。那么我该如何改进/修复它? - wanderer
通常情况下,您可以使用move或set内在函数来实现。然而,我非常怀疑SSE会帮助您的代码,因为您只有2条指令……如果您能暴露更多的向量化,那也许可以。 - Mysticial
此外,在那个内部循环中,您似乎有4个函数调用(可能已经内联)。我不知道它们的作用,但如果您想要更好的性能,您可以考虑手动内联这些函数,并查看其中是否有任何内容也可以进行矢量化。每次像那样打包/解包数据时都会有很多开销。因此,如果您要矢量化一部分,则应在解包和存储数据之前尽可能多地执行操作。 - Mysticial

1

我对OpenCV不是很熟悉,但我怀疑如果你确保在循环之外对访问的数据进行对齐,而不是在循环内部加载未对齐的数据,那么你才能获得良好的吞吐量。


0

没有测试过,但应该可以给你一些想法:

auto base = _mm_set_epi32(g_iLowHue, g_iLowSat, g_iLowVal, 0);

for (int y = 0; y < cvmColorImg.rows; y++)
{
    for (int x = 0; x < cvmColorImg.cols; x++)
    {              
        auto hsv = _mm_loadu_si128(&cvmHSV.at<cv::Vec3b>(y, x)[0]); // Would be better if cvmHSV was aligned in which case _mm_load_si128 is faster     
        auto m1  = _mm_sub_epi32(hsv, base);
        auto m2  = _mm_srli_epi32(m1, g_iSValPerbinBit); 

        auto fFG = static_cast<double>(m_cvmSkinHist.at<float>(m2.m128i_i32[0], m2.m128i_i32[1], m2.m128i_i32[2]));
        auto fBG = static_cast<double>(m_cvmBGHist.at<float>(m2.m128i_i32[0], m2.m128i_i32[1], m2.m128i_i32[2]));         
        cvmProb.at<double>(y, x) = fFG/(fBG+fFG);                         
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接