3 / 1302_3_3
.pdf
МИНОБРНАУКИ РОССИИ САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ ЭЛЕКТРОТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ «ЛЭТИ» ИМ. В.И. УЛЬЯНОВА (ЛЕНИНА) Кафедра САПР
ОТЧЕТ по лабораторной работе №3
по дисциплине «Архитектура параллельных вычислительных систем» ТЕМА: «Решение СЛАУ итерационными методами на системах
с общей памятью» Вариант 3
Студенты гр. 1302 |
Харитонов А.А. |
Наволоцкий И.Р.
Солончак И.П.
Преподаватель |
Костичев С.В. |
Санкт-Петербург
2025
Цель работы
Практическое освоение методов решения систем линейных алгебраических уравнений (СЛАУ) итерационными методами на вычислительных системах с общей памятью.
1.Задание на лабораторную работу
1.1.Разработать алгоритм решения СЛАУ методом Зейделя (ГауссаЗейделя) для последовательных и параллельных вычислений.
1.2.Написать и отладить программы на языке С++, реализующие разработанные алгоритмы последовательных и параллельных вычислений с использованием библиотек OpenMP и mpi.
1.3.Запустить программы для следующих значений размерности СЛАУ: 5, 10, 100, 500, 1000, 5000, 10000.
1.4.Оценить размерность СЛАУ, при которой эффективнее использовать алгоритмы последовательного и параллельного вычислений для разного числа потоков (по крайней мере для меньшего, равного и большего, чем число процессоров).
2. Программное и аппаратное окружение при выполнении работы
При выполнении лабораторной работы использовался ноутбук Apple MacBook Air с чипом M1, содержащим 8-ядерный CPU и 7-ядерный GPU, а также 8 Гб оперативной памяти. Работа выполнялась под управлением macOS Sequoia 15.7.1.
Среда разработки: Visual Studio Code 1.105.1, система сборки: CMake, управляемый через расширение CMake Tools в VS Code.
3. Формализация задачи Справка:
Метод Зейделя (Гаусса-Зейделя) — это итерационный метод решения систем линейных алгебраических уравнений (СЛАУ). Является
2
модификацией метода Якоби. Основное отличие заключается в том, что при вычислении нового приближения неизвестной ! используются уже найденные на текущей итерации новые значения ", … , !#", а не старые значения с предыдущей итерации. Это обычно ускоряет сходимость, но создает сложности при распараллеливании из-за зависимости данных.
Алгоритм работы программы:
1.Компиляция и запуск: исходный код 1302_3_3.cpp компилируется компилятором clang++ с флагами поддержки OpenMP (- fopenmp) в исполняемый файл, который затем запускается.
2.Проверка корректности (Example Test):
•Для наглядного подтверждения правильности работы алгоритма запускается функция runExample.
•Решается заранее заданная СЛАУ размерности 2 2
(4 " + $ = 5, " + 3 $ = 4).
•Выполняется 6 итераций, на каждой из которых
выводятся текущие значения. Результаты сверяются с ожидаемыми (стремление к 1.0).
3.Начало основного тестирования: программа переходит к блоку бенчмарков. Инициализируется массив для хранения результатов измерений времени.
4.Цикл по размерностям данных: запускается основной цикл, перебирающий заданные размерности СЛАУ (N): {5, 10, 100, 500, 1000, 5000, 10000}.
5.Для каждого размера N выполняются следующие шаги: a. Генерация данных:
•Вызывается функция generateSystem.
•Создается матрица и вектор . Матрица генерируется с условием диагонального преобладания (сумма модулей недиагональных элементов строки меньше
3
диагонального элемента), что является необходимым условием сходимости метода Зейделя.
•Используется потокобезопасная генерация случайных чисел (rand_r) с зерном, зависящим от текущего времени и номера строки.
b.Замер времени последовательного решения:
•Засекается начальное время (chrono::high_resolution_clock).
•Вызывается функция runSequential, реализующая классический метод Зейделя в одном потоке.
•Вычисления продолжаются до достижения точности EPSILON или лимита итераций MAX_ITER.
•Итоговое время сохраняется в миллисекундах.
c.Цикл по количеству потоков:
•Запускается внутренний цикл по заданному набору потоков: {2, 4, 6, 8, 10, 12, 14, 16}.
•Устанавливается число потоков через omp_set_num_threads.
•Засекается начальное время.
•Вызывается функция runParallel. Распараллеливание внешнего цикла итераций реализуется директивой #pragma omp parallel for с редукцией переменной ошибки (reduction(max:max_error)).
•Итоговое время выполнения для каждого числа потоков сохраняется в структуру результатов.
d.Очистка памяти: векторы и матрица очищаются для освобождения оперативной памяти перед следующей (более крупной) итерацией.
4
6.Вывод результатов:
•Формируется и выводится отформатированная сводная таблица.
•Столбцы таблицы соответствуют размерностям N, строки – количеству потоков (Serial, 2 threads, ..., 16 threads). Ячейки содержат время выполнения в миллисекундах.
4.Примеры работы программы
Вычисления производились с точностью = 10#% и максимальным количеством итераций 2000.
Исходный код программы приведён в приложении А. Блок-схемы алгоритмов приведены в приложении Б.
4.1.Демонстрация работы программы
При запуске программа сначала выполняет вычисления в демонстрационном режиме для решения системы уравнений малой размерности (2x2), точное решение которой известно заранее ( " = 1, $ = 1). Как видно на рисунке 1, программа выводит на экран процесс итеративного приближения решения: значения переменных " и $ с каждой итерацией стремятся к единице.
Рисунок 1 – Пример работы программы при решении СЛАУ 2x2
5
Этот шаг служит для визуального подтверждения математической корректности реализации метода Зейделя перед переходом к масштабным замерам производительности.
4.2.Анализ результатов производительности
После верификации программа выполняет серию автоматизированных тестов для измерения времени решения СЛАУ разных размерностей (от 5 до 10000 неизвестных) с использованием последовательного и параллельного (OpenMP) алгоритмов. Результаты замеров для разного числа потоков сведены в общую таблицу, представленную на рисунке 2.
Рисунок 2 – Итоговая сводная таблица производительности для последовательного и параллельного метода Зейделя
4.2.1. Влияние размера массива на время выполнения
Метод Зейделя имеет квадратичную временную сложность ( $) на одну итерацию. Полученные экспериментальные данные для последовательной версии наглядно подтверждают эту зависимость:
•При увеличении размера в 10 раз (от 1000 до 10000 элементов) время выполнения выросло примерно в 101 раз (с 41.366 мс до 4188.043 мс), что практически идеально совпадает с теоретическим ростом в 100 раз (10$).
•При увеличении размера в 2 раза (от 5000 до 10000 элементов) время выполнения выросло почти в 3.8 раза (с 1097.205 мс до 4188.043 мс), что близко к теоретическому росту в 4 раза (2$).
6
Это подтверждает, что сложность алгоритма является доминирующим фактором, и с ростом размерности матрицы необходимость в параллельных вычислениях резко возрастает.
4.2.2. Эффективность параллельных вычислений
Сравнение последовательного и параллельного (OpenMP) методов показывает наличие «порога вхождения», обусловленного накладными расходами.
•На малых размерах (5, 10, 100): параллельный алгоритм работает медленнее или сопоставимо с последовательным. Например, для = 10 последовательная версия занимает 0.009 мс, а версия на 4 потоках – 0.147 мс. Это объясняется накладными расходами на создание и управление потоками OpenMP (fork-join overhead), которые превышают полезную вычислительную нагрузку.
•На больших размерах (5000 и 10000): начиная с = 500, параллельная версия становится значительно эффективнее.
o Для = 10000 последовательная версия выполняется за 4188 мс.
o Лучший параллельный результат (16 потоков) составляет 1145 мс.
o Ускорение составляет 3.65 раза.
Это демонстрирует, что для систем с общей памятью точка безубыточности находится на сравнительно малых размерностях (около = 100), после чего параллелизм дает ощутимый выигрыш.
4.2.3. Влияние количества потоков и масштабируемость
Анализ времени выполнения OpenMP-алгоритма при разном количестве потоков выявляет ограничение аппаратной масштабируемости.
7
•Линейный рост (до 4 потоков): на размере = 10000 при переходе от 1 к 4 потокам время сокращается с 4188 мс до 1366 мс (ускорение ~3x). Это демонстрирует хорошую масштабируемость.
•Насыщение (после 8 потоков):
o На 8 потоках время составляет 1194 мс.
o На 16 потоках время составляет 1145 мс.
o Прирост производительности при удвоении числа потоков с 8 до 16 практически отсутствует (разница менее 5%). Это явление связано с аппаратной архитектурой процессора (в данном случае Apple M1, имеющего 8 физических ядер). Использование числа потоков, превышающего количество физических ядер, не дает прироста, так как:
1.Ограничение пропускной способности памяти (Memory Wall): метод Зейделя интенсивно работает с памятью. Несколько ядер начинают конкурировать за доступ к шине данных, и скорость вычислений упирается в скорость RAM, а не процессора.
2.Context Switching: при запуске 16 потоков на 8 ядрах планировщик ОС вынужден постоянно переключать задачи, что создает дополнительные накладные расходы.
Таким образом, для данной реализации оптимальным является
использование числа потоков, равного количеству физических ядер (8), так как дальнейшее увеличение не дает экономического эффекта.
Вывод
В ходе лабораторной работы были реализованы и сравнены последовательный и параллельный (OpenMP) итерационные методы решения СЛАУ (метод Зейделя).
Анализ показал, что параллельная версия демонстрирует высокую эффективность на больших матрицах (от 500 элементов), достигая максимального ускорения в 3.65 раза на матрице размерностью 10000. На
8
малых данных использование многопоточности нецелесообразно из-за накладных расходов на управление потоками.
Было установлено, что производительность масштабируется до достижения физического предела ядер процессора (8 потоков). Дальнейшее увеличение числа потоков (до 16) выводит производительность на плато. Это свидетельствует о том, что ограничивающим фактором для данного класса задач на системах с общей памятью становится пропускная способность подсистемы памяти, а не вычислительная мощность ядер.
Работа подтвердила эффективность использования OpenMP для распараллеливания циклов в итерационных алгоритмах линейной алгебры при условии достаточной вычислительной нагрузки на каждый поток.
9
ПРИЛОЖЕНИЕ А Листинги исходного кода программы
1302_3_3.cpp
#include <iostream> #include <vector> #include <cmath> #include <omp.h> #include <chrono> #include <iomanip> #include <string> #include <ctime>
using namespace std;
// Настройки констант
const double EPSILON = 1e-6; const int MAX_ITER = 2000; const int MANUAL_ITERS = 6;
// Тестовые данные
const vector<int> SIZES = {5, 10, 100, 500, 1000, 5000, 10000}; const vector<int> THREAD_COUNTS = {2, 4, 6, 8, 10, 12, 14, 16};
//Настройки ширины таблицы const int W_LABEL = 20; const int W_DATA = 12;
//Пример решения СЛАУ void runExample() {
cout << "\n=== EXAMPLE TEST (2x2 SYSTEM) ===" << endl;
cout << "System:\n4*x1 + 1*x2 = 5\n1*x1 + 3*x2 = 4\nExpected: x1=1, x2=1\n" << endl;
10
