AVX2 1GB 长阵列

Posted

技术标签:

【中文标题】AVX2 1GB 长阵列【英文标题】:AVX2 1GB long array 【发布时间】:2017-11-03 23:09:39 【问题描述】:

我有一个 1gb 长的数组,在 .bin 文件中有浮点数。在我读完它之后,我如何用 avx2 指令对元素求和,并打印结果?

我用 Jake 'Alquimista' LEE 的回答编辑了我的代码。 问题是结果比它要小得多。还有其他问题,我如何为从 .bin 文件中读取的每个数字添加一个常量?

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <immintrin.h>

inline float sumf(const float *pSrc, uint32_t len)

    __m256 sum, in;
    float sumr;
    uint32_t sumi;
    uint32_t lenr = len & 7;
    while (len--)
    len >>= 3;
    sum = _mm256_set1_ps(0.0f);
    
        in = _mm256_loadu_ps(pSrc++);
        sum = _mm256_add_ps(in, sum);
    

    sum = _mm256_hadd_ps(sum, in);
    sum = _mm256_hadd_ps(sum, in);
    sum = _mm256_hadd_ps(sum, in);
    sumi = _mm256_extract_epi32(*(__m256i *)&sum, 0);
    sumr = *(float *)&sumi;

    while (lenr--)
    
        sumr += *pSrc++;
    

    return sumr;



int main(void)


        FILE *file;

        float *buffer2;
        uint32_t fileLen;

        if((file = fopen("example.bin","rb"))==NULL)
        
                printf("Error! opening file");
                exit(1);
        


        fseek(file, 0, SEEK_END);
        fileLen=ftell(file);
        fseek(file, 0, SEEK_SET);
    buffer2=(float *)malloc(fileLen+1);
        if (!buffer2)
        
                fprintf(stderr, "Memory error!");
                                fclose(file);
                return 0;
        


        fread(buffer2, fileLen, 1, file);
        fclose(file);
        printf( "File size : %lu Bits \n", fileLen );
        for(int i = 0; i<10; i++)
        printf("%f \n", buffer2[i]);

    float sum =sumf(buffer2,fileLen);
        printf("%f\n",s);
        free(buffer2);
        return 0;

【问题讨论】:

您不需要 AVX2。 IO 将成为您的瓶颈。只需管道 IO 和简单的求和代码就可以了。 考虑将文件作为块读取。将浮点数分组为一对 __m256 浮点数(可能使用强制转换或使用向量加载内在函数),然后在两个向量上使用 _mm256_add_ps(a, b) 执行加法。稍后我可以帮助编写一段代码。 向数组中的每个元素添加一个常量有什么意义?您可以将const * len 添加到最终结果中。 顺便说一句,您应该保留原来的问题。 @RafaNadal95 我已经添加了一个答案。检查一下,告诉我结果如何。 【参考方案1】:

将 1GB 文件读入内存是大内存和 I/O 开销。虽然我对AVX2 不是很熟悉,但我阅读了互联网上的文章,我可以提出以下解决方案,该解决方案经过实际测试并证明有效。 我的解决方案包括将文件读取为 512 字节的块(128 个浮点数的块),然后总结向量对(每块 16 个总向量),以便最后我们得到一个最终的 __m256 向量,通过将其转换为float* 我们可以总结其各个组成部分以获得最终结果。 文件不是 128 浮点数对齐的情况在最后一个 for 循环中通过汇总各个浮点数来处理。 代码已注释,但如果您有任何建议来为答案添加更多解释,请随时这样做。

#include <immintrin.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int     make_floatf(char *, int);
float   avx_sfadd(char*);

char error_buf[1024];

#define PERROR()                            \
    do                                     \
        strerror_r(errno, error_buf, 1024); \
        printf("Error: %s\n", error_buf);   \
        fclose(fp);                         \
        return -1;                          \
     while(0)

/* This function generates a .bin file containing blocks 
 *   of 128 floating point numbers
 */
int make_floatf(char *filename, int nblocks)

    FILE *fp = NULL;

    if(!(fp = fopen(filename, "wb+")))
        PERROR();

    float *block_ptr = malloc(sizeof(float) * 128);  /* 512 Bytes block of 128 floats */
    if(!block_ptr)
        PERROR();

    int j, i;

    for(j = 0; j < nblocks; j++)
    
        for(i = 0; i < 128; i++)
            block_ptr[i] = 1.0;

        int ret = fwrite(block_ptr, sizeof(float), 128, fp);
        if(ret < 128)
        
            free(block_ptr);
            PERROR();
        
    

    free(block_ptr);
    fclose(fp); 

    return 0;


/* This function reads the .bin file as chuncks of 512B 
 * blocks (128 floating point numbers) and calculates thier sum.
 * The final sum in a form of vector is looped through and its 
 * components are summed up to get the final result.
 */
