递归方法比迭代慢10倍

时间:2013-02-28 15:53:28

标签: c# recursion rhino

尽可能清除代码以显示我的问题。 基本上它是一个八叉树搜索+相交。 交叉函数来自SDK(整个项目是rhino的插件)。 如果我使用交叉调用进行循环,它比通过八叉树的递归搜索快10倍。陌生人甚至 - 我隔离了交叉调用的时间 - 在递归内部它比循环慢8倍。 它可能有1000个原因,为什么它会像这样,但我希望我通过查看代码可以发现一些明显的错误。

有一个缓慢的背诵片:

public void NewRayCast()
{            
    int runs = 500000;                          //how many rays we cast
    Point3d raypos = new Point3d(0, 0, 0);      //raystart
    Ray3d ray = new Ray3d();                

    Random r = new Random();                    //here we create targets to scatter the ray directions
    Vector3d[] targets = new Vector3d[runs];
    for (int i = 0; i < runs; i++)
    {
        targets[i] = new Vector3d(r.NextDouble() * 200 - 100, 500, r.NextDouble() * 200 - 100);
    }

    for (int i = 0; i < runs; i++)
    {
        boxes = new List<int>();            // this collects the octree leaves the rays hits
        rayline.From = ray.Position;
        rayline.To = ray.Position + ray.Direction;                
        LineBoxer(starter);                 // this starts the raycasting - starter is a array with 1 value (the scene bounding box)
    }            
}

public void LineBoxer(int[] check)    // Cast a ray against boxes
{            
    for (int i = 0; i < check.Length; i++)  // check only because the first call is with the scene box (1 index)
    {
        if (octmanB.node[check[i]].closed == false) // if node is a dead end > empty we skip it
        {                                       
            isect = Rhino.Geometry.Intersect.Intersection.LineBox(rayline, octmanB.node[check[i]].bbox, 0, out ival); // intersection function, changing to an arbitrary bounding box doesnt speed it up either                    
            if (isect == true)
            {                        
                if (octmanB.node[check[i]].leaf == false)   // continue with recursion
                {
                    LineBoxer(octmanB.node[check[i]].child);
                }
                else
                {
                    boxes.Add(check[i]);    // here we have a leaf
                }
            }
        }
    }
}

这里是快速的:

public void FasterTestRun()
{
    int runs = 500000;                       
    Line line = new Line(new Point3d(1, 1, 1), new Point3d(0, 1000, 0));
    BoundingBox box = new BoundingBox(new Point3d(-50, 50, -50), new Point3d(50, 150, 50));

    bool isect;
    Interval v = new Interval();

    Random r = new Random();
    Point3d[] targets = new Point3d[runs];
    for (int i = 0; i < runs; i++)
    {
        targets[i] = new Point3d(r.NextDouble() * 20 - 10, 1000, r.NextDouble() * 20 - 10);
    }

    for (int i = 0; i < runs; i++)
    {
        line.To = targets[i];                
        isect = Rhino.Geometry.Intersect.Intersection.LineBox(line, box, 0, out v);                
    }            
}

谢谢!

1 个答案:

答案 0 :(得分:4)

现在我在家,我终于可以回答你的问题,所以让我们开始......

递归

首先:递归总是比迭代慢,如果你正在使用结构化编程语言。你不能概括这一点,因为函数式编程语言中的函数调用更快(函数在那里定义不同)。有关详细信息,Wikipedia是一个很好的来源。

详细地说,递归调用将所有局部变量推送到堆栈中的函数(或方法),等待内部调用返回(这包括相同的过程on on和on ...),最后弹出调用堆栈中的值并继续使用它们。这不仅是大量的内存负载,这对垃圾收集器来说也是痛苦的:你的函数必须等待的时间越长,你的对象在内存中的持续时间越长,老化并最终到达 gen1 gen2 < / em>的。这意味着它们需要很长时间才能被释放。

我能看到的另一个问题如下:

public void LineBoxer(int[] check)
{
    // ...
    LineBoxer(octmanB.node[check[i]].child);
    // ...
}

递归传递数组意味着数组的所有值都会长时间驻留在堆栈中。即使大部分元素已准备好发布!

如果你正在迭代地做同样的事情,那么堆栈就没有压力了。分配的变量通常是临时变量,可以很快释放。内存分配是这里的瓶颈。这个,(因为你在评论中提到了这个问题)是我继续深入细节的原因。

提高绩效 - 理论上

