哪种存储方案是最优的,很大程度上取决于访问模式。您已经说过一次访问一个粒子,但是您是要按顺序访问粒子,还是随机访问?这个问题的另一个传统名称是 struct-of-arrays 与 array-of-structs。
这里的关键是了解内存层次结构如何在 CPU 上工作(参见例如
https://www.scss.tcd.ie/Jeremy.Jones/CS3021/5%20caches.pdf以及许多问题关于堆栈溢出的这个主题)。从主内存读取有非常大的延迟,这将导致执行中的长时间停顿,除非通过预取来缓解这种情况。每次从主内存读取都会读取一个缓存行的数据,将其放入缓存中,并且从缓存中读取是相对空闲的。
因此,例如,如果您有一个矩阵,并且它在内存中以列主要顺序存储为
然后读取(或 ) 将从到
的所有八个元素放入缓存中(假设正确对齐和典型的 64 字节缓存行和 8 字节的元素大小),使它们可以自由使用权。(aij)1≤i,j≤3
a11,a21,a31,a12,a22,a32,a13,a23,a33,
a11 a11,…,a23a11a23
如果你有几个不同的数组,一个 struct-of-arrays,像
你读,您将有效地读取每个数组的前八个元素。如果您最终立即使用接下来的七个元素,那很好。如果不是,那么通过读取 24 个元素而不是 3 个元素,这浪费了 7/8 的内存带宽,这是低效的。
x1,…,y1,…,z1,…
x1,y1,z1
相反,如果您将它们存储为结构数组,
那么读取将读取前八个元素。如果您使用接下来的那些元素,则没有太大区别,否则您会浪费 5/8(读取 8 而不是 3)的内存带宽 - 请注意,这仍然比读取 24 个元素(如上一个示例)要好。
x1,y1,z1,x2,y2,z2,…
x1,y1,z1
有九个 8 字节的元素,如果你知道你会连续访问它们,那么任何一种方式都应该没有太大的区别,如上所述。如果您以随机顺序访问它们,则读取一组九个元素将导致元素(如果存储为数组结构)或
元素(如果存储为结构数组) - 这可能是一个非常大的性能提升。9×8=72⌈98⌉×8=16
我觉得列优先和行优先之间的区别在这里有点红鲱鱼 - 关键是了解访问模式,并确保数据布局与访问模式匹配。下一个最重要的事情是预取,这是一种隐藏相对较大的内存访问延迟的技术。
另一个需要考虑的相关问题是您如何访问您之前已经读取的数据,以及它是否可能仍保留在缓存中,或者是否已清除它以便为其他数据让路,从而导致缓存未命中 - 数组字节是,很可能完全适合 L2 或 L3 缓存。5000×9×8360KB
如果您的程序涉及在不同内核上运行的多个线程,并且每个内核可能具有单独的较低级别缓存,则情况会变得更加复杂。
请注意,顺便提一下,对于非顺序访问模式,如果高速缓存行是 8 个元素,那么每个粒子有 9 个元素本身效率有点低,因为读取 9 个元素本质上等同于读取 16 个元素 - 一种技术是拆分数据将经常使用的部分与很少使用的部分进行区分,以便经常使用的部分更适合。
拥有多行或多列不会受到惩罚。不确定“许多内存地址”的惩罚是什么意思,但没有这样的惩罚。
我所说的一些事情取决于你的程序将运行的架构的确切属性。使用像 R 或 Matlab 这样的解释性语言也会严重影响这一点——对于 C++ 来说,它应该成立。一般来说,最好使用结构数组模式,除非您有特定的理由不这样做。