Файл: Лабораторная работа 4 по дисциплине Вычислительные системы Многопоточное программирование Проверил.docx

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 05.12.2023

Просмотров: 50

Скачиваний: 3

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ «НОВОСИБИРСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ»

Кафедра вычислительной техники

ЛАБОРАТОРНАЯ РАБОТА № 4

по дисциплине «Вычислительные системы»

Многопоточное программирование

Выполнил: Проверил:

Студентки: Преподаватель: Перышкова Е.Н.

Алиева Н.И

Коломеец О.В

Группа: АММ2-22

Новосибирск

2023

Задание

1. Для программы умножения двух квадратных матриц DGEMM BLAS разработанной в лабораторной работе №3 на языке С/С++ реализовать многопоточные вычисления. В потоках необходимо реализовать инициализацию массивов случайными числами типа double и равномерно распределить вычислительную нагрузку. Обеспечить возможность задавать размерность матриц и количество потоков при запуске программы. Многопоточность реализовать несколькими способами.

1) С использованием библиотеки стандарта POSIX Threads.

2) С использованием библиотеки стандарта OpenMP.

3) *C использованием библиотеки Intel TBB.

4) **C использованием библиотеки стандарта MPI. Все матрицы помещаются в общей памяти одного вычислителя.

5) ***C использованием технологий многопоточности для графических сопроцессоров (GPU) - CUDA/OpenCL/OpenGL/OpenACC.

2. Для всех способов организации многопоточности построить график зависимости коэффициента ускорения многопоточной программы от числа потоков для заданной размерности матрицы, например, 5000, 10000 и 20000 элементов.

3. Определить оптимальное число потоков для вашего оборудования.

4. Подготовить отчет отражающий суть, этапы и результаты проделанной работы.

Ход работы

В ходе выполнения работы была написана программа на С. Многопоточность реализована с использованием библиотек стандарта POSIX Threads, OpenMP.

Были построены графики зависимости коэффициента ускорения (дефолтное время выполнения/ускоренное время выполнения) от числа потоков для заданных размерностей матрицы (128, 256, 512, 1024).



Рис.1 - Зависимость коэффициента ускорения от числа потоков для размерности матрицы = 128




Рис.2 - Зависимость коэффициента ускорения от числа потоков для размерности матрицы = 256



Здесь используется функция pthread_create для создания потоков, которые выполняют функцию multiply, производящую умножение блоков матриц. Каждый поток получает свой идентификатор thread_id, а также вычисляет начальную и конечную строку, для которых он отвечает.

Для синхронизации доступа к общей матрице C здесь не используется никаких мьютексов или семафоров, так как каждый поток работает с разными строками и не пересекается с другими потоками.



Тут мы используем директиву #pragma omp parallel for, чтобы распараллелить внешний цикл в функции dgemm(). Затем мы вызываем omp_set_num_threads() для установки количества потоков, которые мы хотим использовать.



Рис.3 - Зависимость коэффициента ускорения от числа потоков для размерности матрицы = 512



Рис.4 - Зависимость коэффициента ускорения от числа потоков для размерности матрицы = 1024

Оптимальным числом потоком для данного оборудования можно назвать 4. Оно совпадает с количеством ядер оборудования.

Вывод

В ходе выполнения лабораторной работы были изучены библиотеки для параллельных вычислений, что позволило снизить время вычислений.

Для выполнения задания использовались библиотеки OpenMP и POSIX.

В ходе работы было выявлено оптимальное число потоков (=4).

Листинг
#include


#include

#include

#define MATRIX_SIZE 1000 // Размерность матриц

#define NUM_THREADS 4 // Количество потоков

double A[MATRIX_SIZE][MATRIX_SIZE]; // Матрица A

double B[MATRIX_SIZE][MATRIX_SIZE]; // Матрица B

double C[MATRIX_SIZE][MATRIX_SIZE]; // Матрица C

void *multiply(void *arg) {

int thread_id = *(int*)arg;

int chunk_size = MATRIX_SIZE / NUM_THREADS; // Размер блока для каждого потока

int start_row = thread_id * chunk_size; // Начальная строка для текущего потока

int end_row = start_row + chunk_size - 1; // Конечная строка для текущего потока

// Умножение блоков матриц A и B

for (int i = start_row; i <= end_row; i++) {

for (int j = 0; j < MATRIX_SIZE; j++) {

C[i][j] = 0;

for (int k = 0; k < MATRIX_SIZE; k++) {

C[i][j] += A[i][k] * B[k][j];

}

}

}

pthread_exit(NULL);

}

int main() {

// Инициализация матриц A и B случайными числами

for (int i = 0; i < MATRIX_SIZE; i++) {

for (int j = 0; j < MATRIX_SIZE; j++) {

A[i][j] = (double)rand() / RAND_MAX;

B[i][j] = (double)rand() / RAND_MAX;

}

}

// Создание потоков

pthread_t threads[NUM_THREADS];

int thread_ids[NUM_THREADS];

for (int i = 0; i < NUM_THREADS; i++) {

thread_ids[i] = i;

pthread_create(&threads[i], NULL, multiply, &thread_ids[i]);

}

// Ожидание завершения всех потоков

for (int i = 0; i < NUM_THREADS; i++) {

pthread_join(threads[i], NULL);

}

return 0;

}

Библиотека OpenMp

#include

#include

#include

#include

void dgemm(int size, std::vector& A, std::vector& B, std::vector& C) {

#pragma omp parallel for

for (int i = 0; i < size; ++i) {

for (int j = 0; j < size; ++j) {

double cij = 0;

for (int k = 0; k < size; ++k) {

cij += A[i*size+k] * B[k*size+j];

}

C[i*size+j] = cij;

}

}

}

int main(int argc, char* argv[]) {

if (argc < 3) {

std::cerr << "Usage: " << argv[0] << " " << std::endl;

return 1;

}

const int size = std::stoi(argv[1]);

const int threads = std::stoi(argv[2]);

std::vector A(size*size);

std::vector B(size*size);

std::vector C(size*size);

srand(time(NULL));

for (int i = 0; i < size*size; ++i) {

A[i] = (double)rand() / RAND_MAX;

B[i] = (double)rand() / RAND_MAX;

C[i] = 0;

}

omp_set_num_threads(threads);

auto start = std::chrono::high_resolution_clock::now();

dgemm(size, A, B, C);

auto end = std::chrono::high_resolution_clock::now();

std::chrono::duration duration = end - start;

std::cout << "Time taken: " << duration.count() << " seconds" << std::endl;

return 0;

}