Skip to content
Closed
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
269 changes: 269 additions & 0 deletions tasks/chernov_t_radix_sort/all/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
# Поразрядная сортировка для целых чисел с простым слиянием — ALL

- Student: Чернов Тимур Владимирович
- Technology: ALL (MPI + OMP)
- Variant: 17

## 1. Контекст

Гибридная версия (ALL) сочетает межпроцессный параллелизм (MPI)
и внутрипроцессный (OpenMP) для ускорения вычислений.

**Цель:** оценить эффективность двухуровневой декомпозиции
и понять, когда гибрид оправдан по сравнению с OMP/TBB/STL.

## 2. Постановка задачи

(Идентична последовательной версии — см. [seq/report.md](seq/report.md))
**Вход:** `std::vector<int>` произвольной длины.
**Выход:** Отсортированный по неубыванию вектор.
**Ограничения:** Корректная обработка 32-битных знаковых целых, линейная сложность.

## 3. Базовый алгоритм

Поразрядная сортировка LSD с основанием 256:

1. Преобразование знака через `x ^ 0x80000000`.
2. 4 прохода по байтам: гистограмма → префиксные суммы → стабильное рассеивание.
3. Обратное преобразование знака.
(Подробности — в [seq/report.md](seq/report.md))

## 4. Межпроцессная схема (уровень MPI)

**Роли рангов:**

- `rank == 0` (мастер): вычисляет чанки, рассылает данные,
собирает/сливает результаты, рассылает финальный ответ.
- `rank > 0` (воркеры): получают чанк, сортируют локально, отправляют результат.

**Ключевые MPI-вызовы и их назначение:**

| Вызов | Назначение | Почему выбран |
|------------------------------------|-----------------------------------------------|--------------------------------------------------------------|
| `MPI_Comm_rank` / `MPI_Comm_size` | Получение ранга и числа процессов | Базовая инициализация для ветвления логики |
| `MPI_Scatter` (для `recv_counts`) | Рассылка размеров чанков | Каждый процесс знает свой размер данных |
| `MPI_Scatterv` | Рассылка данных переменного размера | Чанки могут отличаться на 1 элемент |
| `MPI_Bcast` (размер + данные) | Рассылка финального результата | Требуется для валидации в тестовом фреймворке |
| `MPI_Gatherv` | Сбор отсортированных чанков на `rank 0` | Для последующего слияния нужен полный массив на мастере |

**Формула разбиения данных (на `rank 0`):**

```cpp
// File: all/src/ops_all.cpp
int base = static_cast<int>(total_elements / static_cast<size_t>(num_processes));
int remainder = static_cast<int>(total_elements % static_cast<size_t>(num_processes));
for (int i = 0; i < num_processes; ++i) {
recv_counts[i] = base + (i < remainder ? 1 : 0); // Первые 'remainder' процессов получают на 1 элемент больше
displs[i] = current_disp;
current_disp += recv_counts[i];
}
```

**Синхронизация на уровне MPI:**

- Явные `MPI_Barrier` не используются — коллективные операции
(`Scatterv`/`Gatherv`/`Bcast`) содержат **неявные барьеры**.
- Последовательность вызовов гарантирует порядок:
`MPI_Gatherv` начнётся после завершения сортировки всеми.

## 5. Внутрипроцессная схема (уровень OpenMP)

**Выбранная технология:** OpenMP (`#pragma omp parallel for`)
для параллельной обработки элементов внутри чанка процесса.

**Почему OpenMP, а не std::thread или TBB внутри процесса:**

- Инфраструктура курса экспортирует `PPC_NUM_THREADS`
как `OMP_NUM_THREADS` — единообразие управления потоками.
- Директивы OpenMP позволяют распараллелить регулярные циклы с минимальными изменениями кода.
- `default(none)` требует явных атрибутов переменных —
повышает безопасность и соответствует `clang-tidy`.

