gpt4 book ai didi

c - SSE/AVX + OpenMP : fast sum of arrays

转载 作者:行者123 更新时间:2023-12-01 22:19:46 28 4
gpt4 key购买 nike

我正在对两个数组求和并输出第三个数组(不是缩减)。像这样:

void add_scalar(float* result, const float* a, const float* b, const int N) {   
for(int i = 0; i<N; i++) {
result[i] = a[i] + b[i];
}
}

我想以最大的吞吐量来做这件事。对于 SSE 和四核,我天真地期望速度提高 16 倍(SSE 为四倍,四核为四倍)。我已经用 SSE(和 AVX)实现了代码。 Visual Studio 2012 具有自动矢量化功能,但我通过“展开循环”获得了更好的结果。我为四种大小的数组(32 字节对齐)运行我的代码:小于 32KB、小于 256KB、小于 8MB 和大于 8MB,对应于 L1、L2、L3 缓存和主内存。对于 L1,我看到使用展开的 SSE 代码(使用 AVX 时为 5-6)大约有 4 倍的加速。这和我期望的一样多。之后每个缓存级别的效率都会下降。然后我使用 OpenMP 在每个内核上运行。我在数组的主循环之前放置了“#pragma omp parallel for”。但是,我获得的最佳加速是 SSE + OpenMP 的 5-6 倍。有谁知道为什么我没有看到 16 倍的加速?也许是由于数组从系统内存到缓存的一些“上传”时间?我意识到我应该分析代码,但这本身就是我必须学习的另一次冒险。

#define ROUND_DOWN(x, s) ((x) & ~((s)-1))  
void add_vector(float* result, const float* a, const float* b, const int N) {
__m128 a4;
__m128 b4;
__m128 sum;
int i = 0;
for(; i < ROUND_DOWN(N, 8); i+=8) {
a4 = _mm_load_ps(a + i);
b4 = _mm_load_ps(b + i);
sum = _mm_add_ps(a4, b4);
_mm_store_ps(result + i, sum);
a4 = _mm_load_ps(a + i + 4);
b4 = _mm_load_ps(b + i + 4);
sum = _mm_add_ps(a4, b4);
_mm_store_ps(result + i + 4, sum);
}
for(; i < N; i++) {
result[i] = a[i] + b[i];
}
return 0;
}

我的错误主循环有这样的竞争条件:

float *a = (float*)_aligned_malloc(N*sizeof(float), 32);
float *b = (float*)_aligned_malloc(N*sizeof(float), 32);
float *c = (float*)_aligned_malloc(N*sizeof(float), 32);
#pragma omp parallel for
for(int n=0; n<M; n++) { //M is an integer of the number of times to run over the array
add_vector(c, a, b, N);
}

我根据 Grizzly 的建议更正了主循环:

for(int i=0; i<4; i++) {
results[i] = (float*)_aligned_malloc(N*sizeof(float), 32);
}
#pragma omp parallel for num_threads(4)
for(int t=0; t<4; t++) {
for(int n=0; n<M/4; n++) { //M is an integer of the number of times to run over the array
add_vector(results[t], a, b, N);
}
}

最佳答案

免责声明:和你一样,我没有分析代码,所以我不能绝对肯定地回答。

您的问题很可能与内存带宽或并行化开销有关。

你的循环是非常轻计算的,因为它对 3 个内存操作执行 1 个加法运算,这使你自然受到内存带宽的限制(考虑到 ALU 吞吐量比现代架构中的内存带宽好得多)。因此,您的大部分时间都花在了传输数据上。

如果数据小到足以容纳缓存,您可以(理论上)将 openmp 线程绑定(bind)到特定内核并确保 vector 的正确部分位于特定内核的 L1/L2 缓存中,但这不会真的很有帮助,除非你可以并行初始化(当你传输数据时并不重要,如果你必须这样做的话)。因此,将数据从一个核心缓存传输到另一个核心缓存会受到影响。

如果数据不适合处理器缓存,您最终会受到主内存带宽的限制。由于预取,一个内核可能几乎可以用尽这种简单访问模式的带宽,让您的增长空间很小。

要记住的第二点是,创建 omp parallel 构造和分配循环会产生一定的开销。对于小型数据集(适合 L1/L2/L3 的数据集可能符合条件),这种开销很容易与计算时间本身一样高,几乎没有加速。

关于c - SSE/AVX + OpenMP : fast sum of arrays,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15271800/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com