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

11 KiB
Raw Blame History

Исследование алгоритма разрешения зависимостей в 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. Вывод