【问题标题】:Optimizing C++ code for performance优化 C++ 代码以提高性能
【发布时间】:2010-09-08 13:59:27
【问题描述】:

你能想出一些方法来优化这段代码吗?它旨在在 ARMv7 处理器 (Iphone 3GS) 中执行:

4.0%  inline float BoxIntegral(IplImage *img, int row, int col, int rows, int cols) 
      {
0.7%    float *data = (float *) img->imageData;
1.4%    int step = img->widthStep/sizeof(float);

        // The subtraction by one for row/col is because row/col is inclusive.
1.1%    int r1 = std::min(row,          img->height) - 1;
1.0%    int c1 = std::min(col,          img->width)  - 1;
2.7%    int r2 = std::min(row + rows,   img->height) - 1;
3.7%    int c2 = std::min(col + cols,   img->width)  - 1;

        float A(0.0f), B(0.0f), C(0.0f), D(0.0f);
8.5%    if (r1 >= 0 && c1 >= 0) A = data[r1 * step + c1];
11.7%   if (r1 >= 0 && c2 >= 0) B = data[r1 * step + c2];
7.6%    if (r2 >= 0 && c1 >= 0) C = data[r2 * step + c1];
9.2%    if (r2 >= 0 && c2 >= 0) D = data[r2 * step + c2];

21.9%   return std::max(0.f, A - B - C + D);
3.8%  }

所有这些代码都取自 OpenSURF 库。这是函数的上下文(有些人要求上下文):

//! Calculate DoH responses for supplied layer
void FastHessian::buildResponseLayer(ResponseLayer *rl)
{
  float *responses = rl->responses;         // response storage
  unsigned char *laplacian = rl->laplacian; // laplacian sign storage
  int step = rl->step;                      // step size for this filter
  int b = (rl->filter - 1) * 0.5 + 1;         // border for this filter
  int l = rl->filter / 3;                   // lobe for this filter (filter size / 3)
  int w = rl->filter;                       // filter size
  float inverse_area = 1.f/(w*w);           // normalisation factor
  float Dxx, Dyy, Dxy;

  for(int r, c, ar = 0, index = 0; ar < rl->height; ++ar) 
  {
    for(int ac = 0; ac < rl->width; ++ac, index++) 
    {
      // get the image coordinates
      r = ar * step;
      c = ac * step; 

      // Compute response components
      Dxx = BoxIntegral(img, r - l + 1, c - b, 2*l - 1, w)
          - BoxIntegral(img, r - l + 1, c - l * 0.5, 2*l - 1, l)*3;
      Dyy = BoxIntegral(img, r - b, c - l + 1, w, 2*l - 1)
          - BoxIntegral(img, r - l * 0.5, c - l + 1, l, 2*l - 1)*3;
      Dxy = + BoxIntegral(img, r - l, c + 1, l, l)
            + BoxIntegral(img, r + 1, c - l, l, l)
            - BoxIntegral(img, r - l, c - l, l, l)
            - BoxIntegral(img, r + 1, c + 1, l, l);

      // Normalise the filter responses with respect to their size
      Dxx *= inverse_area;
      Dyy *= inverse_area;
      Dxy *= inverse_area;

      // Get the determinant of hessian response & laplacian sign
      responses[index] = (Dxx * Dyy - 0.81f * Dxy * Dxy);
      laplacian[index] = (Dxx + Dyy >= 0 ? 1 : 0);

#ifdef RL_DEBUG
      // create list of the image coords for each response
      rl->coords.push_back(std::make_pair<int,int>(r,c));
#endif
    }
  }
}

一些问题:
函数是内联的是个好主意吗? 使用内联汇编会显着提高速度吗?

