An open API service indexing awesome lists of open source software.

https://github.com/s00inx/goarchiver

highly-performance archiver on golang using Huffman algorithm without external libraries and focused on memory-consuming optimization
https://github.com/s00inx/goarchiver

Last synced: 2 months ago
JSON representation

highly-performance archiver on golang using Huffman algorithm without external libraries and focused on memory-consuming optimization

Awesome Lists containing this project

README

          

# goarchiver

**goarchiver** — архиватор на языке Go, на основе алгоритма сжатия Хаффмана. Фокус в проекте делался на максимальную оптимизацию использования памяти и эффективную работу с кэшем процессора.
#### Ключевые особенности:

- **Нет внешних зависимостей** - используется стандартная библиотека Go.
- **Эффективное использование памяти** - минимальное количество аллокаций благодаря использованию `sync.Pool`.
- **Гарантия целостности данных** - встроенная проверка целостности данных с помощью CRC32.

# Бенчмарки и производительность
Тестирование проводилось на **Intel Core Ultra 5 125H**.
### Основные показатели скорости (1 Мб сырых данных):
Тестирование проводилось с одинаковым объемом сырых данных.

```
Unpack 8060137 ns/op 138.22 MB/s 4712 B/op 12 allocs/op
Pack 4905285 ns/op 227.12 MB/s 7146 B/op 10 allocs/op
```

Благодаря использованию `sync.Pool` и переиспользованию буферов, нагрузка на GC минимальна, что позволяет использовать архиватор в высоконагруженных пайплайнах. Распаковка занимает гораздо больше времени из-за особенностей цикла декодирования (и это дает почву для следующей оптимизации).

