2015-02-23 609 views
5
读取

我的问题是,在下面的语句从书的方面:阵列随机结构(AOS)与数组(SOA)结构的量化

不幸的是,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] 
} 

和定义这些的阵列得到随机从一些数据源

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 */ 
} 

这将导致更好的性能,在SoA的情况下,我会在缓存行上推送更多信息,以便在计算过程中实际需要。一些CPU预缓存?在AoS的情况下,取而代之的是更好的性能。

我的假设是正确的还是还有别的?

+3

可怕的三字母首字母缩略词:SoA =阵列结构,AoS =结构阵列。 – 2015-02-23 14:59:31

+0

@HansPassant:他们就是这样称呼的,写下完整的名字会让标题太长而且不难看。 – Tigran 2015-02-23 15:01:51

+3

@Tigran:为了那些不了解你的书的术语的人的利益,定义一次术语并不需要很长时间。 – 2015-02-23 15:05:24

回答

8

您可以通过两种方式并行化您的程序:水平和垂直。我认为你正在混合这两种方法。

水平并行化将SIMD单元中的每个通道作为独立的“线程”处理不同的数据。垂直并行化使整个SIMD单元在相同的数据对象上工作,试图从其内部的多维度中受益。

举一个具体的例子:考虑你有2个数组XY你想添加的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; 
    } 
    
  • 垂直的方法:所述SIMD单元的每个车道取相同向量的不同分量:

    for(idx = 0; idx<size; idx+=1) { 
        ... = X[idx].coord(laneid) + Y[idx].coord(laneid); 
    } 
    

垂直方法更容易实现。事实上,编译器正在尝试自动矢量化。问题是,随着SIMD单元的宽度不断增长,实施不能从中受益。如果您从4宽转换为16宽SIMD,那么您的3D矢量仍然只能并行添加3个数字。

水平方法更难。你通常必须处理分歧的分支,函数调用等......并且 - 你想将数据重新组织到数组结构中 - 以便不同数据对象的相应字段在内存中彼此相邻。


现在,回到你的问题:SOA使感只有当你做横向并行。当每个通道访问不同对象的相同字段时,SoA允许用更好的对齐的单个内存提取来代替昂贵的收集指令。 如果你试图做垂直,就像你在问题中的例子 - 没有人会考虑首先做SoA - 访问同一对象的多个字段会导致“聚集”。

但是,对于随机访问,即使执行水平并行化,SoA也可能不是最佳选项。首先,你没有SoA的好处,因为你仍然需要做昂贵的聚会。但是,由于同一对象的字段分布在内存中,因此每个负载都会碰到不同的缓存通道。不仅增加了内存带宽的使用,还可能导致缓存抖动。 这就是为什么SoA在随机访问方面效率不高的原因。

更好的解决方案是采用混合方法:将数据打包为SIMD-size结构的数组结构。但这是另一回事......

+1

可能要提到SoA的病态情况 - 其中同一逻辑结构的两个组件具有缓存行争用(从一个读取导致另一个卸载) – Yakk 2015-02-23 15:23:10

1

是的,你似乎了解情况。

如果您从相同的结构中读取多个值,那么CPU将只需要获取它需要的那些结构成员的缓存行数 - 如果结构成员布局合理,可能只有一个。所以缓存可能是这样的(其中v是你想要的值,而空插槽其他值)

line 1: | v | | v | v | | | v | | 

如果每个这些值都必须从一个单独的数组读取,那么它将不得不去取整个缓存行为每个值。所以缓存可能看起来像

line 1: | | | v | | | | | | 
line 2: | | | | | v | | | | 
line 3: | | v | | | | | | | 
line 4: | | | v | | | | | | 

如果你通过数组为了工作,那是很好的 - 你很快就会需要的是为获取额外的价值。然而,如果你不按顺序工作(用本书的话来说,你处于“随机或不连贯的情况”),那么每次获取比你需要的更多的东西会浪费缓存中的空间,如果所需的值在一个结构中一起使用,最终会使用更多的内存带宽。