Files
ARSV/articles/for-asvk/plan.md
T
2026-08-11 15:12:52 +03:00

242 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Исследование алгоритма разрешения зависимостей в ALT RPM
## Замысел и структура статьи
Статья будет как решение одной формальной задачи. В ALT RPM пакет-потребитель задаёт множество
требуемых символов $R$ (`Requires`), а пакет-поставщик - множество предоставляемых
символов $P$ (`Provides`). Зависимость выполнима тогда и только тогда, когда
$$
R \subseteq P.
$$
Явное перечисление тысяч ELF-, Python- и файловых символов раздувает RPM-метаданные,
поэтому множества представлены компактными set-строками.
## 1. Постановка задачи и данные ALT Linux
Ввести обозначения для bpp(b), множеств, хэш функций(H), длин строк и т.д.
Проверяемое программой условие имеет вид
$$
H_b(R)\subseteq H_b(P).
$$
Отмечаем вероятностную природу: из $R\subseteq P$ следует
$H_b(R)\subseteq H_b(P)$, поэтому коллизия не создаёт ложного отказа. Обратное
неверно: отсутствующий символ может совпасть по хешу с символом из $P$ и дать
ложное принятие зависимости.
(Представить данные ниже табличкой)
Привести срез Sisyphus `x86_64 + noarch`: 47 367 пакетов, 14 761 `Provides`-отношение и 68 857
`Requires`-отношений. Для 68 198 реально сопоставимых пар медианы равны
$p=480$, $r=13$, $p/r=28.1$; p75 - 1886, 37 и 80; p90 - 6219, 104 и 257.
В 92.4% пар выполняется $p/r\ge 4$. Это к теме, почему рассматриваем разреженные множества.
## 2. Устройство существующей `set:`-строки
Даём схему формата:
```
массив строк
| (Jenkins OAAT)
v
массив хэшей
| (qsort)
v
отсортированный массив хэшей
| (вычисление разницы между элементами)
v
массив delta
| (Rice-Golomb преобразование)
v
битовый массив
| (base62 преобразование)
v
set-строка
```
Нужно отдельно разобрать заголовок, диапазоны `bpp` и `Mshift`, внешний префикс
`set:` и роль Base62: строка должна оставаться допустимым токеном версии RPM.
Полезна небольшая иллюстрация на 5-8 символах: исходные имена, хеши,
отсортированный массив, дельты, биты кода и итоговая строка.
Но. Это много альт-специфики, может настолько глубоко не стоит (хотя и не очень глубоко)
### 2.1. Хеширование и вероятность коллизий
! Всё ещё нет нормального анализа коллизий
Текущий код использует 32-битный Jenkins OAAT и оставляет $b$ бит. Для модели
равномерного независимого хеширования ожидаемое число столкнувшихся пар среди
$n$ различных символов равно
(будет мудрёная формула, теория)
В статье формулы дополняем разными табличками по реальным данным из Sisyphus: распределения $p,r,b$, ожидаемые коллизии, реальные.
(Другие хэши идут далее)
### 2.2. Кодирование Golomb-Rice
Для отсортированных значений $0\le x_1<\dots<x_n<N$ определить
$\delta_1=x_1$, $\delta_i=x_i-x_{i-1}$.
Занимаеи столько и столько бит. В реализации параметр выбирается из
$b$ и $n$ приблизительно как
$$
m=b-\lfloor\log_2 n\rfloor-1
$$
с ограничением допустимым диапазоном формата. При средней дельте
$\mathbb E\delta\approx 2^b/n$ это даёт ожидаемую длину порядка
...
### 2.3. Base62
Пояснить экзотичность Base62, его алфавит, принцип кодирования, Z-escape,
## 3. Построение `set:`-строк в `set.c`
(фактически его можно в 2 упихать)
Рассказать путь `set_new()` - `set_add()` - `set_fini()` и отдельно объяснить
сложные участки:
(а тут и нет сложного, всё в cmp запихали)
Можно рассказать о слитом энкодере, но это есть в переписанной части
В оригинальном варианте используется `qsort()`
Записать итоговую сложность построения:
...
И доп памяти:
...
## 4. Сравнение `set:`-строк
### 4.1. Обратное декодирование
Кратко пройти обратную цепочку:
```
set-строка
| (обратное base62 преобразование)
v
битовый массив
| (обратное Rice-Golomb преобразование)
v
массив delta
| (вычисление изначальных значений)
v
массив хэшей
```
Если `bpp` различается, более точное множество проецируется на меньшее число бит,
после чего снова удаляются дубликаты. (btw, тут есть сортировка слиянием, интересный момент)
### 4.2. Кэш декодированных множеств
В исходном `set.c` кэш содержит 256 записей (`CACHE_SIZE=256`), при вытеснении
использует позицию 243 (`PIVOT_SIZE=243`), `free()` и два `memmove()`. Ключ включает
короткий fingerprint, а окончательная проверка требует сравнения строки.
- что именно кэшируется;
- стоимость линейного поиска, `strcmp`, перемещений и аллокаций;
- слабое повторное использование при потоке десятков тысяч различных пар;
(всё надо сильно короче расписывать)
### 4.3. Проверка включения и «прыжок» по массивам
После декодирования задача сводится к двум возрастающим массивам. Базовый вариант
рассказ про IFLT4 и IFLT8
Худшая асимптотика остаётся
$$
T_{\subseteq}(p,r)=O(p+r),
$$
Полная сложность алгоритма холодного сравнения:
...
При попадании в кэш:
...
## 5. Оптимизации с сохранением текущего формата
### 5.1. Новый кэш
Заменить небольшой кэш на два раздельных bucketed LRU для `Provides` и `Requires`, увеличить вместимость. Хранить длину и более сильный fingerprint, избегать лишних
`strlen`/`strcmp`, `free` и полных `memmove`. Отдельно убрать повторные `realloc` при построении входных наборов.
Проверить cold, warm, поток меняющихся `Requires` к одному `Provides` и поток
полностью уникальных пар.
### 5.2. Radix sort
Для больших наборов заменить $O(n\log n)$ `qsort()` стабильной LSD radix sort по
байтам хеша. Число проходов
$$
k=\left\lceil\frac b8\right\rceil,
\qquad T_{\mathrm{radix}}=O(kn),
$$
дополнительная память $O(n+256)$. Для малых $n$ оставить `qsort()`; в `set9.c`
порог равен 128.
### 5.3. Оптимальный проход по массивам
Сделать адаптивный алгоритм:
- равные мощности - `memcmp` и быстрый вывод «равны/несравнимы»;
- близкие мощности - обычное линейное слияние;
- $p/r$ велико - galloping/jump search с монотонной нижней границей;
- немедленный выход при первом отсутствующем хеше.
## 6. Варианты со сменой формата или API
### 6.1. Roaring Bitmap - отрицательный результат
Равномерно распределённые хеши не образуют плотных диапазонов, ради которых создан
Roaring. Для 1000 символов при `bpp=32` получено:
| Представление | Длина строки | Cold compare | Warm compare |
| -------------------- | -----------: | -----------: | -----------: |
| Golomb/Base62 `set9` | 3994 | 293.87 мкс | 5.24 мкс |
| raw Roaring/hex | 19 924 | 1321.17 мкс | 399.20 мкс |
То есть строка длиннее в 4.99 раза, cold-сравнение медленнее в 4.50 раза, warm -
в 76.14 раза. Zstd уменьшал строку до 14 126 символов, но оставлял её в 3.54 раза
длиннее `set9`; в соответствующем прогоне cold- и warm-сравнение проиграли в 4.96
и 101.86 раза. Этот вариант нужен в статье как важный отрицательный эксперимент.
### 6.2. Прямые хеши и Base64
В формате D1 отсортированные усечённые хеши хранятся фиксированной шириной без
дельт и Golomb-кода, затем упаковываются в Base64. Теоретическая длина payload:
...
Для 1000 символов и `bpp=32` строка выросла с 3994 до 5340
символов (+33.7%). Приложить разные результаты тестов. (тесты бы прогнать снова)
Отдельно про увеличение размера и влияние на apt команды от этого
выводы насчёт формата
### 6.3. Альтернативные хэш функции
тут про разные хэши, их быстрдействие и тесты коллизий
## 8. Вывод