递归方法比交互式方法慢 10 倍 [关闭]

2024-05-14

代码已尽可能地清理以显示我的问题。 基本上它是八叉树搜索+相交。 intersect 函数来自一个 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);                
    }            
}

thanks!


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

递归

首先:递归总是比迭代慢,如果您正在使用结构化编程语言。您不能概括这一点,因为函数式编程语言中的函数调用速度更快(函数的定义不同)。了解更多信息维基百科 http://en.wikipedia.org/wiki/Recursion_%28computer_science%29#Recursion_versus_iteration是一个很好的来源。

详细来说,递归调用将函数(或方法)分配的所有局部变量压入堆栈,等待内部调用返回(这包括不断重复的相同过程...),最后从堆栈中弹出值调用堆栈并继续与他们合作。这不仅是沉重的内存负载,对于垃圾收集器来说也是痛苦的:函数必须等待的时间越长,对象在内存中停留、老化和最终到达的时间就越长gen1 or gen2。这意味着他们实际上需要很长时间才能被释放。

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

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

递归传递数组意味着all数组的值在堆栈上驻留了很长时间。即使大部分元素已经准备好发布!

如果你迭代地做同样的事情,那么堆栈就不会有压力。分配的变量通常是临时变量,并且可以很快释放。内存分配是这里的瓶颈。这(并且因为您在评论中要求这样做)就是为什么我将继续更详细地介绍。

提高性能——理论上

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

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

这只是简单地搜索所有与光线相交的盒子。但这并不是引入树木的动机。你可以想象一个八叉树二叉搜索树 http://en.wikipedia.org/wiki/Binary_Tree扩展到 3 维。二叉搜索树可以在一维中搜索条目(例如列表或数组)。要搜索二维结构中的信息,您可以使用四叉树 http://en.wikipedia.org/wiki/Quadtree。现在我们需要添加另一个维度(因为我们现在处于 3D 状态),所以我们使用octrees http://en.wikipedia.org/wiki/Octree.

到目前为止一切顺利,但是树如何帮助我们“更好”地执行搜索操作呢?

那是因为分而治之原则 http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm。如果我们要在更大的信息集中搜索特定的内容,我们会将信息集分成小块。我们这样做直到找到我们正在寻找的具体东西为止。

对于我们的八叉树来说,这意味着我们将一个立方体分为8更小的立方体。现在我们测试每个盒子是否与我们的光线相交。在最好的情况下,它恰好与一个盒子相交。这是需要进一步检查的问题。但如果每个盒子都包含1000我们只需扔掉盒子7000通过一张额外的支票进行检查!

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

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

And our maximum number of checks is never more than 8 x Depth elements if we are searching for one specific box. So we increased our algorithm performance from O(n) to 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;
    }
}

这将为您带来所有魔力!它仅测试树,直到测试失败的深度,并继续直到拥有光线相交的所有叶子。您还可以按照“谁获得了最大的交集间隔”对它们进行排序,以便在流式传输它们时将内部对象置于更高的优先级,但由于我完全失败了该主题,现在我将继续:

您甚至可以通过应用并行性进一步加速该算法。这很容易使用TPL http://msdn.microsoft.com/en-us/library/vstudio/dd460717.aspx,这很简单:

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

好吧,我想暂时就够了。我希望这对您和周围的更多人有帮助。 :)

Note:我不太熟悉rhino3d。也许有内置功能可以帮助您更好地解决问题!

Note 2:请原谅我,当我在所有观点上都不是 100% 正确时,我已经有一段时间没有进行这些理论上的考虑了......

1 In best case, where we are searching for one specific leaf in a completely balanced tree.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

递归方法比交互式方法慢 10 倍 [关闭] 的相关文章

