Structure of arrays and array of structures - performance difference
我有一个这样的班级:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //Array of Structures class Unit { public: float v; float u; //And similarly many other variables of float type, upto 10-12 of them. void update() { v+=u; v=v*i*t; //And many other equations } }; |
我创建了一个单位类型的对象数组。并调用更新。
1 2 3 4 5 6 7 8 9 | int NUM_UNITS = 10000; void ProcessUpdate() { Unit *units = new Unit[NUM_UNITS]; for(int i = 0; i < NUM_UNITS; i++) { units[i].update(); } } |
号
为了加快速度,并可能使循环自动向量化,我将AOS转换为数组结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //Structure of Arrays: class Unit { public: Unit(int NUM_UNITS) { v = new float[NUM_UNITS]; } float *v; float *u; //Mnay other variables void update() { for(int i = 0; i < NUM_UNITS; i++) { v[i]+=u[i]; //Many other equations } } }; |
当循环无法自动向量化时,数组结构的性能会非常差。对于50个单元,SOA的更新速度比AOS稍快,但从100个单元开始,SOA比AOS慢。在300个单元的情况下,SOA的情况几乎是前者的两倍。在100k单位时,SOA比AOS慢4倍。虽然缓存可能是SOA的一个问题,但我没想到性能差异会如此之大。cacheGrind上的分析显示两种方法的未命中次数相似。单位对象的大小为48字节。一级缓存256K,二级缓存1MB,三级缓存8MB。我这里缺什么?这真的是缓存问题吗?
编辑:我使用的是GCC4.5.2。编译器选项是-o3-msse4-ftree矢量化。
我在SOA中做了另一个实验。我没有动态地分配数组,而是在编译时分配了"v"和"u"。当有10万个单元时,这样的性能比具有动态分配数组的SOA快10倍。这里发生了什么?为什么静态和动态分配内存之间存在这样的性能差异?
在这种情况下,数组的结构对缓存不友好。
同时使用
使用
根据CPU的不同,有两件事你应该知道,这会产生巨大的影响:
由于您使用的是SSE4,因此使用一个专门的内存分配函数,该函数返回一个在16字节边界上对齐的地址,而不是
至于缓存线别名,Intel Explicit在其参考手册中提到过(搜索"英特尔"?64和IA-32体系结构优化参考手册")。英特尔说这是你应该知道的,特别是在使用SOA时。所以,你可以尝试的一件事是填充你的数组,这样它们的低6位地址就不同了。这样做的目的是避免让他们为同一条缓存线而战。
预取对于花费大部分执行时间等待数据显示的代码至关重要。现代前端总线有足够的带宽来保证预取的安全性,前提是您的程序不会超过当前的负载集。
由于各种原因,结构和类可以在C++中产生许多性能问题,并且可能需要更多的调整来获得可接受的性能级别。当代码很大时,使用面向对象编程。当数据很大(性能很重要)时,不要这样做。
1 2 3 4 5 6 | float v[N]; float u[N]; //And similarly many other variables of float type, up to 10-12 of them. //Either using an inlined function or just adding this text in main() v[j] += u[j]; v[j] = v[j] * i[j] * t[j]; |
当然,如果您没有实现矢量化,就没有太多的动机来进行SOA转换。
除了相当广泛的事实上接受"限制"之外,GCC4.9还采用了
至于显式预取的使用,如果有用的话,当然您可能需要在SOA中使用更多的显式预取。主要的一点可能是通过提前提取页面来加速DTLB未命中解析,这样您的算法就会变得更需要缓存。
我不认为在没有更多细节(包括操作系统的细节)的情况下,可以对您所称的"编译时"分配做出明智的评论。毫无疑问,高层次分配和重新使用分配的传统是很重要的。