【问题标题】:Instancing data management实例化数据管理
【发布时间】:2014-07-16 00:50:44
【问题描述】:

我正在尝试使用实例化绘制几千个粒子。它可以工作而且速度很快,但我有一个瓶颈会减慢整个程序的速度。

我的Particle 类是这样的:

public class Particle
{
    public Vector2 Position;

    //More data not used for drawing
    //....
}

现在在我的DrawLoop() 中,我得到了这样的信息:

Vector2[] instanceData = new Vector2[numParticles];

public void Draw()
{
    for(int i = 0; i < numParticles; ++i)
        instanceData[i] = Particles[i].Position; //THAT'S the slow part

    instanceBuffer.SetData(instanceData);

    //Now draw VertexBuffer using instancing
    //...
}

我尝试过使用Parallel.For,但速度不够快,因为我有大约 8000 个粒子。我还查看了来自 MSDN 的 particlesystem 示例。但他们的Particle struct 只包含用于绘制粒子的数据,并且位置是在着色器中计算的。但是,我需要一些算法的额外数据。

想不出类设计,所以不需要每帧都给数组分配粒子位置。

【问题讨论】:

  • 你可以通过使循环不安全的代码和使用指针来加速它。可能是数组边界检查需要花费大量时间。当然,您也可以尝试将循环替换为instanceData = Particles.Select(p =&gt; p.Position).ToArray();,看看是否更快。
  • 哇,使用 Select() 确实有很大帮助!现在它的速度提高了 80 倍。谢谢!
  • 快 80 倍?真的吗?我觉得很难相信——Select() 并没有做任何神奇的事情,它只是在循环中运行,就像你正在做的那样。此外,在主循环中使用 LINQ 是一个糟糕的主意。它在整个托管堆上分配内存。我怀疑这里发生了一些更微妙的事情。
  • 对于 8000 个粒子,我得到 1.2714698s - for loop 0.2745342s - Parallel.For() 0.0023655s - LINQ。这比 for 快 537 倍,比 Parallel.For 快 116 倍,如果我做对了的话。使用其他数量的粒子时速度提高了 80 倍,但我不记得我使用了多少
  • 这个循环需要 1 又 25 秒 来执行 8000 个粒子?这段代码中的Particles 是什么?

标签: c# xna buffer particles


【解决方案1】:

由于这个问题最终是由所使用的数据结构引起的,所以让我为您介绍一种常见的替代链表的方案,例如这种情况。

链接列表通常不是存储粒子的好主意,原因有两个:第一,您无法有效地随机访问它们,正如您在此处发现的那样;第二,链表的locality of reference很差。考虑到粒子系统的性能要求,后一点可能是致命的。

标准列表具有更好的参考局部性,但正如您所发现的,添加和删除项目可能会很慢,这是您在粒子引擎中通常会做的事情。

我们可以改进吗?

让我们从比列表更基本的东西开始,一个简单的数组。为简单起见,让我们对引擎中的粒子数量进行硬性限制(我们稍后会解决这个问题)。

private const Int32 ParticleCount = 8000;
private readonly Particle[] particles = new Particle[ParticleCount];
private Int32 activeParticles = 0;

假设你有空间,你总是可以在恒定时间内将一个粒子添加到数组的末尾:

particles[activeParticles++] = newParticleData;

但是移除一个粒子是O(n),因为它之后的所有粒子都需要下移:

var indexOfRemovedParticle = 12;
particles.RemoveAt(indexOfRemovedParticle);
activeParticles--;

我们还能在恒定时间内做什么?好吧,我们可以移动粒子:

particles[n] = particles[m];

我们可以用它来提高我们的性能吗?

是的!把remove操作改成move操作,原来的O(n)变成了O(1):

var indexOfRemovedParticle = 12;
var temp = particles[indexOfRemovedParticle];
particles[indexOfRemovedParticles] = particles[activeParticles - 1];
particles[activeParticles - 1] = temp;
activeParticles--;

我们对数组进行分区:开始的所有粒子都是活动的,而结束的所有粒子都是非活动的。所以要移除一个粒子,我们要做的就是将它与最后一个活跃粒子交换,然后减少活跃粒子的数量。

(请注意,您需要删除粒子数组中的索引。如果您必须去搜索它,您最终会恢复到 O(n) 时间;但是,由于粒子的通常工作流程是“循环遍历整个列表,更新每个粒子,如果它已经死了,就从列表中删除它,“无论如何,你通常会得到“免费”的死粒子索引。)

现在,这一切都假设粒子数量是固定的,但是如果您需要更大的灵活性,您可以像 List&lt;T&gt; 类一样解决这个问题:每当您用完空间时,只需分配一个更大的数组并复制所有内容进入它。

此数据结构提供快速插入和删除、快速随机访问和良好的参考局部性。后者可以通过将您的 Particle 类变成一个结构来进一步改进,这样您的所有粒子数据都将连续存储在内存中。

【讨论】:

  • 哇,原来这么简单。这不仅加快了绘图速度,还加快了整个算法的速度,因为我不必再每帧都创建新实例了!感谢您的耐心和详细的回答!现在我可以毫无问题地拥有 30.000 个粒子。
猜你喜欢
  • 2019-05-26
  • 1970-01-01
  • 2014-05-13
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 1970-01-01
相关资源
最近更新 更多