Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ jobs:
run: ./scripts/run_clang_tidy.sh

- name: Test
run: ./scripts/test.sh
run: ./scripts/test.sh
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ dkms.conf
__pycache__
build
pics/
*_cat.bmp
*_monkey.bmp
images/*_cat.bmp
images/*_monkey.bmp
output_queue_mode/
92 changes: 92 additions & 0 deletions Analysis_of_queue_benchmark_results.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Анализ производительности режима очередей (`queue mode`)

## Введение

В рамках данного анализа исследуется эффективность реализации параллельной обработки изображений в архитектуре очередей (`--mode=queue`). В этой модели обработка изображений разбита между тремя типами потоков, которые соединяются очередьми с ограничениями по размеру добавляемого изображения, образуя тем самым конвейер обработки:
- `readers` - отвечают за чтение исходных данных;
- `workers` - выполняют применение фильтра `bl`;
- `writers` - записывают результаты обработки.

**Конвейер:**
```
[Reader(s)] → [Input Queue] → [Worker(s)] → [Output Queue] → [Writer(s)]
```
`Reader` читает изображение и помещает его во входную очередь (`Input Queue`) → `Worker` извлекает изображения из `Input Queue`, выполняет свертку, помещает результат в выходную очередь (`Output Queue`) → `Writer` извлекает результат из `Output Queue` и записывает в файл.

Тестирование проводилось на наборе из [9 изображений](input_queue_mode/), полученных применением различных фильтров к [`cat.bmp`](images/cat.bmp). Таким образом, все изображения имеют одинаковый размер - `5.9 MiB`, что позволяет минимизировать влияние объёма данных на результаты тестирования. Однако их содержимое различно, поэтому каждое изображение необходимо отдельно загружать в память и записывать результат обратно, в отличие от случая с полностью идентичными изображениями, когда можно было бы загрузить файл один раз и использовать его повторно.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну хоть бы директорию на полгига прогнать, а то масштабы какие-то детские


Также стоит отметить, что расположение мьютексов при работе с условными переменными в конце функциях [`queue_push`](src/queue_mode/queue.c#L111) и [`queue_pop`](src/queue_mode/queue.c#L145) выбрано не случайно. Именно такая организация блокировок позволяет достичь минимального времени выполнения программы. Ключевым моментом является то, что в функции `queue_pop` [`pop_mutex`](src/queue_mode/queue.c#L149) освобождается только после освобождения [`push_mutex`](src/queue_mode/queue.c#L147) при отправке сигнала о появлении свободного места в очереди. Это гарантирует, что поток `worker` сможет раньше начать претендовать на доступ к новым данным из очереди. Если бы `pop_mutex` освобождался раньше, до попытки захвата [`queue_push`](src/queue_mode/queue.c#L145), `worker` оказывался бы в состоянии ожидания освобождения `push_mutex` (которое чаще всего долгое).

Эксперимент охватывал следующие параметры:
1) Конфигурации распределения потоков между ролями: (`readers`, `workers`, `writers`);
2) Лимиты памяти :
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А почему было принято решение ограничивать память, а не число изображений в очереди? Так мы можем не угадать и где-нибудь в середине мы неожиданно пропустим какие-то изображения

- `6 MiB` - одновременно в очереди может находиться только одно изображение;
- `30 MiB` - до пяти изображений могут быть в очереди;
- `55 MiB` - очередь позволяет хранить все изображения.
3) Число потоков для применения фильтра `bl`: `--thread=2`, `--thread=3`, `--thread=4`, `--thread=5`.

**Количество прогонов для каждого теста:** 40 (обеспечивает статистическую достоверность результатов).

**Код бенчмарка**: [`tests/benchmarks/comparison_of_queue_mode.py`](tests/benchmarks/comparison_of_queue_mode.py)

## Конфигурация системы:

- **Процессор:** Процессор: Intel Core i7-11370H (4 физических ядра, 8 логических потоков) с фиксированной частотой 3.3 GHz (`sudo cpupower frequency-set --min 3300MHz --max 3300MHz`);
- **Кэш L1:** 80 КБ на ядро (48 КБ – кэш данных, 32 КБ – кэш инструкций);
- **Кэш L2:** 1.25 МБ на ядро;
- **Общий кэш L3:** 12 МБ.

- **ОЗУ:** 16ГБ

- **ОС:** Linux Kubuntu 24.04

- **Настройки производительности:** Использовался режим `performance` (`sudo cpupower frequency-set -g performance`) для минимизации влияния динамического изменения частоты процессора.

Для обеспечения чистоты эксперимента система перезагружалась после каждого тестирования, а сторонние процессы были минимизированы.

## Результаты тестирования

В ходе тестирования измерялось:
- Общее время выполнения программы;
- Время работы каждого этапа - чтение, обработка и запись изображения.

Графики и соответствующие им результаты находятся в папках [`tests/plots/queue_mode_<N>`](tests/plots/), где N обозначает число потоков для применения фильтра.

## Анализ результатов

### Влияние количества потоков (`--thread`) и `mem_lim`
- **`--thread=2`**:
- Наилучшая конфигурация: **1 Reader, 3 Workers, 2 Writers** (`mem_lim=6`, Среднее время - **1.78 сек.**).
- Увеличение `mem_limit` до 55 MiB ухудшает время.
- Увеличение общего числа создаваемых потоков (`readers` + `workers` + `writers` + `threads`) сверх 8 не приводит к улучшению производительности, поскольку процессор поддерживает максимум 8 логических потоков. При превышении этого числа возникает конкуренция за вычислительные ресурсы, так как планировщик операционной системы вынужден выполнять частые переключения контекста между потоками. Это увеличивает накладные расходы и ухудшает производительность вместо её повышения. Поэтому в дальнейших тестах суммарное количество потоков намеренно ограничивалось значением 8.

- **`--thread=3`**:
- Оптимальная производительность: **1 Reader, 3 Workers, 1 Writer** (`mem_lim=6`, Среднее время - **1.735 сек.**).
- При увеличении `mem_lim` время также увеличивается.

- **`--thread=4`**:
- Лучший результат: **1 Reader, 2 Workers, 1 Writer** (`mem_lim=6`, Среднее время - **1.726 сек.**).
- При увеличении `mem_lim` время также увеличивается.

- **`--thread=5`**:
- Минимальное среднее время: **1.74 сек.** (конфигурация `1-1-1`, `mem_lim=6`).

**Объяснение:**

**1.** Результаты показывают, что обработка изображения является узким местом всего pipeline'а. По этой причине увеличение числа потоков, ответственных за применение фильтра, приводит к снижению времени выполнения этого этапа, что в свою очередь положительно влияет на общее время выполнения программы.

**2.** Во всех рассмотренных случаях наилучшие результаты достигаются при минимальном лимите памяти (**6 MiB**). Увеличение лимита до **30** и **55 MiB** не улучшает, а в большинстве случаев даже его ухудшает. Это объясняется особенностями работы синхронизации между потоками `readers` и `workers` в контексте входной очереди (`Input queue`). При малом лимите памяти (**6 MiB**), очередь позволяет хранить только одно изображение, поэтому читатель вынужден ожидать освобождения места (пока `worker` заберёт это изображение). Это создаёт небольшую задержку для `reader'а`, но позволяет `worker` немедленно приступить к обработке, что особенно важно, поскольку этап обработки является самым медленным звеном pipeline. Напротив, при увеличении лимита памяти, `reader` может загрузить сразу несколько изображений в очередь без ожидания, что повышает вероятность конкуренции за мьютексы (`push_mutex` и `pop_mutex`) между `readers` и `workers`.
В результате:
- Рабочие тратят больше времени на ожидание блокировок;
- Начало обработки первого изображения откладывается;
- Общее время выполнения программы возрастает.

Таким образом, ограничение памяти положительно влияет на производительность, так как способствует более раннему началу самого длительного этапа - обработки изображения, минимизируя простои и конкуренцию за общие ресурсы.

## Вывод

Минимальное время выполнения программы зафиксировано при конфигурации `--thread=4`, `1 Reader`, `2 Workers`, `1 Writer`, `mem_lim=6` - среднее время: **1.726 сек.**

Этот результат демонстрирует, что для достижения максимальной производительности необходимо:
- Ограничивать размер очереди, чтобы он соответствовал размеру одного изображения.
- Уменьшить время обработки изображений - найти баланс между ускорением выполнения сверстки (`--thread=4`) и количеством одновременно работающих `worker'ов`.
6 changes: 1 addition & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")

add_subdirectory(src)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules")

find_package(CMocka CONFIG)
if (CMocka_FOUND)
include(AddCMockaTest)
include(AddMockedTest)
add_subdirectory(tests)
enable_testing()
add_subdirectory(tests)
endif(CMocka_FOUND)
31 changes: 23 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,29 @@

## Description

Image-convolution is an image processing application that applies various convolution filters to images. It supports both sequential and parallel execution modes with different workload distribution strategies. The application is designed for benchmarking and comparing different parallelization approaches for image processing algorithms.
Image-convolution is an image processing tool that applies convolution filters in sequential, parallel (with different workload distribution strategies), and queue-based pipeline modes. In queue mode, readers, workers, and writers operate concurrently in a producer-consumer model with memory-limited queues. The app is designed for benchmarking different parallelization strategies and thread scalability.

## Usage
Basic Command:
```bash
./build/src/image-convolution <image_path> <filter_name> --mode=<mode> [--thread=<num>]
```
### Options
| Parameter | Description | Required |
|--------------------|---------------------------------------------------------------------|----------|
| `<image_path>` | Path to input image or `--default-image` (predefined default image) | Yes |
| `<filter_name>` | Filter to apply (see [Available Filters](#available-filters)) | Yes |
| `--mode=<mode>` | Execution mode: `seq`, `pixel`, `row`, `column`, `block` | Yes |
| `--thread=<num>` | Number of threads (for parallel modes) | No* |
| Parameter | Description |
|--------------------|-----------------------------------------------------------------------------|
| `<image_path>` | Path to input image or `--default-image` (predefined default image) |
| `<filter_name>` | Filter to apply (see [Available Filters](#available-filters)) |
| `--mode=<mode>` | Execution mode: `seq`, `pixel`, `row`, `column`, `block` or `queue` |
| `--thread=<num>` | Number of threads to use for parallel convolution (ignored if `--mode=seq`) |

*Required for all modes except seq
#### Queue options
| Parameter | Description |
|--------------------|---------------------------------------------------------------------|
| `--num=<images>` | Number of images to process |
| `--readers=<num>` | Number of reader threads |
| `--workers=<num>` | Number of worker threads |
| `--writers=<num>` | Number of writer threads |
| `--mem_lim=<MiB>` | Memory limit for queues in MiB (e.g. 10) |

### Available Filters
| Name | Description | Kernel Size |
Expand All @@ -47,6 +54,10 @@ Basic Command:
```bash
./build/src/image-convolution images/cat.bmp gbl --mode=block --thread=4
```
3) Queue-Based pipeline processing:
```bash
./build/src/image-convolution images mbl --mode=queue --thread=2 --num=25 --readers=2 --workers=3 --writers=2 --mem_lim=15
```

## Build
To build the project:
Expand All @@ -68,6 +79,10 @@ The project includes three types of tests:
```bash
./scripts/perf.sh <image_name>
```
4) Queue mode performance analysis - evaluate execution time distribution across stages (reader, worker, writer) for various thread configurations under a memory limit:
```bash
./scripts/queue_benchmark.sh <num_of_imgs> <mem_lim>
```

## Prerequisites for Benchmarks (`Performance benchmarks` and `Cache performance analysis`)
Before running benchmarks, you need to set up a Python virtual environment and install dependencies:
Expand Down
Loading