【问题讨论】:

  • 这两个问题的唯一正确答案是:测量。
  • 是的,看看最近的 C++ 问题 - 有一个关于向量与数组的速度的问题 - 代码显示了如何使用加速计时器进行分析。您还可以查看 graphics.stanford.edu/~seander/bithacks.html - 那里的许多小技巧可以提供更快的做事方式。内联汇编 - 也许 - 我不知道那个 CPU 所以不能说。
  • 为什么 r1,r2,c1,c2 中的任何一个都是负数?这些测试都应该是多余的。

标签: c++ iphone performance optimization


【解决方案1】:

专门针对边缘,这样您就不需要在每一行和每一列中检查它们。我假设这个调用在一个嵌套循环中并且被调用了很多。这个函数会变成:

inline float BoxIntegralNonEdge(IplImage *img, int row, int col, int rows, int cols) 
{
  float *data = (float *) img->imageData;
  int step = img->widthStep/sizeof(float);

  // The subtraction by one for row/col is because row/col is inclusive.
  int r1 = row - 1;
  int c1 = col - 1;
  int r2 = row + rows - 1;
  int c2 = col + cols - 1;

  float A(data[r1 * step + c1]), B(data[r1 * step + c2]), C(data[r2 * step + c1]), D(data[r2 * step + c2]);

  return std::max(0.f, A - B - C + D);
}

你摆脱了每个 min 的条件和分支,以及两个条件和每个 if 的分支。如果您已经满足条件,则只能调用此函数 - 在调用者中检查整个行而不是每个像素。

当您必须对每个像素进行处理时,我写了一些优化图像处理的技巧:

http://www.atalasoft.com/cs/blogs/loufranco/archive/2006/04/28/9985.aspx

博客中的其他内容:

  1. 您正在使用 2 次乘法(索引是乘法)重新计算图像数据中的位置 -- 您应该递增一个指针。

  2. 不是传递 img、row、row、col 和 cols,而是传递指向要处理的确切像素的指针——这是通过递增指针而不是索引获得的。

  3. 如果上面不做,step对所有像素都是一样的,在caller中计算并传入。如果做1和2,就完全不需要step了。