float avx_sfadd(char *filename)

    FILE *fp = NULL;

    __m256  v1;
    __m256  v2;
    __m256  sum = _mm256_setzero_ps();

    if(!(fp = fopen(filename, "rb")))
       PERROR();

    struct stat stat_buf;
    stat(filename, &stat_buf);

    size_t fsize     = stat_buf.st_size;
    size_t nblocks   = fsize / (sizeof(float) * 128); 
    size_t rem_size  = fsize - nblocks * sizeof(float) * 128;
    size_t rem_floats = rem_size / (sizeof(float));

    printf("File size: %ld\nnblocks:%ld\nnremfloats: %ld\n",\
            fsize, nblocks, rem_floats); 

    /* This memory area will hold the 128 floating point numbers per block */
    float *block_ptr = malloc(sizeof(float) * 128);
    if(!block_ptr)
        PERROR();

    int i;
    for(i = 0; i < nblocks; i++)
    
        int ret = fread(block_ptr, sizeof(float), 128, fp);
        if(ret < 128)
            PERROR();   

        /* Summing up vectors in a block of 16 vectors (128 floats) */
        int j;
        for(j = 0; j < 16; j += 2)
        
            v1 = _mm256_loadu_ps(block_ptr + j*8);
            v2 = _mm256_loadu_ps(block_ptr + (j+1)*8);

            sum += _mm256_add_ps(v1, v2);
         
    

    /* Handling the case if the last chunck of the file doesn't make 
     * a complete block.
     */
    float rem_sum = 0;
    if(rem_size > 0)
    
        int ret = fread(block_ptr, 1, rem_size, fp);
        if(ret < rem_floats)
            PERROR();

        int j;
        for(j = 0; j < rem_floats; j++)
            rem_sum += block_ptr[j];
    

    float final_sum = rem_sum;
    float *sum_ptr = (float*)&sum; /* The final vector hold the sum of all vectors */

    /* Summing up the values of the last vector to get the final result */
    int k;
    for(k = 0; k < 8; k++)
        final_sum += sum_ptr[k];

    free(block_ptr);
    fclose(fp);

    return final_sum;



int main(int argc, char **argv)

    if(argc < 2)
        puts("./main filename [nblocks]");
        return 0;
    

    /* ./main filename number_of_block_to_create (eg. ./main floats.bin 1024 )*/
    else if(argc == 3)

        if(!make_floatf(argv[1], atoi(argv[2])))
            puts("File has been created sucessfully\n");
    

    /* ./main filename (eg. ./main floats.bin) to calculate sum*/
    else 
        printf("avx_sum = %f\n", avx_sfadd(argv[1])) :


    return 0;

【讨论】:

【参考方案2】:

这是(很可能)你的错误:

while (len--)
len >>= 3;

这是一个while循环。只要 len != 0,您将 len 替换为 (len - 1) >> 3。然后将其更改为 -1。看不到循环。

【讨论】:

糟糕,这是怎么发生的?我一定是贴错了位置。谢谢指出。【参考方案3】:
inline float sumf(const float *pSrc, uint32_t len)

    __m256 sum, in;
    float sumr;
    uint32_t sumi;
    uint32_t lenr = len & 7;
    len >>= 3;
    sum = _mm256_set1_ps(0.0f);
    while (len--)
    
        in = _mm256_loadu_ps(pSrc++);
        sum = _mm256_add_ps(in, sum);
    
    in =  *(__m256 *)&_mm256_permute4x64_pd(*(__m256d *)&sum, 0b01001110);
    sum = _mm256_hadd_ps(sum, in);
    sum = _mm256_hadd_ps(sum, in);
    sum = _mm256_hadd_ps(sum, in);
    sumi = _mm256_extract_epi32(*(__m256i *)&sum, 0);
    sumr = *(float *)&sumi;

    while (lenr--)
    
        sumr += *pSrc++;
    

    return sumr;

上面的函数就可以了。但是,我认为它不会带来太多的性能提升(如果有的话),因为它是非常微不足道的,而且编译器无论如何都会对其进行自动矢量化。

请注意,当您将它们作为参数传递时,您必须将指向 float * 的指针进行类型转换,并将 filelen 除以 sizeof(float)

【讨论】:

实际上编译器一般不能自动向量化典型的求和循环,因为浮点加法不是可交换的,所以向量化会产生与标量循环不同的结果。您可以使用-Ofast 告诉编译器“我不在乎,将其向量化”,然后使用it will。 sum = _mm256_hadd_ps(sum, in);为什么是三次? @RafaNadal95 您即将添加八个浮点数。您需要将对添加三次(8 - 4 - 2 - 1),然后提取最低元素。 @Jake 'Alquimista' LEE 我在你的帮助下编辑了我的问题。希望您能再次提供帮助。 您的水平总和不太正确 - 仔细查看 _mm256_hadd_ps 的工作原理。像许多 AVX 指令一样,它在两个 128 位通道上运行,而不是一个完整的 256 位操作。您可能只想调用它两次,然后提取元素 0 和 4。

以上是关于AVX2 1GB 长阵列的主要内容,如果未能解决你的问题,请参考以下文章

多阵列动态内存分配错误

如何使用 JavaScript 将长数组拆分为更小的数组

以阵列一个圆为例,绘制矩形阵列环形阵列

线性阵列与圆周阵列

PERCH330阵列卡啥意思

h330阵列卡和h730阵列卡性能有多大差距?