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

Posted

技术标签:

【中文标题】我可以优化具有 3 个 for 循环和 4 个 if 的代码吗?【英文标题】:Can I optimize code that has 3 for loops and 4 ifs? 【发布时间】:2008-11-29 13:38:27 【问题描述】:

我又发了一个帖子

here 我问如何在 3-d 空间中创建一个立方体素节点的 26 个邻居。我得到了一个很好的答案并实施了它。

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

我想知道是否有办法,与 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;

....

【问题讨论】:

【参考方案1】:

首先,摆脱 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 语句,因此它也可能更快。 允许将中心点添加到向量中,然后要么忽略它,要么在循环之后将其删除(这将是一个有点昂贵的操作,并且可能会或可能不会得到回报。如果你这样做可能会更便宜使用双端队列而不是向量。

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

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

请注意,我建议的大部分内容都是“它可能不会更快,但是......”。您必须不断对所做的每一项更改进行分析和基准测试,以确保您确实在提高性能。

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

【讨论】:

不确定 idx 的计算是否正确(可能我没看懂;))我猜想用 int 循环(范围 -1..1)替换双循环并比较 int 循环索引而不是 double 值也会加快代码速度 糟糕,最后两个语句在 th 中吗? : 运营商互换。现在可能更有意义了。 :) 感谢您的回答,但尝试了它并不起作用(链接错误).. :( 好吧,我没有测试它。不要只是复制/粘贴代码,了解我所做的更改以及原因,然后自己编写代码以使其正常工作。 ;)【参考方案2】:

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

真的,我在这里发帖的真正原因是代码实在是野兽,即:哎呀。呸。我对过度嵌套的代码有个人和最近产生的仇恨,并且会努力将其中一些内部循环导出到单独的函数,尽管它会增加额外的理论开销(分析它,小无论如何,函数通常都会内联)

我个人的看法是,如果你的代码是高性能的,但没有人能理解它,它比次优但可维护的代码更糟糕。


另外,如果你能保证坐标的数量相对于起点是固定的,你也许可以通过硬编码结构来受益,即手工完成,即:

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 个宏来为您完成。

至少这样你完全依赖编译器优化内存结构的能力,没有循环或任何东西。 (可以肯定的是,配置文件)

【讨论】:

是的。如果它适用于 OP 的情况,这个 generate26 函数可能是最好的优化。没有循环,没有条件,并且非常可读。我很遗憾我只能投一票。 我不知道如果他想要 3d 中的所有 26 个邻居,它的可读性如何。这是很多硬编码的索引。在简单的情况下,我同意,这会更干净。 @jalf,我猜您可以轻松创建一个简单的构造来生成一些包含的文件。运行一次,内联处理。【参考方案3】:

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

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

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

是返回向量,还是通过引用传递向量并写入性能更高,只能通过测试来确定。编译器可以优化掉返回值副本。

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

【讨论】:

【参考方案4】:

有没有办法实现这个 以某种方式递归地使它 更快?

没有。真的,

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

递归是一个强大的工具,可以用来做一些非常棘手的事情,同时保持可读性,但它不是一种高性能技术。在最好的情况下,您可能会找到一个优化尾递归的编译器,使其运行速度与正常循环一样快——这是通过在后台将递归代码转换为正常循环来实现的。

【讨论】:

【参考方案5】:

你所有的 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)

在此处使用常见的 for 循环形式将允许优化器在需要时展开这些循环。我认为您拥有的另一种形式不够简单,以至于优化器无法确定它实际上只会发生 3 次。这个是。

编辑:此外,如果您对 MinPos/MaxPos 值使用 const 或 #define,编译器可能会加快我们的处理速度一点点。我不认为它能够以你拥有它们的方式确定值真的是常数。

【讨论】:

原版有bug。如果分辨率为零或非常小的值,则永远循环。【参考方案6】:

用浮点数比较相等性是非常危险的并且容易出错。

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

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

【讨论】:

vector对象是push_back复制的,在push_back中使用栈分配对象或者按值返回vector都没有错。 按值返回向量可能会降低性能。但也可能是它根本没有降低性能。返回值优化是一个相当微不足道的优化 按值传递 pos 是完全正确的。通过引用传递它甚至会降低性能(假设 Pos 只有 4 个浮点数而没有用户定义的复制构造函数) 公平地说,我已经很久没有使用 C++ 了。 @litb:为什么它是否有用户定义的 cctor 很重要?如果它是用户定义的但可以内联,那有什么坏处?【参考方案7】:

所以基本上,在正常情况下,您希望向向量添加 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

还有一个“情况 0”,其中 none 值在范围内。但如果对于任何维度都是如此,那么您根本不需要添加任何值。

结合三个维度中的每一个维度的 5 种可能性,可以为您提供 125 种可能的实现案例。鉴于您拥有 125 个案例中的哪一个,您可以将循环和 if 展开为最多 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 个病例了!

不要——重复不要实际上是手写所有这些代码!!! 如果不编写一个难以发现的错误,任何人都无法做到这一点。 编写另一个程序来编写源代码。 :-)

【讨论】:

我的错:每个维度都有六个有趣的案例,而不仅仅是五个。你可以有middle-only的情况。总共需要实施 216 个案例!【参考方案8】:
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;

我试图优化它以提高可读性。你真的关心速度吗?我认为速度对于创建一些邻居节点并不重要。您是否分析过您的代码以查看这是否是瓶颈?

【讨论】:

【参考方案9】:

我还没有尝试弄清楚,但您可以使用 SSE2/Altivec/其他向量指令做一些漂亮的事情,一次进行多个比较。

【讨论】:

以上是关于我可以优化具有 3 个 for 循环和 4 个 if 的代码吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 3 个 FOR 循环优化 SQL 查询

python怎么跳出循环

在 Python 中优化 for 循环以更快地工作

循环展开和优化

如何通过for循环将矩阵的值分配给数组

SSE Intrinsics 和循环展开