数组的结构和结构数组 - 性能差异
Structure of arrays and array of structures - performance difference
我有这样的类:
//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
}
};
我创建了一个单位类型的对象数组。并致电更新他们。
int NUM_UNITS = 10000;
void ProcessUpdate()
{
Unit *units = new Unit[NUM_UNITS];
for(int i = 0; i < NUM_UNITS; i++)
{
units[i].update();
}
}
为了加快速度,并可能自动矢量化循环,我将 AoS 转换为数组结构。
//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 的一个问题,但我没想到性能差异会这么大。对缓存进行分析显示两种方法的未命中次数相似。Unit 对象的大小为 48 字节。一级缓存为 256K,二级缓存为 1MB,三级缓存为 8MB。我在这里错过了什么?这真的是缓存问题吗?
编辑:我正在使用 gcc 4.5.2。编译器选项是 -o3 -msse4 -ftree-vectorize。
我在SoA中做了另一个实验。我没有动态分配数组,而是在编译时分配了"v"和"u"。当有 100K 个单元时,这提供了比动态分配阵列的 SoA 快 10 倍的性能。这是怎么回事?为什么静态和动态分配的内存之间存在如此大的性能差异?
在这种情况下,数组的结构对缓存不友好。
您同时使用u
和v
,但是如果它们有 2 个不同的数组,它们将不会同时加载到一个缓存行中,缓存未命中将造成巨大的性能损失。
_mm_prefetch
可用于更快地AoS
表示。
预取对于花费大部分执行时间等待数据显示的代码至关重要。 现代前端总线具有足够的带宽,预取应该是安全的,前提是您的程序不会超出其当前负载集太远。
由于各种原因,结构和类可能会在C++中产生许多性能问题,并且可能需要进行更多调整才能获得可接受的性能级别。 当代码很大时,请使用面向对象的编程。 当数据很大(并且性能很重要(时,请不要这样做。
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];
您应该注意的两件事可能会产生巨大的差异,具体取决于您的 CPU:
- 对准
- 高速缓存行别名
由于您使用的是 SSE4,因此使用专门的内存分配函数返回在 16 字节边界对齐的地址而不是new
可能会给您带来提升,因为您或编译器将能够使用对齐的加载和存储。我没有注意到较新的 CPU 有太大区别,但在较旧的 CPU 上使用未对齐的负载和存储可能会慢一点。
至于高速缓存行混叠,英特尔在其参考手册中明确提到了它。(搜索"英特尔® 64 和 IA-32 架构优化参考手册"(。英特尔表示,这是您应该注意的事情,尤其是在使用 SoA 时。因此,您可以尝试的一件事是填充数组,以便其地址的较低 6 位不同。这个想法是为了避免让它们争夺相同的缓存行。
当然,如果你没有实现矢量化,就没有太多的动力进行SoA转换。
除了事实上相当广泛地接受__RESTRICT之外,gcc 4.9 还采用了#pragma GCC ivdep
来打破假定的别名依赖关系。
至于显式预取的使用,如果它有用,当然你可能需要更多 SoA 预取。 主要点可能是通过提前获取页面来加速 DTLB 未命中解析,因此您的算法可能会变得更加需要缓存。
我认为如果没有更多细节,包括有关操作系统的细节,就无法对所谓的"编译时"分配进行明智的评论。 毫无疑问,在高层次上分配和重用分配的传统很重要。
- 从函数中全局删除并重新实例化数组结构,而无需在编译时知道数组的大小
- 如何使用函数的输出初始化 const 数组结构字段?
- 传递数组结构、ofstream 和 interger 以运行
- 从文本文件中读取并输入到数组结构中,然后显示读取的数据C++
- 将文本文件读取到数组结构中
- C/C++中数组结构和数组结构的通用接口
- C++ MDC final-在字符类型的数组结构中按字母顺序对记录中的名称进行排序
- C++ 使用数组结构创建平衡的二叉搜索树
- C++:释放动态数组(结构成员)和指向此结构的指针的方法
- 使用 vector 在 c++ 中声明 3D 数组结构
- 数组结构无法正确打印
- 如何在C++中访问数组结构内部的数组结构
- CIN进入数组结构似乎什么也没输入
- ifstream将数组结构到txt文件中,然后尽可能将其提取为数组
- wlanapi-将WlanFreeMemory释放其WLAN_INTERFACE_INFO数组结构
- 显示数组结构 c++
- C++ 使用函数访问数组结构的方法是什么?
- 将庞大的数组结构复制到 GPU
- 如何封送包含字符矩阵的数组结构
- C++多维数组结构的对齐