minhasapov_ruslan_lab_5 #210

Open
safgerd wants to merge 1 commits from minhasapov_ruslan_lab_5 into main
3 changed files with 184 additions and 0 deletions

20
minhasapov_ruslan_lab_5/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
*.fsproj.user
*.fsproj.cache
*.fsproj
*.suo
*.user
*.cache
bin/
obj/
*.dll
*.exe
*.pdb
*.zip
*.tar.gz
*~
*.bak
.DS_Store
Thumbs.db

View File

@ -0,0 +1,77 @@
open System
open System.Threading
open System.Diagnostics
type Matrix = double[,]
// Функция для умножения части матрицы
let multiplyPart (A: Matrix) (B: Matrix) (C: Matrix) startRow endRow =
let size = A.GetLength(0)
for i in startRow .. endRow - 1 do
for j in 0 .. size - 1 do
for k in 0 .. size - 1 do
C.[i, j] <- C.[i, j] + A.[i, k] * B.[k, j]
// Последовательное умножение матриц
let multiplySequential (A: Matrix) (B: Matrix) : Matrix =
let size = A.GetLength(0)
let C = Array2D.zeroCreate size size
multiplyPart A B C 0 size
C
// Параллельное умножение матриц с синхронизацией
let multiplyParallel (A: Matrix) (B: Matrix) numThreads : Matrix =
let size = A.GetLength(0)
let C = Array2D.zeroCreate size size
let rowsPerThread = size / numThreads
let threads = Array.zeroCreate numThreads
for i in 0 .. numThreads - 1 do
let startRow = i * rowsPerThread
let endRow = if i = numThreads - 1 then size else startRow + rowsPerThread
threads.[i] <- new Thread(fun () -> multiplyPart A B C startRow endRow)
// Запускаем потоки
threads |> Array.iter (fun t -> t.Start())
// Ожидаем завершения всех потоков
threads |> Array.iter (fun t -> t.Join())
C
// Функция для генерации матрицы
let generateMatrix size =
let matrix = Array2D.zeroCreate size size
let rnd = Random()
for i in 0 .. size - 1 do
for j in 0 .. size - 1 do
matrix.[i, j] <- double (rnd.Next(10))
matrix
[<EntryPoint>]
let main argv =
let sizes = [ 100; 300; 500 ]
let threadCounts = [ 1; 2; 4; 8; 16; 32; 64; 128; 256; 512]
// Заголовки таблицы
printfn "| Размер | Потоков | Алгоритм | Время (мс) |"
for size in sizes do
for numThreads in threadCounts do
let A = generateMatrix size
let B = generateMatrix size
let stopwatch = Stopwatch.StartNew()
let algorithm =
if numThreads = 1 then
multiplySequential A B |> ignore; "Последовательный"
else
multiplyParallel A B numThreads |> ignore; "Параллельный"
stopwatch.Stop()
// Вывод результатов в виде таблицы
printfn "| %dx%d | %d | %s | %d |" size size numThreads algorithm stopwatch.ElapsedMilliseconds
0

View File

