我想知道有效存储(以及随后访问)可变长度的多维数据数组集的最佳实践是什么。重点是性能,但我也需要能够在运行时处理更改单个数据集的长度,而不会有太多开销。
注意:我知道这是一个有点长的问题,但我环顾四周,找不到足够准确地描述手头问题的解决方案或例子。
背景
上下文是基于不连续伽辽金谱元法(DGSEM)的计算流体动力学(CFD)代码(参见Kopriva(2009),偏微分方程的谱方法实现)。为了简单起见,让我们假设一个2D数据布局(它实际上是三维的,但从2D到3D的扩展应该很简单)。
我有一个由K
正方形元素k
(k = 0,...,K-1
)组成的网格,这些元素可以具有不同的(物理)大小。在每个网格元素(或"单元格")k
中,我有N_k^2
个数据点。N_k
是每个维度中的数据点数量,并且可以在不同的网格单元之间变化。
在每个数据点n_k,i
(其中i = 0,...,N_k^2-1
),我必须存储一个解值数组,该数组在整个域(即处处)中具有相同的长度nVars
,并且在运行时不会改变。
尺寸和变化
网格单元K
的数量为O(10^5)
到O(10^6)
,并且在运行时可以更改
每个网格单元中的数据点N_k
的数量在2
和8
之间,并且在运行时可以改变(并且对于不同的单元可能不同)
存储在每个网格点的变量nVars
的数量大约为5
到10
,并且在运行时不能更改(对于每个网格单元也是一样的)。
要求
性能是关键问题。我需要能够以高效的方式(即,没有太多缓存未命中)在所有单元格的所有网格点上以有序的方式定期迭代。通常,K
和N_k
在模拟期间不会经常改变,因此例如,用于所有单元和数据点的大的连续存储器块可以是一种选择。
然而,我确实需要能够在运行时细化或粗化网格(即删除单元格并创建新单元格,新单元格可能会附加到末尾)。我还需要能够更改近似顺序N_k
,这样我为每个单元格存储的数据点数量也可以在运行时更改。
结论
欢迎提供任何意见。如果你自己也有经验,或者只是知道一些我可以参考的好资源,请告诉我。然而,尽管解决方案对最终程序的性能至关重要,但它只是众多问题之一,因此解决方案需要具有应用性质,而不是纯粹的学术性。
如果这是问这个问题的错误地点,请让我知道什么地方更合适。
通常,这类动态网格结构很难有效处理,但在块结构自适应网格细化代码(在天体物理学中很常见,复杂的几何结构并不重要)或具有大块大小的光谱元素代码中,这通常不太成问题。每个块/元素(在您的情况下,至少有10^5个单元格x 2个点/单元格)有很多工作要做,因此在块之间切换的成本相对较小。
还要记住,在每个元素或块的大量数据已经在缓存中之前,通常不能对该元素或块做太多的艰苦工作。无论如何,在完成块N+1的大量工作之前,您已经必须将块N的大部分数据从缓存中清除。(您的代码中可能有一些操作是例外,但这些操作可能不是您花费大量时间的操作,无论是缓存还是不缓存,因为没有太多数据重用-例如,对单元格值的元素操作)。因此,将每个块/元素放在一起并不一定是一件大事;另一方面,您肯定希望块/元素本身是连续的。
还要注意,当调整大小时,您可以四处移动块以保持它们的连续性,但不仅所有这些内存副本都会擦除缓存,而且内存副本本身也会变得非常昂贵。如果你的问题是填充了相当一部分内存(我们不是一直都是这样吗。如果你知道你将很少进行细化/去细化,那么这可能仍然值得做。
但实际上,天体物理流体动力学中的大多数自适应网格代码(我对这些代码非常了解)只需维护块及其元数据的列表,而不必担心它们的邻接性。YMMV当然。我的建议是,在花太多时间构建完美的数据结构之前,先在两个元素上测试两次操作;第一种是将元素按顺序排列并对其进行1-2次计算,第二种是以"错误"的顺序2-1进行运算,并对两次计算进行多次计时。
对于每个单元格,存储在连续数组中查找单元格数据的偏移量。这种偏移映射是非常有效和广泛使用的。您可以对单元格进行重新排序,以便在遍历中重用缓存。当单元格的顺序或数量发生变化时,创建一个新数组并进行插值,然后丢弃旧数组。这种存储对于外部分析要好得多,因为Krylov方法中的内积和Runge-Kutta方法中的阶段等操作可以在不参考网格的情况下进行管理。它还需要每个向量最小的内存(例如,在Krylov基和时间积分中)。