【讨论】:

    【解决方案2】:

    有一些地方可以重用临时变量,但它是否会提高性能,必须按照急切的说法来衡量:

    改变

      if (r1 >= 0 && c1 >= 0) A = data[r1 * step + c1]; 
      if (r1 >= 0 && c2 >= 0) B = data[r1 * step + c2]; 
      if (r2 >= 0 && c1 >= 0) C = data[r2 * step + c1]; 
      if (r2 >= 0 && c2 >= 0) D = data[r2 * step + c2]; 
    

      if (r1 >= 0) {
        int r1Step = r1 * step;
        if (c1 >= 0) A = data[r1Step + c1]; 
        if (c2 >= 0) B = data[r1Step + c2]; 
      }
      if (r2 >= 0) {
        int r2Step = r2 * step;
        if (c1 >= 0) C = data[r2Step + c1]; 
        if (c2 >= 0) D = data[r2Step + c2]; 
      }
    

    如果你的 if 语句很少提供真值,你实际上可能会过于频繁地进行临时乘法运算。

    【讨论】:

    • 如果使用了适当的优化标志,这将被自动处理
    【解决方案3】:

    您对ABCD 四个变量不感兴趣,而只对A - B - C + D 的组合感兴趣。

    试试

    float result(0.0f);
    if (r1 >= 0 && c1 >= 0) result += data[r1 * step + c1];
    if (r1 >= 0 && c2 >= 0) result -= data[r1 * step + c2];
    if (r2 >= 0 && c1 >= 0) result -= data[r2 * step + c1];
    if (r2 >= 0 && c2 >= 0) result += data[r2 * step + c2];
    
    if (result > 0f) return result;
    return 0f;
    

    【讨论】:

    • @Steve:你当​​然是对的......我记得std::max 函数用于建立最小值,从那时起我所有的推理都是倒退的。
    【解决方案4】:

    编译器可能会在适当的地方自动处理 inling。

    没有任何关于上下文的知识。 if(r1 >= 0 && c1 >= 0) 检查是否必要?

    是否要求row和col参数>0?

    float BoxIntegral(IplImage *img, int row, int col, int rows, int cols) 
    {
      assert(row > 0 && col > 0);
      float *data = (float*)img->imageData; // Don't use C-style casts
      int step = img->widthStep/sizeof(float);
    
      // Is the min check rly necessary?
      int r1 = std::min(row,          img->height) - 1;
      int c1 = std::min(col,          img->width)  - 1;
      int r2 = std::min(row + rows,   img->height) - 1;
      int c2 = std::min(col + cols,   img->width)  - 1;
    
      int r1_step = r1 * step;
      int r2_step = r2 * step;
    
      float A = data[r1_step + c1];
      float B = data[r1_step + c2];
      float C = data[r2_step + c1];
      float D = data[r2_step + c2];
    
      return std::max(0.0f, A - B - C + D);
    }
    

    【讨论】:

      【解决方案5】:

      一些示例说直接初始化ABCD 并使用0 跳过初始化,但这在某些方面与您的原始代码在功能上有所不同。但是我会这样做:

      inline float BoxIntegral(IplImage *img, int row, int col, int rows, int cols)  {
      
          const float *data = (float *) img->imageData;
          const int step = img->widthStep/sizeof(float);
      
          // The subtraction by one for row/col is because row/col is inclusive.
          const int r1 = std::min(row,          img->height) - 1;
          const int r2 = std::min(row + rows,   img->height) - 1;
          const int c1 = std::min(col,          img->width)  - 1;
          const int c2 = std::min(col + cols,   img->width)  - 1;
      
          const float A = (r1 >= 0 && c1 >= 0) ? data[r1 * step + c1] : 0.0f;
          const float B = (r1 >= 0 && c2 >= 0) ? data[r1 * step + c2] : 0.0f;
          const float C = (r2 >= 0 && c1 >= 0) ? data[r2 * step + c1] : 0.0f;
          const float D = (r2 >= 0 && c2 >= 0) ? data[r2 * step + c2] : 0.0f;
      
          return std::max(0.f, A - B - C + D);
      }
      

      像您的原始代码一样,这将使ABCD 具有来自data[](如果条件为true)或0.0f(如果条件为假)的值.另外,我会(如我所展示的)在适当的地方使用const。许多编译器无法根据const-ness 改进代码,但向编译器提供有关其正在操作的数据的更多信息肯定不会有什么坏处。最后,我重新排序了r1/r2/c1/c2 变量,以鼓励重复使用获取的宽度和高度。

      显然,您需要进行分析以确定这是否真的是一种改进。

      【讨论】:

        【解决方案6】:

        我不确定您的问题是否适用于SIMD,但这可能允许您一次对图像执行多个操作并为您带来良好的性能改进。我假设您正在内联和优化,因为您正在执行多次操作。看看:

        1. http://blogs.arm.com/software-enablement/coding-for-neon-part-1-load-and-stores/
        2. http://blogs.arm.com/software-enablement/coding-for-neon-part-2-dealing-with-leftovers/
        3. http://blogs.arm.com/software-enablement/coding-for-neon-part-3-matrix-multiplication/
        4. http://blogs.arm.com/software-enablement/coding-for-neon-part-4-shifting-left-and-right/

        如果启用了正确的标志,编译器确实对 Neon 有一些支持,但您可能需要自己推出一些。

        编辑 要获得对 neon 的编译器支持,您需要使用编译器标志 -mfpu=neon

        【讨论】:

        • 是否有任何编译器标志可以显式启用 Neon 支持?
        猜你喜欢
        • 1970-01-01
        • 2015-06-27
        • 1970-01-01
        • 2021-03-24
        • 2011-07-23
        • 2014-10-08
        • 1970-01-01
        • 1970-01-01
        • 2016-12-20
        相关资源
        最近更新 更多