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函式。