**Ключевой фрагмент (параллельный подсчёт гистограммы):**

```cpp
// File: all/src/ops_all.cpp
#pragma omp parallel for schedule(static) default(none) shared(temp, local_counts, n, shift)
for (size_t i = 0; i < n; ++i) {
int thread_idx = omp_get_thread_num();
int digit = static_cast<int>((temp[i] >> shift) & 0xFFU);
local_counts[static_cast<size_t>(thread_idx)][static_cast<size_t>(digit)]++;
}
```

### Расшифровка директивы OpenMP

| Часть директивы | Значение | Почему выбрано |
|----------------------------------------|--------------------------------------------|---------------------------------------------------------------|
| `parallel for` | Параллельное выполнение итераций | Регулярный доступ, независимые итерации |
| `schedule(static)` | Статическое разбиение на чанки | Минимальный оверхед для равномерной нагрузки |
| `default(none)` | Запрет неявных атрибутов | Безопасность + `clang-tidy`: явные `shared`/`private` |
| `shared(temp, local_counts, n, shift)` | Переменные между потоками | Чтение `temp`, запись в свои строки |

### Атрибуты переменных

- **`shared`:** `temp`, `local_counts`, `n`, `shift` — данные, общие для всех потоков параллельной области.
- **`private`:** `i`, `thread_idx`, `digit` — автоматически приватные, так как объявлены внутри цикла.

### Избегание гонок данных

```cpp
// Каждый поток пишет только в свою строку:
local_counts[static_cast<size_t>(thread_idx)][static_cast<size_t>(digit)]++;
```

### Объединение локальных гистограмм

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

```cpp
std::vector<int> global_count(kRadix, 0);
for (int thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
for (int digit_idx = 0; digit_idx < kRadix; ++digit_idx) {
global_count[digit_idx] += local_counts[static_cast<size_t>(thread_idx)][static_cast<size_t>(digit_idx)];
}
}
```

## 6. Детали реализации

**Файлы:** `all/include/ops_all.hpp`, `all/src/ops_all.cpp`

### Архитектура класса

Класс `ChernovTRadixSortALL` наследует `BaseTask` и реализует стандартный конвейер:

| Метод | Назначение |
|-------------------------|--------------------------------------------------|
| `ValidationImpl()` | Всегда возвращает `true` |
| `PreProcessingImpl()` | Копирует входной вектор в выходной |
| `RunImpl()` | Основная MPI + OpenMP логика |
| `PostProcessingImpl()` | Проверяет `std::is_sorted()` |

### Вспомогательные методы

| Метод | Назначение |
|-----------------------------|----------------------------------------------------|
| `RadixSortLSDParallelOMP()` | Поразрядная сортировка с OpenMP |
| `SimpleMerge()` | Слияние двух массивов (`std::ranges::merge`) |
| `ComputeChunkSizes()` | Вычисление размеров чанков для MPI |
| `MergeChunksOnRank0()` | Последовательное слияние всех чанков |

### Расположение MPI-вызовов

```cpp
// Рассылка размеров чанков
MPI_Scatter(recv_counts.data(), 1, MPI_INT, &local_n, 1, MPI_INT, 0, MPI_COMM_WORLD);

// Рассылка данных
MPI_Scatterv(input_data.data(), recv_counts.data(), displs.data(), MPI_INT,
local_data.data(), local_n, MPI_INT, 0, MPI_COMM_WORLD);

// Сбор результатов
MPI_Gatherv(local_data.data(), local_n, MPI_INT, global_result.data(),
recv_counts.data(), displs.data(), MPI_INT, 0, MPI_COMM_WORLD);

// Рассылка финального результата
MPI_Bcast(&out_size, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Bcast(GetOutput().data(), out_size, MPI_INT, 0, MPI_COMM_WORLD);
```

### Потенциальные узкие места

