使用 Intel Intrinsics 快速查找整数数组的总和

Posted

技术标签:

【中文标题】使用 Intel Intrinsics 快速查找整数数组的总和【英文标题】:Using Intel Intrinsics to quickly find sum of array of integers 【发布时间】:2021-01-02 01:50:07 【问题描述】:

我一直在做一个在线法官的任务:实现int sum(const int* array, unsigned int len) ,让它返回总和的数组。 len可以是20万,这个函数可以调用20万次;我的程序必须在 0.9 秒内执行。

目前,我的代码如下所示:

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

int sum(const int* array, unsigned int len) 
    register int i = 8, s = 0;
    __m256i sm = _mm256_loadu_si256((void *)(array));
    for (; i+8 < len; i += 8) 
        const __m256i x = _mm256_loadu_si256((void *)(array+i));
        sm = _mm256_add_epi32(sm, x);
    
    sm = _mm256_hadd_epi32(sm, sm);
    sm = _mm256_hadd_epi32(sm, sm);
    s = _mm256_extract_epi32(sm, 0);
    s += _mm256_extract_epi32(sm, 4);
    for(; i < len; ++i) s += array[i];
    return s;

但是,由于法官报告Time limit exceeded,此代码未通过。

谁能指出哪些指令在时间上是昂贵的,以及如何加快我的代码速度?

【问题讨论】:

评论不用于扩展讨论;这个对话是moved to chat。 【参考方案1】:

快速检查一下,看起来最合理的最新处理器提供了两个加载端口和两个用于添加的端口,因此至少从理论上讲,您应该通过展开循环的两次迭代来获得可观的收益(尽管如果数据非常很大,它可能很快就会归结为主内存的带宽)。

与任何 AVX 操作一样,您希望确保正在使用的数据正确对齐。如果数据未对齐,较旧的处理器将出现故障。较新的也可以,但您会受到相当严重的速度损失。

【讨论】:

【参考方案2】:

实施@JerryCoffin 的建议:


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

int sum(const int* array, unsigned int len) 
    if(len < 60) 
        int s = 0;
        for(int i = 0; i < len; ++i) s += array[i];
        return s;
    
    register int i = 0, s = 0;
    __m256i sm = _mm256_loadu_si256((void *)(array+i));
    __m256i sm2 = _mm256_loadu_si256((void *)(array+i+8));
    i += 16;
    for (; i+16 < len; i += 16) 
        const __m256i x = _mm256_loadu_si256((void *)(array+i));
        sm = _mm256_add_epi32(sm, x);
        const __m256i y = _mm256_loadu_si256((void *)(array+i+8));
        sm2 = _mm256_add_epi32(sm2, y);
    
    sm = _mm256_add_epi32(sm, sm2);
    sm = _mm256_hadd_epi32(sm, sm);
    sm = _mm256_hadd_epi32(sm, sm);
    s += _mm256_extract_epi32(sm, 0);
    s += _mm256_extract_epi32(sm, 4);
    for(; i < len; ++i) s += array[i];
    return s;


有趣的是,由于该函数被调用了这么多次,在数组对齐之前消耗整数实际上比使用 loadu 花费更多时间。

【讨论】:

因为函数被调用了很多次 - 你的意思是计数很少,所以启动开销会很高?是的,尤其是如果数据经常正确对齐,将它留给硬件通常是好的。 (虽然对于重复处理相同字节两次的东西很便宜,例如复制并添加到非重叠输出中,但未对齐且可能重叠的第一个向量可能很好)。当 L1d 缓存中的数据不热时,支持 AVX2 的 CPU 通常会以足够快的速度吸收错位成本,以跟上 L3 或 RAM(仅慢几个 %),甚至大部分都与 L2 保持同步。 如果小数组性能很重要,请使用更好的水平总和 (Fastest method to calculate sum of all packed 32-bit integers using AVX512 or AVX2),并考虑使用您屏蔽的未对齐向量负载来处理尾部以避免重复计算。 (如果奇数大小是正常的,也可以使用 0..3 __m128i 向量来将需要的标量清理量从 0..15 减少到 0..3 标量)。但是,清理代码的分支预测可能会出现问题;如果您可以分析在线评委的工作量,请 IDK。 如果您受 L2 带宽限制,您可能需要查看 this

以上是关于使用 Intel Intrinsics 快速查找整数数组的总和的主要内容,如果未能解决你的问题,请参考以下文章

Intel Intrinsics pack 命令误解

无符号字符图像上的快速高斯模糊 - ARM Neon Intrinsics - iOS Dev

快速整明白Redis中的字典到底是个啥

RenderScript Intrinsics 高斯模糊

打开查找超大文件可使用—— EmEditor

Intrinsics 与 Naive Vector 减少结果的差异