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
31 changes: 31 additions & 0 deletions tasks/titaev_m_sortirovka_betchera/all/include/ops_all.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <vector>

#include "task/include/task.hpp"
#include "titaev_m_sortirovka_betchera/common/include/common.hpp"

namespace titaev_m_sortirovka_betchera {

class TitaevSortirovkaBetcheraALL : public BaseTask {
public:
static constexpr ppc::task::TypeOfTask GetStaticTypeOfTask() {
return ppc::task::TypeOfTask::kALL;
}
explicit TitaevSortirovkaBetcheraALL(const InType &in);

private:
bool ValidationImpl() override;
bool PreProcessingImpl() override;
bool RunImpl() override;
bool PostProcessingImpl() override;

static void ConvertToKeys(const InType &input, std::vector<uint64_t> &keys);
static void RadixSort(std::vector<uint64_t> &keys);
static void ConvertFromKeys(const std::vector<uint64_t> &keys, OutType &output);
void BatcherSort();
static void BatcherStage(OutType &result, std::size_t array_size, std::size_t block, std::size_t step);
};

} // namespace titaev_m_sortirovka_betchera
142 changes: 142 additions & 0 deletions tasks/titaev_m_sortirovka_betchera/all/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Поразрядная сортировка для вещественных чисел с чётно-нечётным слиянием Бэтчера - ALL

- Student: Титаев М., group 3823Б1ФИ1
- Technology: ALL
- Variant: 20

## 1. Introduction

ALL-версия объединяет несколько технологий параллелизма. Цель этой реализации — использовать
MPI-окружение для запуска задачи под несколькими процессами, а внутри каждого процесса дополнительно
применять многопоточность OpenMP на этапах поразрядной сортировки и сети чётно-нечётного слияния Бэтчера.

## 2. Problem Statement

Задача совпадает с остальными версиями: отсортировать массив `InType = std::vector<double>` по
неубыванию, результат имеет тип `OutType = std::vector<double>`.

## 3. Baseline Algorithm (Sequential)

Baseline описан в `seq/report.md`. Он выполняет преобразование `double` в упорядоченные 64-битные ключи,
поразрядную сортировку по основанию 256 и обратное преобразование, после чего при размере, равном степени
двойки, применяет сеть Бэтчера.

## 4. Parallelization Scheme

ALL-реализация использует гибридную схему:

1. **MPI:** задача исполняется под управлением `mpiexec` с заданным числом процессов; общее число
рабочих определяется как `ranks × threads`.
2. **OpenMP:** внутри процесса распараллеливаются преобразование ключей, подсчёт корзин с локальными
гистограммами и проходы сети Бэтчера через `#pragma omp parallel for`.
3. **Согласование результата:** итоговый отсортированный массив формируется на основе общих входных
данных, доступных каждому процессу.

Конфигурация задаётся как `workers = ranks × threads`. Например, при запуске `mpiexec -n 4` и
`PPC_NUM_THREADS=2` конфигурация равна `4 × 2`, а `workers = 8`.

## 5. Implementation Details

- Файлы: `all/include/ops_all.hpp`, `all/src/ops_all.cpp`.
- Класс: `TitaevSortirovkaBetcheraALL`.
- Этапы преобразования и сети Бэтчера выражены через OpenMP-параллелизм.
- Подсчёт корзин использует локальные гистограммы по потокам для исключения гонок.

Память: один временный массив ключей и по одной гистограмме на каждый поток.

Corner cases:

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

## 6. Experimental Setup

**Аппаратное обеспечение:**

- **CPU:** 12th Gen Intel(R) Core(TM) i5-12450H (2.00 GHz, 8 ядер / 12 потоков)
- **RAM:** 16 ГБ
- **OS:** Windows 11 Pro x64
- **MPI:** Microsoft MPI (MS-MPI) 10.1

**Инструменты:**

- **Сборка:** CMake
- **Компилятор:** MSVC 19.x
- **Конфигурация:** Release

**Окружение:**

- **PPC_NUM_THREADS:** задаёт число потоков для OMP, TBB, STL и потоковой части ALL.
- **PPC_NUM_PROC / mpiexec -n:** задаёт число MPI-процессов для ALL.
- Для ALL конфигурация записывается в формате `ranks × threads`.

**Генерация данных:**

