【问题标题】:how do I optimize this loop further?如何进一步优化这个循环?
【发布时间】:2011-12-08 06:35:59
【问题描述】:

嗯,这个帖子就是Why is memcpy() and memmove() faster than pointer increments?的原因

我有一个具有 4 个通道的自定义图像结构,我喜欢将其中的 3 个 (RGB) 提取到具有 3 个通道的 openCV cv::MAt 结构中。

cv::Mat GetColorImgFromFrame(Image* pimg)
{
    int iHeight = pimg->m_imageInfo.m_height;
    int iWidth = pimg->m_imageInfo.m_width;

    cv::Mat cvmColorImg = cv::Mat::zeros(iHeight, iWidth, CV_8UC3);
    int iDestStep = cvmColorImg.channels(); // 3

    uchar* pucSrc = pimg->m_data;    
    uchar* pucDest = cvmColorImg.data;
    uchar* pucDestLimit = cvmColorImg.data + iHeight*iWidth*iDestStep;

    for (;pucDest < pucDestLimit;)
    {       
        *(pucDest++) = *(pucSrc++);
        *(pucDest++) = *(pucSrc++);
        *(pucDest++) = *(pucSrc++);
        pucSrc++;
    }

    return cvmColorImg;
}

用 memcpy(pucDest, pucSrc, 3) 替换三个内部赋值似乎没有多大帮助。有什么建议?

【问题讨论】:

  • 你的代码C语言有语法错误,建议你不要尝试写多语言源文件:真的很难!
  • 只是好奇,为什么要使用for 循环,但将其简化为本质上的while 循环?
  • @pmg: 你能指出语法错误吗?谢谢!
  • @Peter:好吧,我在搞乱循环。它最初是一个 for 循环,但经过几次迭代后变成了现在的样子。:)
  • 惊讶于没有提出强制性问题:您确定这实际上是性能瓶颈吗?在我看来,您的平均 OpenCV 操作将比仅复制一些字节要慢得多。

标签: c++ for-loop


【解决方案1】:

在 32 位机器上,您可以尝试使用 unsigned int * 在每个循环从源到目标一次复制 4 个字节。诀窍是保持pucDest 和uchar 指针递增3。您将最后一个字作为一种特殊情况处理,而不是为了最后一个字节违反目标数组的数组边界。

由于在大多数机器上复制 int 通常不会比复制 char 慢,我怀疑这将使您的速度大约提高 3 倍。

【讨论】:

    【解决方案2】:

    在使用SIMD intrinsic 进入“类似汇编程序”模式之前,您可以尝试告诉编译器源指针和目标指针没有别名,例如使用带有 gcc 的 __restrict__ 关键字或带有视觉的 __restrict工作室

    【讨论】:

      【解决方案3】:

      扩展 Doc Brown 的答案,展开循环,以便每读取四次就写入三个 uint32_t 整数(uint64_t 需要更大的吞吐,但会更难处理)。

      如果您的pimg-&gt;m_data 和cvmColorImg.data 不在uint32_t 边界上开始,则读取和写入整数可能会出现问题。您可以通过在这些 char* 数据成员之前放置 int 或 unsigned int 数据成员来强制执行此操作。或者你可以使用类似于 Duff 的设备。个人偏好:我只会强制对齐。它使复制代码更清晰、更容易,甚至可能更快。为填充而浪费的几个字节是很小的代价。

      就像展开循环的典型情况一样,您必须做一些特殊的事情来处理结尾。 (Duff 的设备在开始时会处理奇怪的事情,所以这里不要使用 Duff 的设备。)如果您填充输出缓冲区的末尾以使其占用 4*N 字节,那么循环处理结束的许多问题都会消失.

      【讨论】:

      • +1 表示 4 字节对齐备注,但这仅适用于源数组(移动指针 3 字节时目标不会对齐访问)。我怀疑大多数现代 C++ 编译器都可以为你做循环展开作为优化,所以你不需要自己做。
      • 通过将int pad_before_data; 数据成员放在用作目标的数据成员之前,可以强制目标访问对齐。在目标之后放置一个int pad_after_data; 数据成员意味着可以在整个过程中复制ints。严格来说,在某个元素的结尾和下一个元素的开头之间写入编译器生成的焊盘是 UB,但我从未见过 UB 除了预期的行为之外做任何事情(它写入编译器生成的焊盘)。
      猜你喜欢
      • 2011-11-13
      • 2011-05-30
      • 2019-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-05
      • 2011-04-28
      相关资源
      最近更新 更多