[英]OpenMP C++ Matrix Multiplication run slower in parallel
我正在學習使用 OpenMP 並行執行 for 循環的基礎知識。
遺憾的是,我的並行程序運行速度比串行版本慢 10 倍。 我究竟做錯了什么? 我錯過了一些障礙嗎?
double **basicMultiply(double **A, double **B, int size) {
int i, j, k;
double **res = createMatrix(size);
omp_set_num_threads(4);
#pragma omp parallel for private(k)
for (i = 0; i < size; i++) {
for (j = 0; j < size; j++) {
for (k = 0; k < size; k++) {
res[i][j] += A[i][k] * B[k][j];
}
}
}
return res;
}
非常感謝!
您的問題是由於內部循環變量j
上的競爭條件造成的。 它需要私有化。
對於 C89,我會做這樣的事情:
#pragma omp parallel
{
int i, j, k;
#pragma omp for
for(i=0; ...
對於 C++ 或 C99 使用混合聲明
#pragma omp parallel for
for(int i=0; ...
這樣做您不必明確聲明任何共享或私有的內容。
對您的代碼的一些進一步評論。 當您執行B[k][j]
時,您的單線程代碼對緩存不友好。 這會讀取一個緩存行,然后移動到下一個緩存行,依此類推,直到完成點積,此時其他緩存行已被逐出。 相反,您應該先進行轉置並以BT[j][k]
。 此外,您分配了數組數組而不是一個連續的二維數組。 我修復了您的代碼以使用轉置和連續的二維數組。
這是我獲得的 size=512 的時間。
no transpose no openmp 0.94s
no transpose, openmp 0.23s
tranpose, no openmp 0.27s
transpose, openmp 0.08s
下面是代碼(另見http://coliru.stacked-crooked.com/a/ee174916fa035f97 )
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
void transpose(double *A, double *B, int n) {
int i,j;
for(i=0; i<n; i++) {
for(j=0; j<n; j++) {
B[j*n+i] = A[i*n+j];
}
}
}
void gemm(double *A, double *B, double *C, int n)
{
int i, j, k;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
double dot = 0;
for (k = 0; k < n; k++) {
dot += A[i*n+k]*B[k*n+j];
}
C[i*n+j ] = dot;
}
}
}
void gemm_omp(double *A, double *B, double *C, int n)
{
#pragma omp parallel
{
int i, j, k;
#pragma omp for
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
double dot = 0;
for (k = 0; k < n; k++) {
dot += A[i*n+k]*B[k*n+j];
}
C[i*n+j ] = dot;
}
}
}
}
void gemmT(double *A, double *B, double *C, int n)
{
int i, j, k;
double *B2;
B2 = (double*)malloc(sizeof(double)*n*n);
transpose(B,B2, n);
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
double dot = 0;
for (k = 0; k < n; k++) {
dot += A[i*n+k]*B2[j*n+k];
}
C[i*n+j ] = dot;
}
}
free(B2);
}
void gemmT_omp(double *A, double *B, double *C, int n)
{
double *B2;
B2 = (double*)malloc(sizeof(double)*n*n);
transpose(B,B2, n);
#pragma omp parallel
{
int i, j, k;
#pragma omp for
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
double dot = 0;
for (k = 0; k < n; k++) {
dot += A[i*n+k]*B2[j*n+k];
}
C[i*n+j ] = dot;
}
}
}
free(B2);
}
int main() {
int i, n;
double *A, *B, *C, dtime;
n=512;
A = (double*)malloc(sizeof(double)*n*n);
B = (double*)malloc(sizeof(double)*n*n);
C = (double*)malloc(sizeof(double)*n*n);
for(i=0; i<n*n; i++) { A[i] = rand()/RAND_MAX; B[i] = rand()/RAND_MAX;}
dtime = omp_get_wtime();
gemm(A,B,C, n);
dtime = omp_get_wtime() - dtime;
printf("%f\n", dtime);
dtime = omp_get_wtime();
gemm_omp(A,B,C, n);
dtime = omp_get_wtime() - dtime;
printf("%f\n", dtime);
dtime = omp_get_wtime();
gemmT(A,B,C, n);
dtime = omp_get_wtime() - dtime;
printf("%f\n", dtime);
dtime = omp_get_wtime();
gemmT_omp(A,B,C, n);
dtime = omp_get_wtime() - dtime;
printf("%f\n", dtime);
return 0;
}
此外。 “Z boson”,我已經在帶有 intel i5(2 個物理內核或 4 個邏輯內核)的筆記本電腦上測試了您的 C 代碼。 不幸的是,計算速度不是很快。 對於2000x2000隨機雙矩陣,我獲得了以下結果(使用 VS 2010 和 OpenMP 2.0):
為 Win64 編譯:C = A*B,其中 A、B 是大小為 (2000x2000) 的矩陣:
最大線程數 = 4
創建隨機矩陣:= 0.303555 s
沒有轉置沒有 openmp = 100.539924 s
沒有轉置,openmp = 47.876084 s
轉置,沒有 openmp = 27.872169 s
轉置,openmp = 15.821010 秒
為 Win32 編譯:C = A*B,其中 A、B 是大小為 (2000x2000) 的矩陣:
最大線程數 = 4
創建隨機矩陣:= 0.378804 s
沒有轉置沒有 openmp = 98.613992 s
沒有轉置,openmp = 48.233655 s
轉置,沒有 openmp = 29.590350 s
轉置,openmp = 13.678097 秒
請注意,對於“Hynek Blaha”代碼,我系統上的計算時間為739.208 秒( openMP為 226.62 秒)!
而在Matlab x64 中:
n = 2000;
A = rand(n); B = rand(n);
tic
C = A*B;
toc
計算時間為0.591440 秒。
但是使用openBLAS包我達到了0.377814 秒的速度(使用 minGW 和 openMP 4.0)。 Armadillo包提供了一種簡單的方法(在我看來)將矩陣運算與 openBLAS(或其他類似的包)連接起來。 在這種情況下,代碼是
#include <iostream>
#include <armadillo>
using namespace std;
using namespace arma;
int main(){
int n = 2000;
int N = 10; // number of repetitions
wall_clock timer;
arma_rng::set_seed_random();
mat A(n, n, fill::randu), B(n, n, fill::randu);
timer.tic();
// repeat simulation N times
for(int n=1;n<N;n++){
mat C = A*B;
}
cout << timer.toc()/double(N) << "s" << endl;
return 0;
}
如果size
很小,線程同步的開銷將掩蓋並行計算帶來的任何性能提升。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.