C 矢量化:对齐和未对齐阵列

C 矢量化:对齐和未对齐阵列,c,openmp,vectorization,C,Openmp,Vectorization,这个问题只是想对循环矢量化有更多的了解,特别是使用OpenMP4。下面给出的代码生成“大小”随机样本,然后从这些样本中我们从位置“QPO”提取一块“q”的“qsize”样本。然后程序会在“samples”数组中找到“q”的位置。代码如下: #include <float.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #i

这个问题只是想对循环矢量化有更多的了解,特别是使用OpenMP4。下面给出的代码生成“大小”随机样本,然后从这些样本中我们从位置“QPO”提取一块“q”的“qsize”样本。然后程序会在“samples”数组中找到“q”的位置。代码如下:

#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <mm_malloc.h>

// SIMD size in floats, assuming 1 float = 4 bytes
#define VEC_SIZE 8
#define ALIGN (VEC_SIZE*sizeof(float))

int main(int argc, char *argv[])
{
    if (argc!=4)
    {
        printf("Usage: %s <size> <qsize> <qpos>",argv[0]);
        exit(1);
    }
    int size = atoi(argv[1]);
    int qsize = atoi(argv[2]);
    int qpos = atoi(argv[3]);
    assert(qsize < size);
    assert((qpos < size - qsize) && (qpos >= 0));

    float *samples;
    float *q;

    samples = (float *) malloc(size*sizeof(float));
    q = (float *) _mm_malloc(size*sizeof(float),ALIGN);

    // Initialization
    // - Randomly filling the samples
    samples[0] = 0.0;
    for (int i = 1 ; i < size; i++) //LOOP1
        samples[i] = samples[i-1] + rand()/((float)RAND_MAX) - 0.5;

    // - Getting q from the samples
#pragma omp simd aligned(q:ALIGN)
    for (int i = 0; i < qsize; i++) //LOOP2
        q[i] = samples[qpos+i];

    // Finding the best match (since q is taken form the samples it self
    // the position of the best match must be qpos)
    float best_dist = FLT_MAX;
    int pos = -1;
    for (int i = 0; i < size - qsize; i++)//LOOP 3
    {
        float dist = 0;
#pragma omp simd aligned(q:ALIGN) reduction(+:dist)        
        for (int j = 0; j < qsize; j++)//LOOP4
            dist += (q[j] - samples[i+j]) * (q[j] - samples[i+j]);
        if (dist < best_dist)
        {
            best_dist = dist;
            pos = i;
        }
    }    
    assert(pos==qpos);
    printf("Done!\n");

    free(samples);
    _mm_free(q);
}
“q”通过使用_mm_malloc()对齐。对“samples”执行同样的操作是没有意义的,因为最内部的循环(LOOP4)总是以任何方式访问它的未对齐元素

gcc和icc都报告了LOOP4的矢量化(实际上,如果我们省略了gcc拒绝执行的“#pragma omp simd”,icc会自动矢量化循环,但这只是一个额外的观察)。从矢量化报告来看,似乎没有编译器生成剥离循环。我的问题是:

1) 编译器如何处理“示例”未对齐的事实

2) 这会在多大程度上影响性能

3) icc对LOOP2的矢量化没有问题。但是gcc不能:“注意:未矢量化:基本块中没有足够的数据引用”。有什么想法吗


谢谢

以下是我在测试运行流包以测试可持续内存带宽时的一些经验

1) 据我所知,“英特尔编译器”不会生成用于检查对齐的代码,它将使用一些等效的
movdqu
加载示例,使用
movdqa
加载q


2) 这取决于内存带宽和可用触发器的比率。循环4只需要很少的计算量,我猜你当前在现代HPC上的程序只会受到内存带宽的限制,因为样本和q的大小很大,修正对齐没有多大帮助。然而,如果你限制你的核心数量,那么对齐就不再像以前那么重要了。现代处理器处理未对齐的访问的速度几乎和对齐的一样快。那么为什么要对齐呢?我想说的是,如果你想获得最大的性能,对齐仍然很重要。我只是说编译器无论如何都会矢量化,即使它没有对齐。(对于最近的处理器)它们不会特意检查或特殊情况下的对齐。只是因为失调的开销可能比分支和特殊情况处理的开销要小。@Mystical:好的,同意:-)谢谢你的回答。我同意你对第二个问题的回答,但我的问题更倾向于这个方向(很抱歉,我的问题不太清楚):使用
movdqu
而不是
movdqa
,成本会有多高?或者一般来说,使用指令从未对齐的内存加载/存储要比使用指令从未对齐的内存加载/存储要贵多少?对于问题3),我也同意你的看法,但我想知道“基本块中没有足够的数据引用”2)对于串行访问的含义,惩罚可能很小,但如果你随机访问大量的
双精度
,你将更经常地从缓存边界加载双精度,并具有与额外缓存线未命中相当的惩罚。
icc vec-test.c -o icc-vec-test -std=c11 -qopt-report=3 -qopt-report-phase=vec -qopt-report-file=icc.vec -O3 -xHost -fopenmp
gcc vec-test.c -o gcc-vec-test -std=c11 -fopt-info-vec-missed-optimized=gcc.vec -O3 -march=native -fopenmp