polevoy_sergey_lab_5 #215

Open
ChipsEater wants to merge 1 commits from polevoy_sergey_lab_5 into main
4 changed files with 123 additions and 0 deletions
Showing only changes of commit c406c5bfb5 - Show all commits

View File

@ -0,0 +1,11 @@
[package]
name = "matrix"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
opt-level = 3
panic = "abort"
[dependencies]

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,12 @@
# Лабораторная работа №5
## Полевой Сергей ПИбд-42
### Реализация
Для выполнения работы было решено разбивать матрицы на диапазоны строк и столбцов, которые будут передаваться в потоки, которые в свою очередь результаты для этих диапазонов будут возвращать обратно в виде сообщений в канале mpsc для обеспечения безопасности доступа к памяти
### Скриншот работы
![alt text](image.png)
### Выводы
Исходя из полученых результатов можно сделать следующие выводы: использование потоков рационально только в случае больших матриц, так как для малых больше времени уходит на создание и управление потоками
### Демонстрация работы доступна по [ссылке](https://disk.yandex.ru/i/hRymVDcqrKDsoQ)

View File

@ -0,0 +1,100 @@
use std::thread;
use std::sync::mpsc;
use std::time;
// Квадратная матрица хранится в куче как последовательность всех её элементов, при этом на этапе компиляции всегда известен её размер
struct Matrix<const N: usize> {
inner: Vec<[i32; N]>
}
impl<const N: usize> Matrix<N> {
fn new() -> Matrix<N> {
Matrix { inner: vec![[0; N]; N] }
}
// Псевдослучайная генерация данных для матрицы
fn random(seed: i32) -> Matrix<N> {
let mut matrix = Matrix::new();
for i in 0..N {
for j in 0..N {
matrix.inner[i][j] = i as i32 + j as i32 - seed;
}
}
matrix
}
}
// Перемножение матриц с помощью произвольного количества потоков, размер матриц и количество потоков известно на этапе компиляции
fn multiply<const N: usize, const M: usize>(f: &Matrix<N>, s: &Matrix<N>) -> Matrix<N> {
let mut result = Matrix::<N>::new();
let part_size = N / M;
let last_part_size = N - (N / M) * (M - 1);
let (sender, receiver) = mpsc::channel();
// Конструкция scope гарантирует, что созданные внутри потоки завершат работу там же и не буду существовать дольше, чем сами данные
thread::scope(move |scope| {
for n_part in 1..M {
let sender = sender.clone();
scope.spawn(move || {
for row in (if n_part != 0 {n_part - 1} else {n_part}) * part_size..n_part * part_size {
let mut data = Box::new([0; N]);
for col in 0..N {
data[col] = f.inner[row].iter().zip(s.inner.iter().map(|row| row[col])).map(|(v1, v2)| v1 * v2).sum()
}
sender.send((row, data)).unwrap();
}
});
}
// В случае одного потока или если осталась часть матрицы, которую не удалось передать в поток
if last_part_size != 0 {
for row in N-last_part_size..N {
let mut data = Box::new([0; N]);
for col in 0..N {
data[col] = f.inner[row].iter().zip(s.inner.iter().map(|row| row[col])).map(|(v1, v2)| v1 * v2).sum()
}
sender.send((row, data)).unwrap();
}
}
});
// Потоки отдают результаты в виде сообщений в mpsc канале, при этом передаётся указатель на данные, а не сами данные
while let Ok((row, data)) = receiver.recv() {
for col in 0..N {
result.inner[row][col] = data[col];
}
}
result
}
// Замер времени выполнения, при этом размеры матриц и количество потоков известны на этапе компиляции
fn benchmark<const N: usize, const M: usize>() {
let first = Matrix::<N>::random(1);
let second = Matrix::<N>::random(2);
let start_time = time::Instant::now();
multiply::<N, M>(&first, &second);
println!("{0:>4}x{0:<4} ({1:>2} потоков): {2} сек", N, M, (time::Instant::now() - start_time).as_secs_f64())
}
fn main() {
benchmark::<100, 1>();
benchmark::<300, 1>();
benchmark::<500, 1>();
benchmark::<1000, 1>();
benchmark::<1200, 1>();
benchmark::<100, 4>();
benchmark::<300, 4>();
benchmark::<500, 4>();
benchmark::<1000, 4>();
benchmark::<1200, 4>();
benchmark::<100, 8>();
benchmark::<300, 8>();
benchmark::<500, 8>();
benchmark::<1000, 8>();
benchmark::<1200, 8>();
}