@ -0,0 +1,87 @@
# Лабораторная работа №5
#### ПИбд-42. Минхасапов Руслан.
---
**Цель:** Реализовать последовательный и параллельный алгоритмы умножения квадратных матриц и сравнить их производительность.
---
**Описание задачи:** В работе реализованы два алгоритма умножения матриц:
* **Последовательный:** Классический алгоритм умножения матриц с тремя вложенными циклами.
* **Параллельный:** Алгоритм, распараллеливающий вычисления по строкам результирующей матрицы. Каждому потоку назначается подмножество строк для вычисления. Число потоков задается параметром.
---
**Описание параллельного алгоритма:**
Параллельный алгоритм `multiplyParallel` разделяет вычисления между заданным количеством потоков. Входные матрицы `A` и `B`, а также результирующая матрица `C` передаются в каждый поток.
1. **Разделение по строкам:** Матрица `C` разделяется на блоки по строкам. Каждый поток отвечает за вычисление элементов в своем блоке строк. Количество строк на поток рассчитывается как `size / numThreads`, где `size` - размер матрицы, а `numThreads` - количество потоков.
2. **Создание потоков:** Создается массив потоков `threads`. Каждый поток выполняет функцию `multiplyPart`, которая вычисляет значения элементов в заданном диапазоне строк.
3. **Запуск и синхронизация:** Все потоки запускаются с помощью `t.Start()`. Затем основной поток ожидает завершения всех потоков с помощью `t.Join()`. Это гарантирует, что все вычисления будут завершены до возвращения результата.
4. **Возврат результата:** После завершения всех потоков функция `multiplyParallel` возвращает результирующую матрицу `C`.
---
**Результаты бенчмарков:**
Были проведены тесты для матриц размером 100x100, 300x300 и 500x500 элементов с разным количеством процессов (2, 4, 8, 16, 32, 64, 128, 256, 512). Результаты представлены в таблице:
| Размер | Кол-во потоков | Алгоритм | Время (мс) |
|---|---|---|---|
| 100x100 | 1 | Последовательный | 11 |
| 100x100 | 2 | Параллельный | 7 |
| 100x100 | 4 | Параллельный | 6 |
| 100x100 | 8 | Параллельный | 5 |
| 100x100 | 16 | Параллельный | 6 |
| 100x100 | 32 | Параллельный | 6 |
| 100x100 | 64 | Параллельный | 14 |
| 100x100 | 128 | Параллельный | 27 |
| 100x100 | 256 | Параллельный | 44 |
| 100x100 | 512 | Параллельный | 69 |
| 300x300 | 1 | Последовательный | 393 |
| 300x300 | 2 | Параллельный | 177 |
| 300x300 | 4 | Параллельный | 197 |
| 300x300 | 8 | Параллельный | 155 |
| 300x300 | 16 | Параллельный | 148 |
| 300x300 | 32 | Параллельный | 139 |
| 300x300 | 64 | Параллельный | 153 |
| 300x300 | 128 | Параллельный | 154 |
| 300x300 | 256 | Параллельный | 167 |
| 300x300 | 512 | Параллельный | 358 |
| 500x500 | 1 | Последовательный | 1616 |
| 500x500 | 2 | Параллельный | 831 |
| 500x500 | 4 | Параллельный | 598 |
| 500x500 | 8 | Параллельный | 522 |
| 500x500 | 16 | Параллельный | 542 |
| 500x500 | 32 | Параллельный | 578 |
| 500x500 | 64 | Параллельный | 642 |
| 500x500 | 128 | Параллельный | 812 |
| 500x500 | 256 | Параллельный | 1038 |
| 500x500 | 512 | Параллельный | 1496 |
---
**Анализ результатов:**
* **Влияние размера матрицы:** Время выполнения обоих алгоритмов ожидаемо растет с увеличением размера матрицы, подтверждая кубическую сложность O(n³).
* **Влияние числа потоков:** Параллельный алгоритм демонстрирует ускорение по сравнению с последовательным. Наиболее заметное ускорение наблюдается при умеренном количестве потоков. Для матрицы 100x100 оптимальное количество потоков — 8, для 300x300 — 32, а для 500x500 — 8.
* **Ухудшение производительности при большом числе потоков:** При слишком большом количестве потоков (64, 128, 256, 512) производительность начинает ухудшаться из-за накладных расходов на создание, управление и синхронизацию потоков. Эти расходы перевешивают выгоду от распараллеливания, особенно на относительно небольших матрицах.
* **Оптимальное число потоков:** Оптимальное количество потоков нелинейно зависит от размера матрицы и, вероятно, ограничено количеством логических ядер процессора. В данном случае, для матриц большего размера оптимальное число потоков меньше максимального протестированного, что указывает на наличие узкого места, связанного с управлением потоками.
**Выводы:**
Параллельное умножение матриц может значительно ускорить вычисления, но требует настройки количества потоков. Слишком большое количество потоков приводит к снижению производительности. Оптимальное количество потоков близко к количеству логических ядер процессора (8), но может варьироваться в зависимости от размера матрицы и других факторов. Результаты подчеркивают важность баланса между распараллеливанием вычислений и накладными расходами на управление потоками.
---
#### Демонстрация
Видео доступно по [ссылке](https://disk.yandex.ru/i/aG-7_gTgRwqf5A)