В данной статье будут рассмотрены стандарты 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.