【问题标题】:Faster nested loops over tens of thousands of particles数万个粒子的更快嵌套循环
【发布时间】:2020-08-19 21:17:54
【问题描述】:

我正在 Unity 环境中进行一些视觉艺术研究。 我正在尝试实现与差分线增长非常相似的东西as explained here 但我主要担心的是,在算法的某个地方,每个节点都应该检查每个其他节点,看看它有多接近,并从所有这些靠近的粒子构造一个排斥力数组。

这是我的代码的 sn-p:

   public void Differentiate()
    {
        int c = nodes.Count;                                 
        Vector3[] repulsionForces = new Vector3[c];

        for (int i = 0; i < c ; i++)
        {

            // Construct nearbies
            List<DifferentialNode> nearby = new List<DifferentialNode>();
            foreach(DifferentialNode n in nodes)
            {
                float d = Vector3.Distance(n.position, nodes[i].position);
                if (d < 5)
                {
                    nearby.Add(n);
                }
            }
            // Get Forces
            Vector3 repulsionForce = nodes[i].RepulsionForce(nearby);

            // Limit Forces
            repulsionForce = Vector3.ClampMagnitude(repulsionForce, maxForce);

            // Apply Multipliers
            repulsionForce *= Repulsion;

            // Put Forces into Array
            repulsionForces[i] = repulsionForce;
        }

        for (int i = 0; i < c; i++)
        {
            nodes[i].applyForce(repulsionForces[i]);
            nodes[i].update();
            nodes[i].velocity = new Vector3(0, 0, 0);
        } 

这是我在 DifferentialLineNode 类中的 RepulsionForce() 函数

public Vector3 RepulsionForce(List<DifferentialNode> nearby)
{
    Vector3 repulsionForce = new Vector3();

    foreach (DifferentialNode n in nearby)
    {
        // calculate distance between both
        float d = Vector3.Distance(n.position, this.position);
        // calculate difference and divide by exp(d) to get less influence when far
        Vector3 diff = ( this.position - n.position ) / (Mathf.Exp(d)); 
        repulsionForce += diff;
    }
    repulsionForce /= (float)nearby.Count;
    repulsionForce.Normalize();

    return repulsionForce;
}

一旦我开始游戏,一切都会降到 1fps 以下,我认为嵌套循环是它的来源,因为它具有 n^n 的复杂性。我一直在研究 Octree / KdTree 实现,但找不到任何解释代码。还有其他路线吗?超过一个 ?可以任意组合吗?非常感谢

【问题讨论】:

  • 您应该查看 Job 系统、ECS 和 Burst 编译器。这会加快速度,但遗憾的是,我对此知之甚少,所以我真的无法为您提供更多帮助。

标签: c# unity3d nested-loops kdtree


【解决方案1】:

对于每个点计算距离内的点是O(n^2),所以如果粒子数量很大,性能下降并不奇怪。但这可以很容易地改进。有多种搜索结构可供选择:

  • 3D 网格。这应该很容易实现,只需创建一个 3D 列表数组。选择一个合适的 bin 大小,以便您有固定数量的 bin 进行迭代。这样做的主要缺点是内存使用量,如果在您的模拟中有没有粒子的大空隙,这将更加显着。
  • 稀疏八叉树。如果点的间距不均匀,则与网格相比的主要优势将是更好的内存使用。如果您需要搜索任意距离,它也会更好地扩展。缺点是实现更复杂。
  • KdTree。这是一个非常好的数据结构,因为它使用的内存很少,可以用连续的内存实现,并且可以很好地扩展。一个缺点是难以处理位置的变化。实现也比网格更复杂。

对于网格或八叉树,可以在 bin 之间移动点。对于 kd 树,重建树可能会更好。任何选项都应将搜索时间从 O(N^2) 减少到 O(n log n)。

考虑到您的问题,我建议您从简单的 3D 网格开始。如果这还不够,我会考虑使用 kd 树。我会避免组合数据结构,除非有特定的理由这样做。八叉树和 kdtree 应该可以很好地扩展。

我建议尝试一些profiling tools 来检查实际需要时间。我还建议设置一些受控环境以在已知数据集上运行算法并测量时间。 Benchmark.Net 是黄金标准,但在测量较大的变化时,一个简单的秒表应该是相当不错的。

还应该有一些进行微优化的机会。避免在紧密循环中创建列表,如果需要,创建一个可重复使用的列表。避免重复操作。避免昂贵的操作。比其他集合更喜欢数组和列表,因为运行时对这些有特殊的优化。首选for 而不是foreach,因为前者可能会避免创建迭代器对象。

【讨论】:

    【解决方案2】:

    您应该考虑使用for 而不是foreach 用于列表,如解释,例如here 当您为性能编码时。不过,我认为您的问题更多是结构性问题,而不是与此类细节有关。

    我建议您查看Quadtrees,例如通过this implementation 将您的总面积分成多个段并将每个粒子与一个段相关联。要找到邻居,您只需遍历树即可。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-07
      • 2016-03-13
      • 1970-01-01
      • 2015-06-29
      • 2017-07-06
      • 1970-01-01
      • 2019-04-15
      • 2023-02-08
      相关资源
      最近更新 更多