随机推荐

  • 如何将数据库查询的行转换为 XML 文件?

    我正在开发一个 Delphi 应用程序 该应用程序需要从一段工作中获取行并将其转换为单个 XML 文件 以便上传到第三方 Web 服务 有没有可用的组件或库可以做到这一点 如果不是 那么构建 DB2XML 转换器的最佳代码方法是什么 我注意
  • 将相同匹配模式的连续 2 行放入单行中

    我想解析这组行 以便如果得到相同的模式 例如 lt email protected cdn cgi l email protection gt 在连续的行中 它应该以单行形式打印 并在两行之间使用 q2VDWKkY010407 222187
  • MatAutocomplete 值 X 显示

    我的自动完成显示具有以下定义的对象的值 export class Person id number name string cityName string 这是自动完成模板
  • 具有子集合成员条件的 NHibernate 查询仅返回部分子集合

    我与以下人员之间存在亲子关系Teacher and StudentReport Each StudentReport有一个时间戳字段记录老师完成报告的时间 我有一个查询 要查找截至某一分钟前已完成一份或多份报告的所有教师 public IL
  • 线程池,C++

    我正在使用 C 开发一个网络程序 我想实现一个 pthread 池 每当我从接收套接字接收到一个事件时 我都会将数据放入线程池中的队列中 我正在考虑创建 5 个独立的线程 并将持续检查队列以查看是否有任何传入数据需要完成 这是一个非常简单的
  • 在 C# 中创建一副纸牌

    因此 我正在尝试为我的一个编程课程创建一副纸牌 我从来没有真正做过这样的事情 如果我犯了一些愚蠢的错误 我很抱歉 我正在 Visual Studio 中对此进行编码 按照类规则 我正在尝试为我的 Deck 创建一系列 Card 对象 我遇到
  • 多 AVL 树旋转

    假设我有一个无序集合 s 3 6 5 1 2 4 并且我需要构造一个 AVL 树 就这么多了 我了解基本的旋转 我在这里达到这一点 5 2 6 1 3 但当我尝试插入 4 时 一切都崩溃了 我得到的最终答案是 左边的 4 But the a
  • iPhone OS 3.0.1 会毁掉你的开发手机吗?

    我将手机更新到3 0 1 虽然手机作为手机工作正常 xcode http en wikipedia org wiki Xcode组织者不再知道手机的名称 它还说这个版本的 xcode 不支持 3 0 1 我下载了最新版本的xcode和操作系
  • 使用把手显示来自 parse.com 的 json 响应

    我想将 json 响应传递给车把 我已经查看了解析文档和 stackoverflow 问题 但我似乎无法弄清楚这一点 这是回应 results address 755 W Yale createdAt 2013 02 09T01 12 15
  • 开源 EDA 项目

    您知道 EDA 电子设计自动化 领域有哪些开源项目正在寻找 C 程序员吗 如果您经常关注 gEDA 的邮件列表 您也许能够加入 gEDA 细节 http www gpleda org developer html http www gple
  • Webpack 子编译器更改配置

    我希望在编译我的服务工作人员时将我的 webpack 构建的输出定义为变量 我想使用子编译功能来编译放入不同路径的服务工作人员 我需要 webpack 编译发出的输出来正确编译服务工作线程 我最初的做法是使用与离线插件相同的策略 在其中创建
  • 为什么 Mac OS 上的 C 运行时允许预组合和分解的 UTF-8?

    所以我们都知道 Mac OS 上的文件系统具有使用完全分解的 UTF 8 的古怪功能 如果您调用 POSIX API 例如realpath 例如 您将从 Mac OS 返回这样一个完全分解的 UTF 8 字符串 当使用像这样的 API 时f
  • 从所有表中选择

    我的数据库中有很多表都具有相同的结构 我想从所有表中进行选择 而不必像这样列出所有表 SELECT name FROM table1 table2 table3 table4 我尝试过 但这不起作用 SELECT name FROM 有没有
  • 从文件中读取未知长度的int数组

    如何从文件中读取未知长度的整数数组 我没有找到获取数组大小的方法 所以我尝试了一些临时字符串的东西 但我的代码爆炸了 有更好的想法吗 Use std vector std ifstream inFile fileName std vecto
  • Laravel 5.3 Eloquent 事务和外键限制

    我正在从事一个更大的项目 我们在一个 Postgres 数据库中有多个模式 我们在模式之间创建了外键 这是一个例子 gt 我们有公司模式和用户模式 公司模式有company users表 该表对user users表有外键限制 CREATE
  • PostgreSQL 如何创建数据库或模式的副本?

    有没有一种简单的方法可以在 PostgreSQL 8 1 中创建数据库或模式的副本 我正在测试一些软件 它对数据库中的特定模式进行大量更新 我想复制它 以便我可以与原始版本进行一些比较 如果它位于同一服务器上 则只需使用带有 TEMPLAT
  • Apache mod_rewrite 将双斜杠转换为单斜杠

    我有一个像这样的网址 http example com img php url http example2 com path to image name jpg 所以我通过这个问题创建了一条规则Apache mod rewrite 复杂 U
  • 如何检测环境是在 Azure 托管服务工作者角色中进行暂存还是生产?

    我在托管服务中担任辅助角色 工作人员每天都会发送电子邮件 但在托管服务中 有 2 个环境 Staging 和 Production 所以我的工人角色每天发送电子邮件 2 次 我想知道如何检测工人是否处于停滞状态或生产状态 提前致谢 根据我的
  • BracketAlignmentStyle:在右括号之前中断

    结合开括号后对齐 BracketAlignmentStyle 选项BinPackArguments and BinPackParameters set to false 可以得到以下格式 someShortFunction argument
  • 递归方法比交互式方法慢 10 倍 [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 代码已尽可