C++ Pthread и OpenMP Умножение матриц

В данной статье будут рассмотрены стандарты POSIX Threads и OpenMP. А именно параллельное умножение матриц NxN с использованием данных стандартов и их сравнение.

1) Код умножения матриц с использованием OpenMP.

[cpp]
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <iostream>
#include <sys/time.h>
#include <omp.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <fcntl.h>
#include <string.h>

int *alloc_matrix(int size){
int *matrix = (int*)malloc(size * size * sizeof(int));
printf("matrix %dx%d allocated\n", size, size);
return matrix;
}

void del_matrix(int *matrix){
free(matrix);
}

double dtime(){
struct timeval tv;
struct timezone tz;
double t;
gettimeofday(&tv, &tz);
t = (double)tv.tv_sec + (double)tv.tv_usec*1e-6;
return(t);
}

int main() {
int N = 50;
int THR = 2;
int i, j, k;

printf("Введите число потоков\n");
scanf("%i",&THR);

omp_set_dynamic(0);
omp_set_num_threads(THR);

printf("Введите размер матрици\n");
scanf("%i",&N);

int *A = alloc_matrix(N);
int *B = alloc_matrix(N);
int *C = alloc_matrix(N);

int rand_num;

srand(time(NULL));

for(int i=0; i<N; i++){
for(int j=0; j<N; j++){
A[i + j * N] = rand() % 5;
B[i + j * N] = rand() % 5;
}
}

double t0 = dtime();
#pragma omp parallel for shared(A, B, C, N) private(i, j, k)
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
for (k = 0; k < N; k++) {
C[i+j*N] += A[i + k * N] * B[k + j * N];
}
}
}
printf("time for multiplying: %f\n", dtime()-t0);
return 0;
}
[/cpp]

2) Код умножения матриц с использованием Pthread.

[cpp]
#include #include
#include
#include
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/times.h>
#include
#include
#define SIZE 50

int *alloc_matrix(int size){
int *matrix = (int*)malloc(size * size * sizeof(int));
printf("matrix %dx%d allocated\n", size, size);
return matrix;
}

void del_matrix(int *matrix){
free(matrix);
}

double dtime(){
struct timeval tv;
struct timezone tz;
double t;
gettimeofday(&tv, &tz);
t = (double)tv.tv_sec + (double)tv.tv_usec*1e-6;
return(t);
}

struct matrix_args{
int *m1;
int *m2;
int *ans;
int size;
int start;
int end;
} ARG[100];

void *multiply_matrix(void *pargs){
struct matrix_args *args = (struct matrix_args *) pargs;
int i, j, k, l, m, tmp = 0;
double t0 = dtime();
int *m1 = args->m1;
int *m2 = args->m2;
int *ans = args->ans;
int size = args->size;
int start = args->start;
int end = args->end;
for(i = start; i < end; i++){
m = i * size;
for(j = 0; j < size; j++){
l = 0;
for(k = 0; k < size; k++){
tmp += m1[m + k] * m2[j + l];
l += size;
}
ans[m + j] = tmp;
tmp = 0;
}
}
printf("thread works %fs\n", dtime() — t0);
pthread_exit(NULL);
}

main(int argc, char** argv){
int i, j, size, k, step, pos, res;
int THR_NUM,THR,N;

printf("Введите число потоков\n");
scanf("%i",&THR);

printf("Введите размер матрици\n");
scanf("%i",&N);
THR_NUM = THR;
size = N;

pthread_t threads[THR_NUM];
pthread_attr_t attr;
printf("allocating\n");
int *m1 = alloc_matrix(size);
int *m2 = alloc_matrix(size);
int *ans = alloc_matrix(size);
srand(time(NULL));

for(int i=0; i<size; i++){
for(int j=0; j<size; j++){
m1[i + j * size] = rand() % 5;
m2[i + j * size] = rand() % 5;
}
}

printf("multiplying\n");
double t0 = dtime();
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
step = (int)((double)size/(double)THR_NUM);
pos = 0;
for(k = 0; k < THR_NUM; k++){
ARG[k].m1 = m1;
ARG[k].m2 = m2;
ARG[k].ans = ans;
ARG[k].size = size;
ARG[k].start = pos;
pos += step;
ARG[k].end = (k == THR_NUM — 1) ? size : pos;
res = pthread_create(&threads[k], &attr, multiply_matrix, (void *)&ARG[k]);
if(res){
fprintf(stderr, "Error creating thread\n");
exit(1);
}
}
pthread_attr_destroy(&attr);
for(k = 0; k < THR_NUM; k++)
pthread_join(threads[k], NULL);
printf("time for multiplying: %f\n", dtime()-t0);
del_matrix(m1);
del_matrix(m2);
del_matrix(ans);
pthread_exit(NULL);
}
[/cpp]

3) Тестирование

Умножение матриц размера 1000×1000 элементов на процессоре Intel Atom

Число потоков Pthread OpenMP
1 поток 55.123835s 92.834934s
2 потока 47.135015s 75.482384s
4 потока 51.729996s 77.169735s
10 потоков 52.890145s 87.410493s

Исходя из полученных данных видно что незначительное увеличение производительности происходит на 2ух потоках из за технологии hyper threading, которую поддерживает процессор . Последующее увеличение числа потоков не привело к росту производительности а наоборот замедлило. Так же видно что ручное распараллеливание за счет Pthread эффективней нежели автоматическое за счет OpenMP.

Умножение матриц размера 1000×1000 элементов на процессоре Intel Xeon

Число потоков Pthread OpenMP
1 поток 12.609208s 16.054086s
2 потока 6.245414s 8.072486s
4 потока 2.883950s 4.013952s
10 потоков 3.654870s 4.112602s

Исходя из полученных данных видно что производительность растет на 2х и 4х потоках так как процессор в состоянии выделить под каждый поток отдельное ядро. Но производительность начинает падать когда мы пытаемся запустить программу в 10 потоков, это происходит и за затрат времени на квантование между потоками. Из данной таблицы можно предположить что эффективней всего запускать по одному потоку на ядро (лучшую производительность приложение показало на 4х потоках). Так же видно что распараллеливание вручную через Pthread оказалось эффективней чем автоматическое через OpenMP.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *