Tig*_*ran 5 c++ parallel-processing vectorization cpu-cache
我的问题是关于本书中的以下短语:
不幸的是,SoA表格并非在所有情况下都是理想的.对于随机或不连贯的情况,收集用于访问数据,SoA表单可能导致额外的不需要的数据被读入缓存,从而降低性能.在这种情况下,使用AoS表单将导致更小的工作集和更高的性能.但是,通常,如果要对计算进行矢量化,则优选SoA形式.
我猜测 AoS可能导致更好性能的原因是,当同一结构中的不同或更好的所有字段都参与单个矢量化运行时.
示例(只是概念,没有具体或工作代码):
/*Note that the types of data I maintain the same intentionally,
to simplify discussion*/
struct Data {
float mean;
float distribution[10]
}
Run Code Online (Sandbox Code Playgroud)
并定义从某些数据源中随机获得的数组
Data aos[5];
现在,如果在矢量化循环期间我做了类似的事情:
float* dataPtr = &(aos[0].mean);
#pragma simd
for(int i=0; i< 60; i++)
{
const float mean = (*dataPtr);
/*do something with mean */
dataPtr++;
/*do something with distribution */
}
Run Code Online (Sandbox Code Playgroud)
这将导致更好的性能,因为在SoA的情况下,我将在高速缓存行上推送我在计算期间可能实际需要的更多信息.有些CPU预缓存?在AoS的情况下,相反会产生更好的性能.
我的假设是正确的,还是还有别的?
您可以通过两种方式并行化程序:水平和垂直.我认为你正在混合这两种方法.
水平并行化将SIMD单元中的每个通道视为处理不同数据的单独"线程".垂直并行化使整个SIMD单元处理同一数据对象,试图从其内部多维度中受益.
举一个具体的例子:考虑你有2个数组X
和Y
你想要添加的3D矢量.
水平方法:SIMD单元的每个通道都可以:
for(idx = 0; idx<size; idx+=SIMD_size) {
... = X[idx+laneid].x + Y[idx+laneid].x;
... = X[idx+laneid].y + Y[idx+laneid].y;
... = X[idx+laneid].z + Y[idx+laneid].z;
}
Run Code Online (Sandbox Code Playgroud)垂直方法:SIMD单元的每个通道采用相同向量的不同组件:
for(idx = 0; idx<size; idx+=1) {
... = X[idx].coord(laneid) + Y[idx].coord(laneid);
}
Run Code Online (Sandbox Code Playgroud)垂直方法更容易实现.事实上,编译器正在尝试自动向量化.问题是随着SIMD单元的宽度不断增长,实施不能从中受益.如果从4宽切换到16宽SIMD,您仍然只能将3D矢量并行添加3个数字.
横向方法更难.您通常必须处理不同的分支,函数调用等...并且 - 您希望将数据重新组织为数组结构 - 以便您的不同数据对象的相应字段在内存中彼此相邻.
现在,回到您的问题:只有在进行水平并行化时,SoA才有意义.当每个通道访问不同对象的相同字段时,SoA允许用更好对齐的单个内存提取替换昂贵的聚集指令.如果你试图做垂直,就像你问题中的例子 - 没有人会考虑首先做SoA - 访问同一个对象的多个字段会导致"聚集".
但是,使用随机访问,即使进行水平并行化,SoA也可能不是最佳选择.首先,你没有得到SoA的好处,因为你仍然需要做昂贵的聚会.但是,由于同一对象的字段分布在内存中,因此每个加载都会遇到不同的缓存通道.它不仅会增加内存带宽的使用,还可能导致缓存抖动.这就是为什么SoA随机访问效率不高的原因.
更好的解决方案是采用混合方法:将数据打包到大小为SIMD的数组结构阵列中.不过那是另一回事了...