然而(在评论中)你也在询问如何更有效地处理光线追踪。基本上你使用八叉树是正确的。您要执行的基本操作是简单搜索。这就是你的问题。据我了解你的代码,你正在测试每个八叶树是否被击中:

public void LineBoxer(int[] check)    // Cast a ray against boxes
{            
    for (int i = 0; i < check.Length; i++)
    {
        // ...
    }
}

只需搜索所有与光线相交的方框即可。但那并不是引树的动机。你可以想象一个像binary search tree这样的八叉树,它可以扩展到3维。二叉搜索树可以搜索1维(例如列表或数组)中的条目。要在二维构造中搜索信息,您可以使用quadtrees。现在我们需要添加另一个维度(因为我们现在是3D),所以我们使用octrees

到目前为止一切顺利,但树木如何帮助我们执行搜索操作&#34;更好&#34;?

那是因为divide and conquer priciple。如果我们在更大的信息集中搜索特定的东西,我们将集合分成小块。我们这样做只要我们找到我们正在寻找的特定事物。

对于我们的八叉树,这意味着我们将一个立方体划分为 8 更小的立方体。现在我们测试每个盒子,如果我们的光线与它相交。在最好的情况下,它恰好与一个盒子相交。这是进一步检查的那个。但如果每个方框都包含 1000 框,我们只需再通过一次检查即可清除 7000 支票!

现在我们一次又一次地这样做,直到找到一片或多片叶子。递归地执行此操作不应该比迭代地执行此操作慢得多。想象一下具有100.000个节点的八叉树。第一个立方体可以存储8个立方体,这8个立方体可以存储64个立方体(深度:2!)等等......

  • 深度= 3:512立方体
  • 深度= 4:4.096立方体
  • 深度= 5:32.768立方体
  • 深度= 6:262.144立方体
  • 深度= 7:2.097.152立方体
  • ...

如果我们要搜索一个特定的框,我们的最大检查次数绝不会超过 8 x Depth 元素。因此,我们将算法性能从 O(n)增加到 O(log(n)) 1

很好,但如何将此问题应用于您的问题?

首先:您正在使用C# - 面向对象的语言。所以使用对象!如果将所有内容封装在对象结构中,则将分而治之原则应用于树结构非常简单。

在您的具体情况下,这意味着:

public class OctreeNode
{
    public bool IsLeaf { get; private set; }
    public OctreeNode[8] Children { get; private set; }

    public OctreeNode()
    {
        this.IsLeaf = true;
        this.Children = null;
    }

    // Don't forget to implement methods to build up the tree and split the node when required.
    // Splitting results in a tree structure. Inside the split-method 
    // you need to allocate the Children-Array and set the IsLeaf-Property to false.

    public bool Intersects(Line rayline, ref ICollection<OctreeNodes> Nodes)
    {
        Interval interval;

        // If the current node does not intersect the ray, then we do not need to
        // investigate further on from here.
        if (!Rhino.Geometry.Intersect.Intersection.LineBox(rayline, this.GetBoundingBox(), 0, out interval))
        {
            return false;
        }

        // If this is a leaf (in which we are interested in) we add it to 
        // the nodes-collection.
        if (this.IsLeaf)
        {
            Nodes.Add(this);
            return true;
        }

        // Not a leaf, but our box intersects with the ray, so we need to investigate further.

        for (int i = 0; i < 8; ++i)
        {
            // Recursive call here, but the result doesn't actually matter.
            this.Children[i].Intersects(rayline, Nodes)
        }

        // The ray intersects with our current node.
        return true;
    }
}

这将为你做所有的魔力!它仅测试树直到测试失败的深度并继续,直到您拥有光线与之相交的所有叶子。你也可以通过某种方式对它们进行排序,他们获得了最大的交集间隔&#34;,在流式传输时将内部的对象置于更高的优先级,但是由于我现在完全失败,我将继续:< / p>

您甚至可以通过应用并行性来进一步加速此算法。使用TPL非常简单,这里非常简单:

Parallel.For<int> (0; 8; i =>
{
    this.Children[i].Intersects(rayline, Nodes)
});

好吧,我认为,目前这是应该做的。我希望这可以帮助你和周围的更多人。 :)

注意:我对 rhino3d 不是很熟悉。也许内置的功能可以帮助您更好地解决问题!

注2:原谅我,当我在所有方面都不是100%正确的时候,我暂时没有完成那些理论上的考虑......

1 在最好的情况下,我们在完全平衡的树中搜索一个特定的叶子。