我可以优化具有3个for循环和4个ifs的代码吗?

时间:2008-11-29 13:38:28

标签: c++ algorithm

我又发了一篇文章

here在那里我问如何在三维空间中创建一个立方体素节点的26个邻居。我得到了一个非常好的答案并实施了它。

为此,我添加了一些MIN MAX位置检查。

我想知道是否有方法,与3 for循环相关,4如果使用,以改善此代码的执行时间。我在另一篇文章中读过,当使用while循环时速度更快但是它的帖子不是语言特定的。

这是真的吗?如果是的话,请你在我的代码中帮助我,因为我有运气经历吗? 有没有办法以一种能让它更快的方式递归地实现它?

这是我的代码:

...
std::vector<Pos> Create26Neighbor(Pos somePos, double resol) 
{
    std::vector <Pos> vect1;
    Pos  m_MinPos(0.0,0.0,0.0);
    Pos  m_MaxPos(5.0,4.0,5.0);

    for (double dz = somePos.m_pPos[2] - resol; dz <= somePos.m_pPos[2] + resol; dz+=resol)
    {
        if (dz>m_MinPos.m_pPos[2] && dz<m_MaxPos.m_pPos[2])
        {
            for (double dy = someCPos.m_pPos[1] - resol; dy <= someCPos.m_pPos[1] + resol; dy+=resol)
            {
                if (dy>m_MinPos.m_pPos[1] && dy<m_MaxPos.m_pPos[1])
                {
                    for (double dx = somePos.m_pPos[0] - resol; dx <= somePos.m_pPos[0] + resol; dx+=resol)
                    {
                        if (dx>m_MinPos.m_pPos[0] && dx<m_MaxPos.m_pPos[0])
                        {
                            // all 27
                            if ((dx != somePos.m_pPos[0]) || (dy != somePos.m_pPos[1]) || (dz != somePos.m_pPos[2]))
                            {
                                Pos tempPos(dx,dy,dz);
                                vect1.push_back(tempPos);
                            }
                        }
                    }
                }
            }
        }
    }
    return vect1;
}
....

9 个答案:

答案 0 :(得分:5)

首先,摆脱if语句。没有必要。您可以将它们合并到循环条件中。其次,避免每次迭代重新计算循环条件。是的,编译器可能会对其进行优化,但它通常对浮点优化非常保守(并且它可能会将从内存中读取的fp值与从寄存器中读取的值不同,这意味着它不能从循环条件中消除数组查找),因此通常最好手动进行简单的优化:

