Files
SSPR_25/lyakhov_timofey_lab_1
2025-03-24 10:14:11 +04:00
..
2025-03-02 16:08:33 +04:00
2025-03-02 16:08:33 +04:00
2025-03-24 10:14:11 +04:00

Лабораторная работа №1

Описание

Программа вычисляет сумму произведений строк квадратной матрицы с использованием трех подходов:

  • Однопоточная реализация
  • Многопоточная реализация с использованием ThreadPool
  • Реализация с использованием ForkJoinPool

Цель — сравнить эффективность различных способов многопоточной обработки данных и замерить время выполнения для каждого подхода.

Как запустить лабораторную работу

  1. Соберите проект и получите .jar файл:

    mvn package
    

    Либо перейдите сразу к шагу 2, если .jar уже собран.

  2. Соберите Docker-образ:

    docker build -t app -f .dockerfile .
    
  3. Запустите контейнер:

    docker run -i app
    

Используемые технологии

  • Java
  • Maven — для сборки проекта
  • Docker — для упаковки и запуска приложения в контейнере
  • Параллельное программирование:
    • ExecutorService (ThreadPool)
    • ForkJoinPool (пул задач с рекурсивным делением данных)

Что делает программа

  • Заполняет матрицу случайными числами от 1 до 100.
  • Вычисляет сумму произведений элементов в каждой строке матрицы тремя способами:
    1. Однопоточная реализация — классический линейный перебор строк.
    2. ThreadPool — распределение строк по пулу потоков.
    3. ForkJoinPool — разбиение задачи на подзадачи с использованием RecursiveTask.
  • Для каждого способа выводит время выполнения в наносекундах.

    public static void calculateSumOfRow() { // Фнукция подсчета в одном потоке
        LocalDateTime startTime = LocalDateTime.now();
        long s = System.nanoTime();
        int sum = 0;
        for (int[] ints : matrix) {
            int product = 1;
            for (int anInt : ints) {
                product *= anInt;
            }
            sum += product;
        }

        long st = System.nanoTime() - s;
        System.out.println("Время выполнения в одном потоке: \t" + st);

    }

    public static void calculateSumOfRowWithThreadPool() { // Фнукция подсчета с помощью  ThreadPool
        int sum = 0;
        int numThreads = Runtime.getRuntime().availableProcessors(); // Определение количества потоков на основании процессора
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        List<Future<Integer>> futures = new ArrayList<>();
        long s = System.nanoTime();
        for (int i = 0; i < matrix.length; i++) { // Создание задач выполнения
            final int row = i;
            futures.add(executor.submit(() -> {
                int product = 1;
                for (int j = 0; j < matrix[row].length; j++) {
                    product *= matrix[row][j];
                }
                return product;
            }));
        }

        for (Future<Integer> future : futures) { // Подсчет всех значений
            try {
                sum += future.get();
            } catch (InterruptedException | ExecutionException e) {
                System.out.println(e.getMessage());
            }
        }

        executor.shutdown();

        long st = System.nanoTime() - s;
        System.out.println("Время выполнения с ThreadPool: \t\t" + st);
    }

    public static void calculateSumOfRowWithForkPool(){ // ПОдсчет через ForkPool

        PartTask pt = new PartTask(matrix, 0, size + 1); // Создание сущности парта
        ForkJoinPool forkJoinPool = new ForkJoinPool(); 
        long s = System.nanoTime();
        forkJoinPool.invoke(pt);
        long st = System.nanoTime() - s;
        System.out.println("Время выполнения с ForkJoinPool: \t" + st);
    }

}
class PartTask extends RecursiveTask<Integer> { // Класс задачи

    private final int[][] matrix;
    private final int start, stop;

    public PartTask(int[][] matrix, int start, int stop) {
        this.matrix = matrix;
        this.start = start;
        this.stop = stop;
    }

    @Override
    protected Integer compute() { // Вычисление части парта(Использование рекурсий)
        if (start + 1 == stop) {
            return Arrays.stream(matrix[start]).sum();
        }
        PartTask p1 = new PartTask(matrix, start, (start + stop) / 2);
        PartTask p2 = new PartTask(matrix, (start + stop) / 2, stop);
        p1.fork();
        p2.fork();
        return p1.join() + p2.join();
    }
}

Особенности кода

  • Используется замер времени выполнения с помощью System.nanoTime().
  • ThreadPool автоматически подстраивается под количество доступных ядер CPU.
  • Реализация с ForkJoinPool демонстрирует работу паттерна divide-and-conquer (разделяй и властвуй).
  • Для ForkJoinPool реализован собственный класс PartTask, унаследованный от RecursiveTask<Integer>.

Тесты (Примеры ввода и вывода)

Тест 1:

Ввод:

Введите размер матрицы (от 2 до 1000): 10

Вывод:

Время выполнения в одном потоке:        2800
Время выполнения с ThreadPool:          1793455
Время выполнения с ForkJoinPool:        599719

Тест 2:

Ввод:

Введите размер матрицы (от 2 до 1000): 100

Вывод:

Время выполнения в одном потоке:        66801
Время выполнения с ThreadPool:          5646255
Время выполнения с ForkJoinPool:        568506

Тест 3:

Ввод:

Введите размер матрицы (от 2 до 1000): 1000

Вывод:

Время выполнения в одном потоке:        2352923
Время выполнения с ThreadPool:          14560843
Время выполнения с ForkJoinPool:        917609

Вывод

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

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