这是矢量化的好习惯吗?

时间:2017-02-11 12:16:43

标签: c++ parallel-processing vectorization

我正在尝试通过向量化此函数来提高this代码的性能:

inline float calcHaarPattern( const int* origin, const SurfHF* f, int n )
{
    double d = 0;
    for( int k = 0; k < n; k++ )
        d += (origin[f[k].p0] + origin[f[k].p3] - origin[f[k].p1] - origin[f[k].p2])*f[k].w;
    return (float)d;
}

据我所知,您可以对包含完全一个数学运算的循环进行矢量化。在上面的代码中,我们有5个数学运算,所以(使用OMP):

#pragma omp simd
for( int k = 0; k < n; k++ )
        d += (origin[f[k].p0] + origin[f[k].p3] - origin[f[k].p1] - origin[f[k].p2])*f[k].w;

不会工作。但是,我在考虑是否将上面的循环分成多个循环而只进行一次数学运算是一个很好的矢量化实践?结果代码为:

double p0[n], p3[n], p1[n], p2[n];
#pragma omp simd
for( int k = 0; k < n; k++ )
        p0[k] = origin[f[k].p0]*f[k].w;
#pragma omp simd
for( int k = 0; k < n; k++ )
        p3[k] = origin[f[k].p3]*f[k].w;
#pragma omp simd
for( int k = 0; k < n; k++ )
        p1[k] = origin[f[k].p1]*f[k].w;
#pragma omp simd
for( int k = 0; k < n; k++ )
        p2[k] = origin[f[k].p2]*f[k].w;
#pragma omp simd
for( int k = 0; k < n; k++ )
        d += p0[k];
#pragma omp simd
for( int k = 0; k < n; k++ )
        d -= p1[k];
#pragma omp simd
for( int k = 0; k < n; k++ )
        d -= p2[k];
#pragma omp simd
for( int k = 0; k < n; k++ )
        d += p3[k];

这是一个很好的解决方案,还是有更好的解决方案?现代编译器(比如说gcc)会自己做这种(或更好)的优化(例如启用-O3)(所以实际上没有性能提升)?

1 个答案:

答案 0 :(得分:1)

这通常是糟糕的HPC编程实践,原因如下:

  1. 这些天你通常必须使你的代码尽可能计算密集。要实现这一点,您需要尽可能为循环提供更高的算术强度( AI )。为简单起见,将AI视为[计算量]的比例除以[为了执行这些计算而移入/移出存储器的字节数]。 通过拆分循环,您可以为每个循环创建更低的AI,因为您不再为不同的计算重用相同的字节。
  2. (向量 - 或线程 - )并行缩减(在您的情况下由变量&#34; d&#34;)具有您不想乘以的成本/开销8或10(即用你手工制作的圈数)。
  3. 在许多情况下,当同一个数据对象的多个数据字段在同一个循环体中处理而不是分裂循环情况时,英特尔/ CGG编译器矢量化引擎可以稍微优化一些。
  4. 循环分裂的理论优势也很少,但它们并不适用于您的情况,所以我提供它们以防万一。在以下情况下,循环拆分是合理的/有利可图的:

    • 有多个循环带有依赖或减少 同样的循环。
    • 如果循环拆分有助于摆脱一些乱序执行 数据流依赖性。
    • 如果您没有足够的向量寄存器来执行所有计算(对于复杂的算法)。

    Intel Advisor(在前面的questons中提到过)有助于分析这些因素并测量AI。

    良好的编制者也不会关心&#34;每当你有一个这样的循环或循环分裂时,因为它们可以轻松地将一个案例转换为另一个案例,反之亦然。 然而,在实际代码中,这种转换的适用性非常有限,因为为了做到这一点,你必须在编译时知道很多额外的信息:无论何时指针或动态数组重叠或不重叠,无论数据是否对齐所以你不应该依赖编译器转换和特定的编译器次要版本,而只需要尽可能多地编写HPC就绪代码。