std::vector<Pos> Create26Neighbor(Pos somePos, double resol) 
{
    std::vector <Pos> vect1(27); // Initialize the vector with the correct size.
    Pos  m_MinPos(0.0,0.0,0.0);
    Pos  m_MaxPos(5.0,4.0,5.0);

    double minz = std::max(somePos.m_pPos[2] - resol, m_MinPos.m_pPos[2]);
    double maxz = std::min(somePos.m_pPos[2] + resol, m_MaxPos.m_pPos[2];
    int i = 0;
    for (double dz = min; dz <= max; dz+=resol)
    {
        double miny = std::max(somePos.m_pPos[1] - resol, m_MinPos.m_pPos[1]);
        double maxy = std::min(somePos.m_pPos[1] + resol, m_MaxPos.m_pPos[1];
        for (double dy = miny; dy <= maxy; dy+=resol)
        {
            double minx = std::max(somePos.m_pPos[0] - resol, m_MinPos.m_pPos[0]);
            double maxx = std::min(somePos.m_pPos[0] + resol, m_MaxPos.m_pPos[0];

            for (double dx = minx; dx <= maxx; dx+=resol)
            {
                ++i;
                // If we're not at the center, just use 'i' as index. Otherwise use i+1
                int idx = (dx != somePos.m_pPos[0] || dy != somePos.m_pPos[1] || dz != somePos.m_pPos[2]) ? i : i+1;
                vec1[idx] = Pos(dx, dy, dz); // Construct Pos on the spot, *might* save you a copy, compared to initilizing it, storing it as a local variable, and then copying it into the vector.
              }
        }
    }
    return vect1;
}

我考虑的最后一点是内部的if语句。紧密循环中的分支可能比您预期的更昂贵。我可以想出一些消除它的方法:

  • 当我在代码中勾画时,可以诱导?:运算符计算中心值的不同向量索引(因此它被写入下一个向量元素,因此在下一次迭代时会再次被覆盖)。这将消除分支,但总体上可能会或可能不会更快。
  • 拆分循环,以便在'resol'值之前和之后有单独的循环。这有点尴尬,有很多较小的循环,整体效率可能较低。但它会消除内部的if语句,所以它也可能更快。
  • 允许将中心点添加到矢量中,然后忽略它,或者在循环之后将其移除(这将是一个有点昂贵的操作,可能会或可能不会得到回报。如果你可能会更便宜使用deque而不是vector。

确保编译器展开内部循环。手动展开它也可能有所帮助。

最后,很大程度上取决于Pos的定义方式。

请注意,我建议的大部分内容都符合“它可能不会更快,但......”。您必须不断地对每个变更进行分析和基准测试,以确保您实际上提高了性能。

根据您愿意走多远,您可以将所有内容合并为一个循环(在整数上运行),并在每次迭代中动态计算Pos坐标。

答案 1 :(得分:3)

如果没有像域名过滤这样的“智能”,你可能不会找到很多简化立方方程的方法。

真的,我在这里发帖的真正原因是代码坦白地说是 beasty ,即:eugh。呸。我有一个个人的,最近开发的对嵌套代码的讨厌,并会努力将一些内部循环导出到一个单独的函数, DESPITE 它将添加的额外理论开销(简介它,小函数通常会内联)

我个人的观点是,如果你的代码具有高性能,但没有人能够理解它,那么它比次优但可维护的代码更糟糕。


此外,如果您可以保证相对于起点固定坐标的数量,您可以通过对结构进行硬编码来获益,也就是说,手动执行,即:

function generate26( x,y,z ){ 
   return [ 
   # Top 
     # Left
      [x-1,y+1,z-1], 
      [x-1,y+1,z],
      [x-1,y+1,z+1]
   ];
}

或者生成一个宏或2来为你做。

至少在某种程度上,你完全依赖编译器优化内存结构的能力,没有循环或任何东西。 (虽然可以确定它的概况)

答案 2 :(得分:2)

  • 从语言的角度来看,您可以通过在向量中保留26(或27,具体取决于您的数字:) :)项目来提高性能:

    std::vector<Pos> vect1; vect1.reserve(27);

    这将使内部数组足够大并避免重新分配向量。

  • 返回向量,或通过引用传递向量并写入更高性能只会通过测试来计算。编译器可以优化返回值副本。

一般情况下,如果您优化算法本身(或通过选择另一个算法),您将获得更多性能提升,而不是尝试优化其实现。

答案 3 :(得分:2)

  

有没有办法实现这一点   以一种可以实现的方式递归地递归   快?

没有。真的,没有

递归意味着函数调用,通常是大量的。函数调用意味着堆栈操作和(可能)上下文更改,这些操作相对较慢。

递归是一个功能强大的工具,可用于执行一些非常棘手的事情,同时保持可读性,但它不是一种高性能技术。在最好的情况下,您可能会发现一个编译器可以优化尾递归,以便像普通循环一样快速运行 - 这可以通过将递归代码转换为幕后的正常循环来实现。

答案 4 :(得分:1)

所有for循环基本上都采用以下形式:

for (d_ = somPos._ - resol; d_ <= somPos_.+resol; d_+= resol)

这恰好执行了3次。如果用表格中的东西替换这三个for循环,这段代码可能会更快:

double dz = somePos.m_pPos[2] - resol; 
for(z = 0; z < 3; z++, dz += resol)

在这里使用common for循环表单将允许优化器根据需要展开这些循环。我不认为你的另一种形式很简单,优化者可以发现它真的只会发生3次。这个是。

编辑:此外,如果您使用const或#define作为MinPos / MaxPos值,编译器可能会加速我们 little 位。我不认为它能够像你拥有它们那样确定值是非常常量的。

答案 5 :(得分:1)

比较与浮点数的相等性是非常危险的,容易出错。

按值传递和返回对象?根据您的对象,这可能会减慢速度。

就优化而言,尽可能在最“外”循环中测试变量。但实际上,您似乎比循环优化有更大的问题需要担心。

答案 6 :(得分:1)

所以基本上,在正常情况下,你想要向量添加26个位置,这些可以很容易枚举,除了你必须小心不要访问超出的体素边界。

如果您确实想要将此功能优化到最大值,那么最佳实现将是单个开关和展开循环。

对于3个维度中的每个维度,只有五个可能性:

case 1:  {somePos[i] - resol};  // 1 value only
case 2:  {somePos[i] - resol, somePos[i]}   // 2 values
case 3:  {somePos[i] - resol, somePos[i], somePos[i] + resol}  // all 3
case 4:                      {somePos[i], somePos[i] + resol}  // 2 values again
case 5:                                  {somePos[i] + resol}  // 1 value only

还有一个“case 0”,其中 none 的值在范围内。但是,如果对于任何维度都是如此,则根本不添加任何值。

为三个维度中的每个维度组合5个可能性,为您提供125个可能的实施案例。根据您获得的125个案例中的哪一个,您可以将循环和ifs展开为最多26个push_back()调用的序列。

这样的事情:

enum eCase {
CASE_NONE = 0,
CASE_LOW1 = 1,
CASE_LOW2 = 2,
CASE_ALL3 = 3,
CASE_HIGH2 = 4,
CASE_HIGH1 = 5,
};

eCase Xcase = /* a function of somePos[0], m_MinPos[0], m_MaxPos[0], and resol */
eCase Ycase = ...
eCase Zcase = ...

#define MUNGE(_x,_y,_z) (((((_x)*6)+(_y))*6)+(_z))
switch (MUNGE(Xcase, Ycase, Zcase) {

default:
    break;  // all CASE_NONE's do nothing
case MUNGE (CASE_ALL3, CASE_ALL3, CASE_ALL3):
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1] - resol, somePos.m_pPos[2] - resol));
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1] - resol, somePos.m_pPos[2]        ));
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1] - resol, somePos.m_pPos[2] + resol));
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1]        , somePos.m_pPos[2] - resol));
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1]        , somePos.m_pPos[2]        ));
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1]        , somePos.m_pPos[2] + resol));
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1] + resol, somePos.m_pPos[2] - resol));
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1] + resol, somePos.m_pPos[2]        ));
    vect1.push_back( pos (somePos.m_pPos[0] - resol, somePos.m_pPos[1] + resol, somePos.m_pPos[2] + resol));

    vect1.push_back( pos (somePos.m_pPos[0]        , somePos.m_pPos[1] - resol, somePos.m_pPos[2] - resol));
    vect1.push_back( pos (somePos.m_pPos[0]        , somePos.m_pPos[1] - resol, somePos.m_pPos[2]        ));
    vect1.push_back( pos (somePos.m_pPos[0]        , somePos.m_pPos[1] - resol, somePos.m_pPos[2] + resol));
    vect1.push_back( pos (somePos.m_pPos[0]        , somePos.m_pPos[1]        , somePos.m_pPos[2] - resol));
    vect1.push_back( pos (somePos.m_pPos[0]        , somePos.m_pPos[1]        , somePos.m_pPos[2] + resol));
    vect1.push_back( pos (somePos.m_pPos[0]        , somePos.m_pPos[1] + resol, somePos.m_pPos[2] - resol));
    vect1.push_back( pos (somePos.m_pPos[0]        , somePos.m_pPos[1] + resol, somePos.m_pPos[2]        ));
    vect1.push_back( pos (somePos.m_pPos[0]        , somePos.m_pPos[1] + resol, somePos.m_pPos[2] + resol));


vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1] - resol, somePos.m_pPos[2] - resol));
vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1] - resol, somePos.m_pPos[2]        ));
vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1] - resol, somePos.m_pPos[2] + resol));
vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1]        , somePos.m_pPos[2] - resol));
vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1]        , somePos.m_pPos[2]        ));
vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1]        , somePos.m_pPos[2] + resol));
vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1] + resol, somePos.m_pPos[2] - resol));
vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1] + resol, somePos.m_pPos[2]        ));
vect1.push_back( pos (somePos.m_pPos[0] + resol, somePos.m_pPos[1] + resol, somePos.m_pPos[2] + resol));
break;

......还有124个案例要去!

不要 - 重复不要 实际上手工编写所有这些代码! 没有编码难以找到的bug,任何人都无法做到这一点。 写另一个程序来代替编写源代码。 : - )

答案 7 :(得分:1)

std::vector<Pos> Create26Neighbor(Pos somePos, double resol) 
{
    std::vector<Pos> vect1(26);
    Pos  m_MinPos(0.0,0.0,0.0);
    Pos  m_MaxPos(5.0,4.0,5.0);

    double z = somePos.m_pPos[2] - resol;

    for(int dz = -1; dz <= 1; ++dz) {
        z += resol;
        if(z <= m_MinPos.m_pPos[2] || z >= m_MaxPos.m_pPos[2])
            continue;

        double y = somePos.m_pPos[1] - resol;

        for(int dy = -1; dy <= 1; ++dy) {
            y += resol;
            if(y <= m_MinPos.m_pPos[1] || y >= m_MaxPos.m_pPos[1])
                continue;

            double x = somePos.m_pPos[0] - resol;

            for(int dx = -1; dx <= 1; ++dx) {
                x += resol;

                if(dx == 0 && dy == 0 && dz == 0)
                    continue;

                if(x <= m_MinPos.m_pPos[0] || x >= m_MaxPos.m_pPos[0])
                    continue;

                vect1.push_back(Pos(x, y, z));
            }
        }
    }

    return vect1;
}

我尝试对此进行优化以提高可读性。你真的关心速度吗?我不认为速度对于创建一些邻居节点非常重要。您是否已分析过您的代码以确定这是否是瓶颈?

答案 8 :(得分:0)

我没有试图弄明白,但你可以用SSE2 / Altivec /其他矢量指令做一些漂亮的事情,一次做多个比较。