### Сравнение с `compress/gzip`
Сравним скорость, потребление памяти и степень сжатия, для этого будем использовать реальные данные из [**Silesia Corpus**]([Silesia corpus - Wikipedia](https://en.wikipedia.org/wiki/Silesia_corpus?ysclid=ml83q92khm133974630)).
Были выбраны файлы dickens (английский текст - стандартный случай), mozilla (сложные бинарные данные с высокой энтропией), nci (научные расчёты), reymont (текст на польском языке - для проверки с другой кодировкой)
#### Сравнение скорости и потребления
##### Сжатие (compression):

| **Файл** | **Инструмент** | **Скорость (MB/s)** | **Память (B/op)** | **Аллокации** |
| :---------: | :------------: | :-----------------: | :---------------: | :-----------: |
| **reymont** | **goarchiver** | **172.78** | **16 KB** | **10** |
| | Gzip | 19.09 | 271 KB | 5 |
| **dickens** | **goarchiver** | **178.66** | **20 KB** | **10** |
| | Gzip | 26.76 | 271 KB | 5 |
| **mozilla** | **goarchiver** | **162.77** | **76 KB** | **12** |
| | Gzip | 39.94 | 813 KB | 16 |
| **nci** | **goarchiver** | **200.82** | **47 KB** | **11** |
| | Gzip | 110.47 | 203 KB | 4 |
##### Анализ ключевых показателей

###### 1. Скорость обработки
- **Текстовые данные:** `goarchiver` обходит Gzip в **6.5 – 9 раз**. Это достигается за счет использования энтропийного кодирования без ресурсозатратного поиска повторов строк (LZ77).
- **Бинарные данные:** Даже на сложных данных (`mozilla`) скорость остается стабильно высокой (~160 MB/s), в то время как Gzip сильно замедляется.
###### 2. Эффективность памят
- **Расход RAM:** В среднем `goarchiver` потребляет в **10–15 раз меньше** оперативной памяти.
- **Стабильность:** Потребление памяти у `goarchiver` крайне низкое и предсказуемое. Это делает его идеальным для использования в микросервисах с жесткими лимитами.
###### 3. Нагрузка на Garbage Collector
- Несмотря на то, что у Gzip в некоторых тестах чуть меньше аллокаций (из-за записи напрямую в `io.Discard`), общий объем выделяемой памяти у него в разы выше.
- **10-12 аллокаций** у `goarchiver` — это результат, гарантирующий отсутствие "фризов" системы при обработке больших архивов.

##### Распаковка (decompression):

| **Данные** | **Инструмент** | **Скорость (MB/s)** | **Память (B/op)** | **Аллокации** |
| :---------: | :------------: | :-----------------: | :---------------: | :-----------: |
| **reymont** | **goarchiver** | **73.20** | **7 KB** | **12** |
| | Gzip | 271.27 | 116 KB | 677 |
| **dickens** | **goarchiver** | **65.47** | **9 KB** | **12** |
| | Gzip | 198.76 | 309 KB | 1865 |
| **mozilla** | **goarchiver** | **70.00** | **22 KB** | **14** |
| | Gzip | 257.55 | 6723 KB | 38951 |
| **nci** | **goarchiver** | **130.24** | **13 KB** | **13** |
| | Gzip | 798.00 | 121 KB | 1067 |

###### 1. Скорость обработки
- `goarchiver` держит уверенные 65–130 MB/s. На бинарных структурах без явных повторов разрыв с Gzip сокращается, так как чистый Хаффман эффективнее обрабатывает данные с высокой энтропией.
###### 2. Эффективность памяти
- **Расход RAM**: `goarchiver` потребляет в 15–30 раз меньше памяти на текстах и до 300 раз меньше на сложных бинарных данных (mozilla: 22 KB против 6.7 MB).
- **Предсказуемость:** мой архиватор использует фиксированные буферы и пулы. Это исключает внезапные скачки потребления ресурсов, что критично для облачных микросервисов с жесткими квотами (LXC/Docker limits).
###### 3. Нагрузка на Garbage Collector
- **Минимум аллокаций:** cтабильные 12–14 аллокаций на операцию. Для сравнения: Gzip на бинарных данных создает почти 39,000 объектов, перегружая сборщик мусора.
- **Отсутствие "фризов":** благодаря тому, что объем выделяемой памяти (B/op) мал, нагрузка на CPU со стороны GC практически отсутствует.
#### Сравнение степени сжатия
| **Файл** | **Оригинал (КБ)** | **Gzip (КБ)** | **GoArchiver (КБ)** | **Сжатие (%)** |
| ----------- | ----------------- | ------------- | ------------------- | -------------- |
| **mozilla** | 50 020 | 18 603 | **39 041** | ~22% |
| **nci** | 32 768 | 3 125 | **9 985** | ~70% |
| **dickens** | 9 954 | 3 778 | **5 690** | ~43% |
| **reymont** | 6 472 | 1 816 | **3 938** | ~39% |

`goarchiver` использует чистый алгоритм Хаффмана, из-за этого уступает `gzip` (который использует deflate) в степени сжатия. Но он оптимизирован для максимальной скорости при сохранении достойного уровня сжатия.
- **максимальное сжатие (nci):** до **70%** экономии места.
- **среднее сжатие текста:** **40-45%**.

Значит `goarchiver`подойдёт для **инфраструктур с дефицитом ресурсов**, где скорость упаковки и стабильность RAM важнее плотности сжатия. Он идеально подходит для микросервисов и Edge-систем, так как потребляет до **230 раз меньше памяти** и работает в **6–9 раз быстрее Gzip** на упаковке логов и бинарных данных, практически не нагружая сборщик мусора (GC).
# Технические детали

Проект спроектирован с упором на эффективное использование кэш-линий процессора и минимизацию давления на Garbage Collector (`Mechanical Sympathy`).
### Алгоритм и оптимизации

Сжатие выполняется в три этапа и 2 прохода по файлу по алгоритм
1. **Статистический анализ:**
Однопоточный сбор частот символов.
2. **Построение модели:**
- **Flat Memory Layout:** Бинарная куча и дерево Хаффмана реализованы на базе **плоских массивов**. Это исключает прыжки по указателям, обеспечивая высокую локальность данных и эффективную работу L1/L2 кэша.
- **Zero-allocation build:** Память под дерево аллоцируется единоразово. Узлы имеют фиксированный размер, что позволяет вычислить объем памяти заранее.
- **Canonical Huffman:** Дерево приводится к каноническому виду. Это позволяет восстанавливать таблицу кодов, храня только их длины, что значительно уменьшает размер заголовка.
3. **Кодирование:**
- **Pooling:** Для минимизации аллокаций в куче используется `sync.Pool`. Тяжелые структуры (деревья) переиспользуются между операциями.
- **Integrity:** CRC32 вычисляется параллельно с кодированием в том же буфере, обеспечивая контроль целостности без дополнительных проходов по памяти.

Распаковка выполняется в три этапа за один проход по сжатым данным:
1. **Восстановление модели (Prepare Data):**
- На основе таблицы длин, считанной из заголовка, восстанавливается структура канонического дерева Хаффмана.
- Вместо построения дерева из узлов и указателей формируются четыре компактных массива-справочника (`mincodes`, `symb`, `offsets`, `counts`). Это позволяет декодировать символы, используя только арифметику массивов.
- Использование плоских массивов гарантирует, что все данные для поиска символа помещаются в кэш-линию процессора, исключая дорогостоящие промахи по памяти.
2. **Декодирование (Bitstream Processing):**
- Реализован механизм `fillacc`, который подгружает данные в 64-битный аккумулятор. Это позволяет обрабатывать биты в памяти регистрами процессора, не обращаясь к `io.Reader` для каждого бита.
- Функция `decodeNext` находит символ за один проход по массиву длин. Благодаря каноническому виду, поиск осуществляется простым сравнением числового кода с минимальным значением для данной длины (`code < mincodes[l] + counts[l]`).
- Основной цикл распаковки не создает новых объектов в куче. Вся обработка происходит в рамках заранее выделенных массивов на стеке или в пуле.
3. **Стриминг и контроль (Streaming & Integrity):**
- Выходные данные накапливаются в буфере из `sync.Pool` и сбрасываются в `io.Writer` блоками по 32 КБ. Это оптимизирует системные вызовы записи.
- CRC32 вычисляется непосредственно в процессе записи декодированных байт.
- После обработки `originalSize` байт выполняется сверка контрольной суммы. Остаток бит в аккумуляторе синхронизируется, чтобы извлечь 4-байтовый хеш и подтвердить целостность данных.
### Формат файла

Бинарная структура файла оптимизирована для компактности:

|**Смещение**|**Размер**|**Описание**|
|---|---|---|
|`0-1`|2 байта|**Magic Bytes** (идентификатор формата)|
|`2-3`|2 байта|Длина оригинального имени файла ($N$)|
|`4-(4+N)`|$N$ байт|Оригинальное имя файла и расширение|
|`...`|8 байт|Размер несжатых данных (uint64)|
|`...`|1 байт|**Tree Metadata:** `0` — полная таблица (256 байт); `1-32` — разреженная таблица (пары символ-длина)|
|`...`|Variable|Таблица длин кодов Хаффмана|
|`...`|Variable|Сжатые данные (Payload)|
|Конец|4 байта|Контрольная сумма **CRC32**|

> Адаптивное хранение дерева (полное или разреженное) позволяет существенно экономить место на файлах с малым алфавитом.
>
# Быстрый старт

### Готовый исходник

Вы можете скачать уже скомпилированный .exe файл из Releases, который не потребует установки ничего больше (чтобы пользоваться надо добавить в PATH).
### Установка и сборка

Перед установкой убедитесь, что у вас установлен Go версии 1.23 или новее и утилита make на Windows ([Make for Windows](https://gnuwin32.sourceforge.net/packages/make.htm)).
Вы можете сами скомпилировать исполняемый файл. и чтобы пользоваться архиватором из любой точки компьютера вам надо будет вручную добавить путь к exe в PATH.

```bash
git clone https://github.com/kfcemployee/goarchiver.git
cd goarchiver
make build
```

Или можно скомпилировать и добавить в PATH одной командой.
```bash
git clone https://github.com/kfcemployee/goarchiver.git
cd goarchiver
make install
```
(никаких внешних зависимостей и go.sum не нужно)

... если у вас вдруг возникли проблемы с make:
```bash
git clone https://github.com/kfcemployee/goarchiver.git
cd goarchiver
go build -ldflags="-s -w" -o goarc.exe ./cmd/goarc/main.go
```
### Использование

```
# Сжатие
goarc p

# Распаковка
goarc u
```
### Запуск тестов и бенчмарков

```bash
make test # тесты
make bench # бенчмарки
make benchcpu # бенчмарк на разном количестве ядер
```
## Структура проекта

- `/cmd/goarc` — CLI интерфейс.
- `/internal/engine` — Ядро архиватора (логика сжатия и LUT).
- `Makefile` — Скрипты сборки и тестирования.
## Лицензия.

This project is licensed under the MIT License - see the [LICENSE](https://www.google.com/search?q=LICENSE) file for details.