问题描述
我一直在做一个在线法官的任务:实现 int sum(const int* array,unsigned int len)
以便它返回和的数组。 len
可以是 200,000,这个函数可以被调用 200,000 次;我的程序必须在 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
时,此代码没有通过。
谁能指出哪些指令在时间上是昂贵的,以及如何加速我的代码?
解决方法
快速检查一下,看起来大多数合理的最新处理器都提供两个加载端口和两个用于加法的端口,因此至少从理论上讲,您应该通过展开循环的两次迭代获得不错的收益(尽管如果数据非常很大,它可能会很快归结到主内存的带宽)。
与任何 AVX 操作一样,您希望确保您正在处理的数据正确对齐。如果数据未对齐,较旧的处理器会出错。较新的可以工作,但你会受到相当严重的速度损失。
,实施@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
花费更多的时间。