您的技术可能的主要改进是滑动窗口。整数加法有一个逆减法。这使得可以从总和中删除元素并添加新元素,而不是从头开始重新计算。 (Terryfkjc's answer demonstrates this)。
正如 Terryfkjc 对他的回答所评论的那样,存在线程级并行性。您可以让不同的线程检查数组的不同部分。如果滑动窗口的大小超过数组大小的一半,那么求和第一个n 应该是线程的。当n 大约是数组大小的一半时,在线程之间划分工作是最棘手的。大得多,它不能滑得那么远。小得多,而且大部分工作只是滑动。
如果您的目标是带有向量指令的 CPU,我们可以使用 SIMD 轻松地向量化第一个 w(窗口大小/宽度)元素的初始总和。例如,使用 x86 SSE(C/C++ 内在函数):
#include <immintrin.h>
const __m128i *array_vec = (__m128i*)array;
__m128i sums = _mm_loadu_si128(array_vec); // or start with _mm_setzero_si128()
// careful to get the loop start/end and increment correct,
// whether you count by vectors (i++) or by elements (i+=4)
// You may need a scalar cleanup at the end if window width isn't a multiple of the vector width
for (int i=1; i < width/4 ; i+=1) {
sums = _mm_add_epi32(sums, _mm_loadu_si128(array_vec+i));
// if you know the input is aligned, and you're going to compile without AVX/AVX2, then do:
// sums = _mm_add_epi32(sums, array_vec[i]);
}
__m128i hisums = _mm_bsrli_si128(sums, 8); // get the high 2 elements into the low 2 elements of a separate vector
sums = _mm_add_epi32(sums, hisums); // one step of horizontal merging.
hisums = _mm_bsrli_si128(sums, 4); // actually, pshufd would be faster for this, since it doesn't need a mov to preserve sums.
sums = _mm_add_epi32(sums, hisums);
int window_sum = _mm_cvtsi128_si32(sums); // get the low 32bit element
我认为不可能矢量化滑动窗口部分。我们不能有效地滑动 4 个单独的窗口,因为每个窗口(向量元素)都需要查看每个数组元素。
但是,如果我们对 4/8/16 种不同的窗口大小(最好是连续的)感兴趣,那么我们可以使用向量来实现。因此,在每次循环迭代中,我们都有一个带有当前滑动窗口总和的向量。使用 SSE4.1 pmaxsd(对于有符号的 32 位整数),我们可以做到
window_max = _mm_max_epi32(window_max, window_sums);
滑动窗口操作变为:添加{ a[i], a[i+1], a[i+2], a[i+3] },同时从所有4个向量元素中删除相同的数组元素。 (或者,广播要添加的元素,并减去不同元素的向量。)
__m128i add_elements = _mm_loadu_si128((__m128i*)&array[i]); // load i, i+1, etc.
__m128i sub_element = _mm_cvtsi32_si128(array[i-width-1]);
sub_element = _mm_shuffle_epi32(sub_element, 0); // broadcast the low element of the vector to the other positions.
// AVX2: use _mm_broadcastd_epi32
// AVX512 has a broadcast mode for memory references that can be used with most instructions. IDK how to use it with intrinsics.
__m128i diff = _mm_sub_epi32(add_elements, sub_element);
window_sums = _mm_add_epi32(window_sums, diff);
正确获取循环开始/结束条件,并正确考虑最后几个元素,始终是向量的挑战。
有关如何在 x86 上使用向量的更多信息,请参阅 https://stackoverflow.com/tags/x86/info。