(OpenCV RC1) 为什么Mat矩阵乘法比逐像素乘法慢20倍?

8
// 700 ms
cv::Mat in(height,width,CV_8UC1);
in /= 4;

替换为

//40 ms
cv::Mat in(height,width,CV_8UC1);
for (int y=0; y < in.rows; ++y)
{
    unsigned char* ptr = in.data + y*in.step1();
    for (int x=0; x < in.cols; ++x)
    {
        ptr[x] /= 4;
    }
}

什么可能引起这样的行为?是由于opencv将Scalar乘法“升级”为Mat乘法,还是由于针对arm的特定失败优化?(启用了NEON)。

你可以尝试使用 *= 1.0f/4.0; 吗?顺便提一下,你没初始化这些元素。 - Micka
在我对每个像素和整个矩阵乘法的测试中,浮点数乘法的结果与整数除法相同,误差/差异约为20%。 - Boyko Perfanov
你能运行perf吗?https://perf.wiki.kernel.org/index.php/Main_Page - auselen
1
也许你可以确认一下,但似乎cv::Mat仅支持双精度标量值的operator /。因此,您机器上的双精度除法计算可能比整数除法慢约20倍? - Micka
你正在使用OpenCV的调试版或发布版库吗?你是在调试模式还是发布模式下编译代码?是否启用了优化、强制单精度等选项?我们所讨论的矩阵大小是什么样的? - Micka
为什么不发布反汇编代码呢?没有它们,我们只能猜测。 - Jake 'Alquimista' LEE
2个回答

2

这是一个非常久远的问题(我几年前就报告了),许多基本操作都需要额外的时间,不仅是除法,还有加法、绝对值等等... 我不知道这种行为的真正原因。更奇怪的是,那些应该需要更长时间的操作,比如addWeighted,实际上非常高效。试试这个:

addWeighted(in, 1.0/4, in, 0, 0, in);

它可以每个像素执行多个操作,但运行速度比添加函数和循环实现快几倍。

这是我在错误跟踪器上的报告。


对于我的设置和OpenCV版本,addWeighted函数的速度相当慢,这是另一个问题。 - Boyko Perfanov
这更奇怪了,因为我刚刚检查过它并且发现addWeighted函数要快得多。你使用的OpenCV版本是哪个? - Michael Burdinov
还可以用其他函数做同样的实验吗?例如abs()。 - Michael Burdinov

1
尝试通过测量 CPU 时间来进行相同的操作。
int main()
{
    clock_t startTime;
    clock_t endTime;

    int height =1024;
    int width =1024;

    // 700 ms
    cv::Mat in(height,width,CV_8UC1, cv::Scalar(255));
    std::cout << "value: " << (int)in.at<unsigned char>(0,0) << std::endl;

    cv::Mat out(height,width,CV_8UC1);

    startTime = clock();
    out = in/4;
    endTime = clock();
    std::cout << "1: " << (float)(endTime-startTime)/(float)CLOCKS_PER_SEC << std::endl;
    std::cout << "value: " << (int)out.at<unsigned char>(0,0) << std::endl;


    startTime = clock();
    in /= 4;
    endTime = clock();
    std::cout << "2: " <<  (float)(endTime-startTime)/(float)CLOCKS_PER_SEC << std::endl;
    std::cout << "value: " << (int)in.at<unsigned char>(0,0) << std::endl;

    //40 ms
    cv::Mat in2(height,width,CV_8UC1, cv::Scalar(255));

    startTime = clock();
    for (int y=0; y < in2.rows; ++y)
    {
        //unsigned char* ptr = in2.data + y*in2.step1();
        unsigned char* ptr = in2.ptr(y);
        for (int x=0; x < in2.cols; ++x)
        {
            ptr[x] /= 4;
        }
    }
    std::cout << "value: " << (int)in2.at<unsigned char>(0,0) << std::endl;

    endTime = clock();
    std::cout << "3: " <<  (float)(endTime-startTime)/(float)CLOCKS_PER_SEC << std::endl;


    cv::namedWindow("...");
    cv::waitKey(0);
}

带有结果:

value: 255
1: 0.016
value: 64
2: 0.016
value: 64
3: 0.003
value: 63

你会发现结果不同,可能是因为mat.divide()执行了浮点除法并进行了四舍五入。而你在更快的版本中使用了整数除法,这样速度更快但结果不同。
此外,在OpenCV计算中有一个saturate_cast,但我认为更大的计算负荷差别将是双精度除法。

你能否将4加上,每个元素乘以0.25?如果我没记错的话,这对我来说也很“快”,这表明除了flop/intop计算性能之外还有其他因素在起作用。 - Boyko Perfanov
在我的机器上,浮点数的乘除法大约是无符号字符除以4(顺便说一下,这是一个位移操作)的两倍时间。双精度除法与浮点数相同,但我目前不信任它 :) - Micka

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