diff --git a/minhasapov_ruslan_lab_5/.gitignore b/minhasapov_ruslan_lab_5/.gitignore new file mode 100644 index 0000000..db2214b --- /dev/null +++ b/minhasapov_ruslan_lab_5/.gitignore @@ -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 \ No newline at end of file diff --git a/minhasapov_ruslan_lab_5/MatrixMultiplication/Program.fs b/minhasapov_ruslan_lab_5/MatrixMultiplication/Program.fs new file mode 100644 index 0000000..0c19d52 --- /dev/null +++ b/minhasapov_ruslan_lab_5/MatrixMultiplication/Program.fs @@ -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 + +[] +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 \ No newline at end of file diff --git a/minhasapov_ruslan_lab_5/README.md b/minhasapov_ruslan_lab_5/README.md new file mode 100644 index 0000000..bf30241 --- /dev/null +++ b/minhasapov_ruslan_lab_5/README.md @@ -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) \ No newline at end of file