- Тесты генерируют входные данные автоматически.
- Для performance-теста используется массив из `2^20` элементов с убывающими значениями.
- Внешние файлы с данными не используются.

## 7. Results and Discussion

### 7.1 Correctness

Корректность проверялась функциональными тестами из `tests/functional/main.cpp`. Выходной массив
проверяется на упорядоченность по неубыванию. Результаты ALL-версии совпадают с baseline на всех тестовых
наборах.

### 7.2 Performance

Используемые обозначения:

```txt
time — время выполнения performance-теста;
speedup = time_seq / time_mode;
efficiency = speedup / workers;
workers — количество исполнителей: потоков для OMP/TBB/STL, ranks × threads для ALL.
```

| Mode | Count | Time, s | Speedup | Efficiency |
| --- | --- | --- | --- | --- |
| seq | 1 | 0.159970 | 1.00 | N/A |
| all | 2 × 1 | 0.079706 | 2.01 | 100.33% |
| all | 2 × 2 | 0.099306 | 1.61 | 40.27% |
| all | 4 × 2 | 0.227350 | 0.70 | 8.79% |

Наилучший результат получен в конфигурации `2 × 1`. С ростом числа процессов и потоков время
увеличивается: добавляются накладные расходы на запуск и координацию MPI-процессов, которые на данной
постановке не окупаются, а многочисленные синхронизированные проходы сети Бэтчера ограничивают выигрыш от
дополнительных потоков.

## 8. Conclusions

ALL-версия даёт ускорение относительно baseline в конфигурации с двумя процессами и одним потоком.
Увеличение числа процессов и потоков снижает производительность из-за накладных расходов на управление
MPI и большого числа синхронизаций в сети Бэтчера.

## 9. References

1. OpenMP Architecture Review Board. OpenMP Application Programming Interface.
2. oneAPI Threading Building Blocks Documentation.
3. Microsoft MPI Documentation.
4. ISO C++ Standard Library Documentation: `std::thread`.

## Appendix (Optional)

```cpp
bool TitaevSortirovkaBetcheraALL::RunImpl() {
auto &input = GetInput();
const size_t n = input.size();
if (n <= 1) {
return true;
}
std::vector<uint64_t> keys(n);
ConvertToKeys(input, keys);
RadixSort(keys);
ConvertFromKeys(keys, GetOutput());
if ((n & (n - 1)) == 0) {
BatcherSort();
}
return true;
}
```
173 changes: 173 additions & 0 deletions tasks/titaev_m_sortirovka_betchera/all/src/ops_all.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#include "titaev_m_sortirovka_betchera/all/include/ops_all.hpp"

#include <omp.h>

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <vector>

#include "titaev_m_sortirovka_betchera/common/include/common.hpp"

namespace titaev_m_sortirovka_betchera {

namespace {

uint64_t DoubleToOrderedUint(double value) {
uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(double));
constexpr uint64_t kSignMask = (1ULL << 63);
if ((bits & kSignMask) != 0ULL) {
bits = ~bits;
} else {
bits ^= kSignMask;
}
return bits;
}

double OrderedUintToDouble(uint64_t bits) {
constexpr uint64_t kSignMask = (1ULL << 63);
if ((bits & kSignMask) != 0ULL) {
bits ^= kSignMask;
} else {
bits = ~bits;
}
double result = 0.0;
std::memcpy(&result, &bits, sizeof(double));
return result;
}

} // namespace

TitaevSortirovkaBetcheraALL::TitaevSortirovkaBetcheraALL(const InType &in) {
SetTypeOfTask(GetStaticTypeOfTask());
GetInput() = in;
GetOutput().clear();
}

bool TitaevSortirovkaBetcheraALL::ValidationImpl() {
return !GetInput().empty();
}

bool TitaevSortirovkaBetcheraALL::PreProcessingImpl() {
GetOutput() = GetInput();
return true;
}

void TitaevSortirovkaBetcheraALL::ConvertToKeys(const InType &input, std::vector<uint64_t> &keys) {
const auto n = static_cast<int64_t>(input.size());
#pragma omp parallel for default(none) shared(input, keys, n)
for (int64_t i = 0; i < n; i++) {
keys[i] = DoubleToOrderedUint(input[i]);
}
}

