OpenMP/归约
外观
< OpenMP
对于我们的第一个并行程序,我们转向一个古老的问题:求和一个浮点数数组。解决这个问题的基本算法非常简单,它允许我们专注于 OpenMP 特性而不是算法细节,但我们很快就会发现,这个问题实际上并不像一开始看起来那么简单。
不用多说,以下是一个顺序算法来求和浮点数列表
#include <stddef.h> // for size_t
float sum(const float *a, size_t n)
{
float total;
size_t i;
for (i = 0, total = 0.; i < n; i++) {
total += a[i];
}
return total;
}
就算法而言,这个算法很简单。将上面的定义放入文件 isum.c
(迭代求和)中。
- 如果您有处理浮点数的经验,您可能会想将
total
设置为double
而不是float
以获得更高的精度。现在不要这样做,因为我们稍后会用另一种方法来解决精度问题。
为了测试我们的算法,我们需要一个驱动程序,我们将把它放在文件 main.c
中
#include <stdio.h>
#include <stdlib.h>
float sum(const float *, size_t);
#define N 1000000 // we'll sum this many numbers
int main()
{
float *a = malloc(N * sizeof(float));
if (a == NULL) {
perror("malloc");
return 1;
}
// fill the array a
for (size_t i = 0; i < N; i++) {
a[i] = .000001;
}
printf("%f\n", sum(a, N));
return 0;
}
最后,我们需要一些方法来构建这个程序。在 Linux/Unix/OS X 上,以下 Makefile
应该可以完成这项工作。它假设您使用的是 GCC。
# C99 extensions are not necessary for OpenMP, but very convenient
CFLAGS = -fopenmp -Wall -std=c99
LDFLAGS = -fopenmp
OBJS = main.o isum.o
# when copy-pasting the following, be aware that the indent must be a tab, not spaces
sum: $(OBJS)
$(CC) $(LDFLAGS) -o sum $(OBJS)
现在用 make sum
编译程序,运行它,并使用 time
等工具查看它的速度。如果速度太快无法测量,请考虑将 N
更改为更大的数字,或者在循环中运行 sum
,而不仅仅运行一次。
我们不得不经过一些设置,但现在我们已经准备好为浮点数创建一个并行求和算法。它在这里
#include <stddef.h> // for size_t
float sum(const float *a, size_t n)
{
float total = 0.;
#pragma omp parallel for reduction(+:total)
for (size_t i = 0; i < n; i++) {
total += a[i];
}
return total;
}
#pragma omp parallel for
将循环变成一个并行循环。如果您有两个核心,OpenMP 将(可能)使用两个线程,每个线程运行一半的循环。reduction(+:total)
声明我们正在对输入数组进行归约,通过求和到变量 total
,所以部分循环完成后,他们的结果必须被加到这个变量中。
将此代码放入 isum.c
中并重新编译。现在运行程序。您得到的结果是否与之前相同?
- 练习:使用环境变量
OMP_NUM_THREADS
的各种设置运行程序,该变量控制 OpenMP 使用的线程池的大小。尝试 1、2、4 和 8。您是否看到每个设置的相同结果?现在尝试荒谬的大量线程,例如 16000。这如何影响性能?
- 练习:两个向量的点积是它们各自项的乘积的总和,. 修改
sum
函数以用于并行计算点积的dot
函数。