| Узкое место | Причина |
|-----------------------------------------|------------------------------------------------------------|
| Финальное слияние на rank 0 | Выполняется последовательно, ограничивает масштабируемость |
| `MPI_Gatherv` | Все данные передаются на один процесс |
| Двойной оверхед | MPI-коммуникации + OpenMP-потоки |
| Последовательное объединение гистограмм | Выполняется главным потоком |

## 7. Проверка корректности

**Методы валидации:**

- `PostProcessingImpl()` возвращает `std::is_sorted()`
- Сравнение с `std::sort()` в тестовом фреймворке
- `MPI_Bcast` гарантирует одинаковый результат на всех процессах

**Функциональные тесты (8 наборов):**

| Тест | Входные данные | Описание |
|---------------------|-----------------------|--------------------------|
| `NoElements` | `{}` | Пустой массив |
| `JustOneItem` | `{42}` | Один элемент |
| `AscendingOrder` | `{1, 2, 3, 4, 5}` | Уже отсортированный |
| `OnlyNegatives` | `{-10, -50, -1}` | Только отрицательные |
| `PosAndNegMixed` | `{-10, 50, -1, 0}` | Смешанные знаки |
| `AllZeroes` | `{0, 0, 0}` | Все одинаковые |
| `PowersOfTwo` | `{1024, 256, 512}` | Степени двойки |
| `BigNums` | `{3243423, -1221313}` | Большие числа |

**Результат:** Все тесты пройдены при конфигурациях `1×1`, `2×2`, `4×2`. Расхождений с SEQ нет.

## 8. Экспериментальная среда

**Оборудование:**

- **CPU:** AMD Ryzen 5 5500U (6 ядер, 12 потоков)
- **RAM:** 8 ГБ DDR4
- **OS:** Windows 11 / WSL2 Ubuntu 22.04
- **Компилятор:** MSVC 19.50.35723
- **Тип сборки:** `Release`

**Переменные окружения:**

```bash
export PPC_NUM_PROC=4 # Число MPI-процессов
export PPC_NUM_THREADS=2 # Число OpenMP-потоков на процесс
```

**Команды запуска:**

```bash
# Сборка
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
cmake --build build --parallel

# Функциональные тесты
export PPC_NUM_PROC=2
export PPC_NUM_THREADS=2
./build/bin/ppc_func_tests --gtest_filter="*chernov*all*"

# Тесты производительности
./build/bin/ppc_perf_tests --gtest_filter="*chernov*all*"
```

**Размеры задач:**

- Функциональные: `0–11` элементов
- Производительность: `20 000 000` элементов

## 9. Результаты

**Базовое время SEQ:** `0.450 с`

| Ranks | Threads/rank | Total workers | Время (с) | Ускорение | Эффективность |
|-------|--------------|---------------|-----------|-----------|---------------|
| 1 | 1 | 1 | 0.450 | 1.00× | 100% |
| 1 | 2 | 2 | 0.240 | 1.88× | 94.0% |
| 2 | 1 | 2 | 0.235 | 1.91× | 95.5% |
| 2 | 2 | 4 | 0.128 | 3.52× | 88.0% |
| 2 | 4 | 8 | 0.102 | 4.41× | 55.1% |
| 4 | 2 | 8 | 0.098 | 4.59× | 57.4% |

## 10. Выводы

**Когда гибридная схема оправдана:**

- Умеренное число MPI-процессов (2–4) и потоков (2–4)
- Данные не помещаются в память одного узла
- Задача естественно делится на независимые блоки

**Когда НЕ оправдана:**

- Малый размер задачи (< 1 млн элементов) — оверхед превышает выигрыш
- Большое число MPI-процессов (> 4) — падение эффективности ниже 50%
- На одном узле чистая OpenMP даёт близкий результат при меньшей сложности

**Оптимальная конфигурация:** `2×2` (3.52×, 88%)
или `2×4` (4.41×) для данной задачи на 6-ядерном процессоре.
Loading
Loading