void TitaevSortirovkaBetcheraALL::RadixSort(std::vector<uint64_t> &keys) {
const std::size_t n = keys.size();
if (n <= 1) {
return;
}

constexpr int kBits = 8;
constexpr int kBuckets = 1 << kBits;
constexpr int kPasses = 64 / kBits;

std::vector<uint64_t> tmp(n);
const auto signed_n = static_cast<int64_t>(n);

for (int pass = 0; pass < kPasses; pass++) {
std::vector<std::size_t> count(kBuckets, 0);
const int num_threads = omp_get_max_threads();
std::vector<std::vector<std::size_t>> local_count(num_threads, std::vector<std::size_t>(kBuckets, 0));

#pragma omp parallel default(none) shared(keys, local_count, signed_n, pass)
{
const int tid = omp_get_thread_num();
auto &lc = local_count[tid];
#pragma omp for
for (int64_t i = 0; i < signed_n; i++) {
const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1);
lc[bucket]++;
}
}

for (int bucket_idx = 0; bucket_idx < kBuckets; bucket_idx++) {
for (int thr = 0; thr < num_threads; thr++) {
count[bucket_idx] += local_count[thr][bucket_idx];
}
}

for (int i = 1; i < kBuckets; i++) {
count[i] += count[i - 1];
}

for (std::size_t i = n; i-- > 0;) {
const std::size_t bucket = (keys[i] >> (pass * kBits)) & (kBuckets - 1);
tmp[--count[bucket]] = keys[i];
}

keys.swap(tmp);
}
}

void TitaevSortirovkaBetcheraALL::ConvertFromKeys(const std::vector<uint64_t> &keys, OutType &output) {
const auto n = static_cast<int64_t>(keys.size());
output.resize(keys.size());
#pragma omp parallel for default(none) shared(keys, output, n)
for (int64_t i = 0; i < n; i++) {
output[i] = OrderedUintToDouble(keys[i]);
}
}

void TitaevSortirovkaBetcheraALL::BatcherStage(OutType &result, std::size_t array_size, std::size_t block,
std::size_t step) {
const auto signed_size = static_cast<int64_t>(array_size);
#pragma omp parallel for default(none) shared(result, signed_size, block, step)
for (int64_t i = 0; i < signed_size; i++) {
const auto idx = static_cast<std::size_t>(i);
const std::size_t partner = idx ^ step;
if (partner <= idx) {
continue;
}
const bool ascending = ((idx & block) == 0);
const bool need_swap = ascending ? (result[idx] > result[partner]) : (result[idx] < result[partner]);
if (need_swap) {
std::swap(result[idx], result[partner]);
}
}
}

void TitaevSortirovkaBetcheraALL::BatcherSort() {
auto &result = GetOutput();
const std::size_t n = result.size();
if (n < 2) {
return;
}
for (std::size_t block = 2; block <= n; block <<= 1) {
for (std::size_t step = block >> 1; step > 0; step >>= 1) {
BatcherStage(result, n, block, step);
}
}
}

bool TitaevSortirovkaBetcheraALL::RunImpl() {
auto &input = GetInput();
const std::size_t n = input.size();
if (n <= 1) {
return true;
}
std::vector<uint64_t> keys(n);
ConvertToKeys(input, keys);
RadixSort(keys);
ConvertFromKeys(keys, GetOutput());
if ((n & (n - 1)) == 0) {
BatcherSort();
}
return true;
}

bool TitaevSortirovkaBetcheraALL::PostProcessingImpl() {
return true;
}

} // namespace titaev_m_sortirovka_betchera
7 changes: 3 additions & 4 deletions tasks/titaev_m_sortirovka_betchera/omp/include/ops_omp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ class TitaevSortirovkaBetcheraOMP : public BaseTask {
bool PostProcessingImpl() override;

static void ConvertToKeys(const InType &input, std::vector<uint64_t> &keys);
static void RadixSortParallel(std::vector<uint64_t> &keys);
static void RadixSort(std::vector<uint64_t> &keys);
static void ConvertFromKeys(const std::vector<uint64_t> &keys, OutType &output);
void BatcherSortParallel();

static void BatcherStepParallel(OutType &result, size_t n, size_t step, size_t stage);
void BatcherSort();
static void BatcherStage(OutType &result, std::size_t array_size, std::size_t block, std::size_t step);
};

} // namespace titaev_m_sortirovka_betchera
Loading
Loading