C++20 библиотека для сортировки данных, превышающих объём оперативной памяти, с использованием алгоритма k-way external merge sort.
При работе с большими данными (логи, базы данных, научные вычисления) часто возникает необходимость сортировки файлов размером в десятки и сотни гигабайт — значительно больше доступной RAM. Стандартные in-memory алгоритмы (std::sort) здесь неприменимы.
External Sort решает эту задачу:
- Читает данные порциями, помещающимися в память
- Сортирует каждую порцию и записывает во временный файл (run)
- Сливает runs через k-way merge до получения финального отсортированного файла
- Сортировка файлов любого размера при ограниченной памяти
- Поддержка любых сериализуемых типов (POD,
std::string,std::vector, custom) - Настраиваемые параметры: объём памяти, степень слияния (k), размер буферов
- Сортировка по возрастанию и убыванию
- Move-семантика для эффективной работы с дорогостоящими типами
- Абстракция от хранилища (файлы или in-memory для тестов)
- Интеграция с любой системой логирования
#include "k_way_merge_sorter.hpp"
#include "file_stream.hpp"
int main() {
// Фабрика для работы с файлами (временные файлы в "temp_dir")
io::FileStreamFactory<int> factory("temp_dir");
// Сортировщик
external_sort::KWayMergeSorter<int> sorter(
factory,
"input.bin", // входной файл
"sorted.bin", // выходной файл
64 * 1024 * 1024, // 64 MB памяти для runs
8, // 8-way merge
4096, // буфер I/O
true // по возрастанию
);
sorter.Sort();
return 0;
}Проект состоит из четырёх модулей, каждый со своей зоной ответственности:
┌─────────────────────────────────────────────────────────────────────┐
│ external_sort │
│ (алгоритм k-way merge sort) │
│ │
│ Использует io для чтения/записи, serialization для работы с │
│ произвольными типами, logging для отладки и мониторинга │
└─────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ io │ │ serialization │ │ logging │
│ │ │ │ │ │
│ Потоки ввода/ │ │ Сериализация │ │ Абстрактный │
│ вывода, │ │ типов в бинар- │ │ интерфейс │
│ временные │ │ ный формат │ │ логирования │
│ файлы │ │ │ │ │
└───────────────┘ └─────────────────┘ └─────────────────┘
-
external_sort — главный модуль, реализует алгоритм сортировки
- Получает данные через абстрактный
IStreamFactory<T> - Использует
Serializer<T>для расчёта размера элементов в памяти - Логирует прогресс через глобальный
logging::Registry
- Получает данные через абстрактный
-
io — абстракция ввода-вывода
FileStreamFactory— работа с реальными файламиInMemoryStreamFactory— работа в памяти (для тестов)TempFileManager— управление временными файлами- Использует
serializationдля записи/чтения элементов
-
serialization — сериализация данных
- Автоматическая поддержка POD типов
- Встроенная поддержка
std::string,std::vector<T> - Расширяемость через методы или специализации
- C++20 concepts для проверки типов
-
logging — гибкое логирование
- Абстрактный интерфейс
ILogger - Готовая интеграция с spdlog
NullLoggerдля отключения логовLoggerAdapterдля любого пользовательского логгера
- Абстрактный интерфейс
| Модуль | Описание | Документация |
|---|---|---|
| external_sort | Алгоритм k-way merge sort | README |
| io | Потоки ввода-вывода, буферизация | README |
| serialization | Сериализация типов | README |
| logging | Система логирования | README |
- Bazel (рекомендуется 7.0+)
- C++20 компилятор (GCC 11+, Clang 14+, MSVC 2022+)
- macOS, Linux или Windows
| Зависимость | Версия | Назначение |
|---|---|---|
| GoogleTest | 1.14.0 | Тестирование |
| spdlog | 1.16.0 | Логирование (опционально) |
| Google Benchmark | 1.9.4 | Бенчмарки |
# Сборка всего проекта
bazel build //...
# Запуск всех тестов
bazel test //...
# Пример использования
bazel run //external_sort:example
# Пример сериализации
bazel run //serialization:example
# Бенчмарки производительности
bazel run -c opt //external_sort:performance_runner
# Генерация compile_commands.json (для IDE)
bazel run @hedron_compile_commands//:refresh_allint, double, float, char, uint64_t, ...
struct Point { double x, y, z; }; // trivially copyablestd::string
std::vector<T> // где T — любой поддерживаемый типstruct MyRecord {
uint64_t id;
std::string name;
// Методы сериализации
bool Serialize(FILE* file) const;
bool Deserialize(FILE* file);
// Для оптимальной производительности
uint64_t GetSerializedSize() const;
// Для сортировки
bool operator<(const MyRecord& other) const;
};Подробнее: serialization/README.md
| Параметр | Описание | Рекомендация |
|---|---|---|
mem_bytes |
Память для runs | Больше → меньше runs → быстрее |
k_degree |
Степень k-way | 4-16 оптимально |
io_buf_elems |
Буфер I/O | 1024-8192 элементов |
// Для файла ~10 GB
KWayMergeSorter<MyType> sorter(
factory,
"huge_input.bin",
"sorted_output.bin",
256 * 1024 * 1024, // 256 MB
16, // 16-way merge
8192,
true
);Алгоритм оптимизирован для минимизации I/O операций:
- Время: O(N log N)
- I/O: O((N/M) × log_k(N/M)) проходов по данным
- Диск: ~2x размер входных данных (временные файлы)
Где N — количество элементов, M — память для runs, k — степень слияния.
bazel run -c opt //external_sort:performance_runnerТестируемые сценарии:
- Разные размеры памяти (16/64/256 MB)
- Разные степени слияния (2/8/32/128)
- Разные размеры файлов (5M-100M элементов)
- Разные распределения (random/sorted/reverse)
- Разные типы (uint64_t/string/struct)
# Все тесты
bazel test //...
# Тесты конкретного модуля
bazel test //external_sort/...
bazel test //io/...
bazel test //serialization/...
# С подробным выводом
bazel test //... --test_output=streamedExternal-Sort/
├── external_sort/ # Основной алгоритм сортировки
│ ├── include/
│ │ └── k_way_merge_sorter.hpp
│ ├── examples/
│ ├── benchmarks/
│ └── tests/
│
├── io/ # Ввод-вывод и буферизация
│ ├── include/
│ │ ├── interfaces.hpp # IInputStream, IOutputStream, IStreamFactory
│ │ ├── file_stream.hpp
│ │ └── memory_stream.hpp
│ └── tests/
│
├── serialization/ # Сериализация типов
│ ├── include/
│ │ ├── serializers.hpp
│ │ └── type_concepts.hpp
│ ├── examples/
│ └── tests/
│
├── logging/ # Система логирования
│ ├── include/
│ │ ├── ILogger.hpp
│ │ ├── Registry.hpp
│ │ └── SpdlogWrapper.hpp
│ └── src/
│
├── MODULE.bazel # Зависимости Bazel
├── WORKSPACE
└── README.md
- Endianness: Бинарный формат зависит от архитектуры (little-endian/big-endian). Файлы, созданные на одной архитектуре, могут быть некорректно прочитаны на другой
- Версионирование формата: Отсутствует — при изменении структуры типа старые файлы станут несовместимы
- Многопоточность: Сортировка выполняется в одном потоке. Для параллельной обработки нескольких файлов создавайте отдельные экземпляры
KWayMergeSorter - Дисковое пространство: Требуется ~2x размера входных данных для временных файлов
- Минимальная память:
mem_bytesдолжен вмещать хотя бы один элемент
Проект открыт для вклада! Если вы хотите помочь:
- Форкните репозиторий
- Создайте ветку для вашей функции (
git checkout -b feature/amazing-feature) - Сделайте коммит изменений (
git commit -m 'Add amazing feature') - Запушьте ветку (
git push origin feature/amazing-feature) - Откройте Pull Request
# Убедитесь, что все тесты проходят
bazel test //...
# Проверьте форматирование (если настроен clang-format)
# clang-format -i <измененные файлы>MIT License
Copyright (c) 2024 Vladimir Kondratyonok
Vladimir Kondratyonok
Ссылки на документацию модулей:
- external_sort — алгоритм сортировки
- io — потоки ввода-вывода
- serialization — сериализация
- logging — логирование