Compare commits

...
3 Commits
Author SHA1 Message Date
krosh 292421d970 doc for set9.c 2026-07-29 04:39:31 +03:00
krosh b95decdffb add doc for set9.c 2026-07-29 04:37:48 +03:00
krosh 086dda83ca delete unused data 2026-07-28 06:29:13 +03:00
9 changed files with 587 additions and 886 deletions
-125
View File
@@ -1,125 +0,0 @@
# Сравнение set-зависимостей Sisyphus с `newset.c`
Скрипт [`scripts/compare_sisyphus_set_versions.sh`](../scripts/compare_sisyphus_set_versions.sh)
сравнивает set-строки из RPM-заголовка с результатом `reimplement/newset.c`.
## Что именно сравнивается
1. Из изолированных APT-индексов `Sisyphus/x86_64` и `Sisyphus/noarch`
берутся `Package`, `Architecture`, `Version`, `Filename`, `MD5Sum`,
`Provides` и `Depends`/`Pre-Depends`.
2. RPM скачивается непосредственно из `Sisyphus/files/<arch>/RPMS` и
проверяется по `MD5Sum` из индекса.
3. Эталонные зависимости читаются из RPM-заголовка через массивы тегов:
`PROVIDENAME/PROVIDEFLAGS/PROVIDEVERSION` и
`REQUIRENAME/REQUIREFLAGS/REQUIREVERSION`.
4. Для воспроизведения зависимостей вызываются установленные вместе с
`rpm-build` программы `%_rpmlibdir/find-provides` и
`%_rpmlibdir/find-requires` с методом `none,lib`.
5. В каталоге инструментов заменяется **только** `mkset`: вместо штатного
`mkset` подставляется совместимая обвязка над `newset.c`. `provided_symbols`,
`ldd --bindings`, `eu-readelf`, `objdump`, `file` и остальные средства
отбора/извлечения символов остаются штатными инструментами `rpm-build`.
Для `Requires` APT получает точный уже скачанный RPM-файл как вход и скачивает
его полный runtime dependency closure в пустой пользовательский RPM root. Так
версия и архитектура целевого пакета не выбираются повторно по имени. APT
запускается с пустым `APT_CONFIG`; source list, package cache, archives, lists,
status и preferences перенаправлены во временный каталог, поэтому настройки,
hooks и RPM-база основной системы не участвуют в разрешении зависимостей.
Перед распаковкой проверяются имена элементов cpio и цели символических ссылок.
Абсолютная RPM-ссылка считается путём внутри виртуального package root: например,
`/usr/sbin/update-alternatives -> /bin/true` безопасно переписывается в
`../../bin/true`, а не направляется в `/bin/true` основной системы. Все symlink
сначала собираются из RPM-заголовков и создаются в порядке от родительских путей
к дочерним; затем `cpio` извлекает только нессылочные элементы. Благодаря этому
usrmerge-ссылки вроде `/bin -> usr/bin` создаются до файлов `/bin/*`.
Имена элементов с выходом через `..` и относительные symlink, фактически
выходящие из временного root, по-прежнему отклоняются. После проверки RPM-файлы
распаковываются без установки. Это позволяет штатному `rpm-build/ldd`
использовать интерпретатор и библиотеки из того же снимка Sisyphus, не изменяя
систему.
## Ограниченный тест
На ALT p11 с установленным `rpm-build`:
```sh
bash scripts/compare_sisyphus_set_versions.sh \
--package strace \
--report strace-set-report.tsv
```
Несколько пакетов:
```sh
bash scripts/compare_sisyphus_set_versions.sh \
--package strace \
--package libdw \
--report sample-set-report.tsv
```
Либо первые десять записей индекса:
```sh
bash scripts/compare_sisyphus_set_versions.sh \
--limit 10 \
--report sample-set-report.tsv
```
Без `--all`, `--limit` или `--package` скрипт завершится до обращения к
репозиторию. Это защищает от случайного полного запуска.
## Полный запуск
Команда предусмотрена, но в ходе разработки не запускалась:
```sh
bash scripts/compare_sisyphus_set_versions.sh \
--all \
--report sisyphus-set-report.tsv
```
Продолжение после прерывания:
```sh
bash scripts/compare_sisyphus_set_versions.sh \
--all \
--resume \
--report sisyphus-set-report.tsv
```
`--resume` пропускает только уже завершённую комбинацию
`package/architecture/version`; обновившийся пакет будет обработан заново.
Завершённость подтверждается маркером `complete=1` в полной 11-польной строке
`SUMMARY`, поэтому оборванная при записи строка не считается результатом.
Во время работы отчёт можно наблюдать отдельно:
```sh
tail -f sisyphus-set-report.tsv
```
`START` записывается до обработки пакета, а `DEPENDENCY` и `SUMMARY` — сразу
после сравнения пакета. Поэтому файл обновляется в процессе, а не только после
завершения всего обхода.
## Статусы отчёта
- `match` — set-строка из RPM совпала с результатом `newset.c`;
- `mismatch` — capability и оператор совпали, set-строки различаются;
- `missing_generated` — RPM содержит set-зависимость, но повторный запуск
`rpm-build` её не сгенерировал;
- `extra_generated` — повторный запуск сгенерировал зависимость, которой нет в
RPM-заголовке;
- `no_set_metadata` — в APT metadata пакета нет set-строк;
- `*_error` — ошибка скачивания, контрольной суммы, распаковки либо генератора.
`extra_generated` показывается в строках `DEPENDENCY` и делает итоговый статус
пакета равным `mismatch`. При анализе нужно учитывать, что бинарный RPM не
содержит исходных spec-фильтров (`filter_from_requires` и аналогичных), поэтому
часть таких расхождений может относиться не к `newset.c`, а к невозможности
полностью воспроизвести фильтрацию исходного spec. Их число отдельно указано в
поле `extras=N` строки `SUMMARY`.
-71
View File
@@ -1,71 +0,0 @@
# Notes
## to read
https://www.altlinux.org/Features/Specific
## Hash
в данный момент используется Jenkins OAAT. Считается чуть устаревшим(check), главным аналогом является:
https://github.com/cyan4973/xxhash
xxHash64 (XXH64): относительно простая имплементация на си, хвалят за скорость, 64 бит, параллель
необходимы тесты и графики:
1. насколько быстрее/медленнее
2. коллизии на наборе букв в сравнении с рандомными данными
3. коллизии у первого и второго хэшей между собой
> В XXH3 внутренний цикл, который оптимально обрабатывается векторизацией. Благодаря этому функция использует аппаратную поддержку на наборах инструкций SSE2, AVX2 и NEON. Производительность зависит от комилятора. Неожиданно оказалось, что версия, скомпилированная clang, намного превосходит остальные. Ян Колле даже подумал, что производительность хеш-функции здесь превысит пропускную способность памяти. Этой версии на графике соответствует пунктирная линия.
есть ещё cityhash от гугла, но она c++
в случае XXH64 строк кода ~200, можно вставить в файл
в случае XXH3 строк кода ~2к, стоит использовать библиотеку напрямую, бессмысленно
## about collizions
> А вы пробовали побитовую вероятностную карту строить?
> Есть у нас по условию множество из входных значений, которые замапились в N хешей (включая коллизии!). Эти N хешей имеют одинаковый размер и, по сути, являются массивами из K бит.
> Так вот можно среди этих хешей посчитать 0 и посчитать 1, получится K распределений. Чем они все ближе к пополамному ± 6%, тем лучше.
> https://raw.altlinux.team/arseny/snippets/2026-06/QSIrJSuwxp.txt
> https://raw.altlinux.team/arseny/snippets/2026-06/QSIrJSuwxp.c
## free
free не делает очистку самой структуры, valgrind
## data
алфавит для символ библиотек:
```
.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
```
- предоставленные/определенные символы из библиотек обычно используют @@VERSION
- обязательные/неопределенные символы из двоичных файлов обычно используют @VERSION
## additional about set:version.md
[коды](https://altlinux.space/arseny/atsv-research) от Арсения для наглядности происходящего
some funny [msg's](https://lists.pld-linux.org/mailman/pipermail/pld-devel-en/2013-November/012467.html)
по коду неоднократно раскидано `bpp < 10 || bpp > 32` проверки
запись `char = [1,0]` мне не нравится.
про "отрицательные значения = код ошибки" встречается в многих местах, стоит вынести отдельно
проверить кодом примеры, особенно base62
кэш вечно копируется и переносится, немного странно
PIVOT_SIZE странный
прыжки IFLT8 и IFLT4 я бы возможно делал как c1/c2
учитывая оптимизации, возможно стоит самостоятельно менять массивы местами до начала всех операций (но проблема с кэшем возможна)
хочу себе день, чтобы переписать это всё на English
-46
View File
@@ -1,46 +0,0 @@
# raw_data
## messages from tg
[6/23/26 9:57PM] Arseny: Кстати. У меня есть серьёзная математическая, она же очень серьёзная алгоритмическая задача на исследование. ALT set:version (https://git.altlinux.org/gears/r/rpm.git?a=blob;f=lib/set.c) нужен майнтейнер.
[6/23/26 9:57PM] Arseny: Вы хотели алгоритмов? Вот, расскажите мне про этот.
[6/23/26 9:57PM] Arseny: весь алгоритм можно разбить на 4 последовательных стадии.
0) Задача в том, чтобы проверять, выполняется ли R ⊂ P.
множества, стоящие в позиции P, даются командой навроде nm --dynamic -j -U /usr/lib64/libyourfavourite.so.N.
множества, стоящие в позиции R, даются командой навроде nm --dynamic -j -u /usr/bin/yourfavouriteprog, или там может быть иной .so.
Тут, наверное, ещё и введение в предметную область должно быть.
1) hash: N элементов сбрасываются в этот самый хеш; его эффективность бы стоило оценить хорошенько. (гипотеза от меня: он вообще-то плохой и даёт много коллизий на интересных нам инпутах), в результате K <= N отсортированных хешей, чисел из [0; 2**10) (по умолчанию 10 бит на хеш);
2) delta: K <= N чисел из прошлого шага заменяются на разности с предыдущими, и получаются по-другому распределённые числа, чаще маленькие и очень, очень мало сверхбольших; есть мнение, что они для равномерно распределённых хешей распределены геометрически;
3) golomb: эти K разностей кодируются по Rice-Golomb (https://en.wikipedia.org/wiki/Golomb_coding), делитель по умолчанию 2**7; получается префиксный код из K битострок, которые можно слева направо прочесть без lookahead и восстановить массив разностей;
4) (якобы) base62: их конкатенация кодируется в буквы.
(2) и (3) близки к очевидно-понятным; я поизучал, реализовал на питоне (https://altlinux.space/arseny/atsv-research) и снова поизучал.
А вот (1) и (4) я не занимался совсем.
От (4) у меня тоже башка взрывается, а (1) можно очень хорошо так поизучать, и существует, наверное, обширная литература.
[6/23/26 9:57PM] Arseny: grep -A16 Jenkins lib/set.c
вот в этом дереве исходников (https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=lib/set.c;h=9474a2ee6d7c;hb=a01a87db6e8a)
[6/23/26 9:57PM] Arseny: Пусть параметр-делитель равен M, и энкодеру на вход поступает число A. Тогда ищутся такие q и r < q, что A = qM + r. Частное кодируется унарной кодировкой из единиц, за ним следует нулик, а далее ровно log₂M бит на остаток. Подобрать M нужно с умом.
```sh
python3 stringset.py 10 7 <<EOF
foo
bar
quux
EOF
hashes=[298, 487, 588]
deltas=[298, 189, 101]
codes=[b'1100101010', b'100111101', b'01100101']
golomb divisor: 128 (minimum word bit length: 8)
total bit length: 27
per-elem bit length: 9.000
```
Но здесь не хеш из файла выше, а питонический hash() обычный. Чтобы смотреть на актуальные результаты, нужно подсунуть актуальный.
[6/23/26 9:58PM] Arseny: А вот страничка set:version на ALT Wiki (https://www.altlinux.org/Set-version)
[6/23/26 9:58PM] Arseny: Putze, F.; Sanders, P.; Singler, J. (2007),
253 * "Cache-, Hash- and Space-Efficient Bloom Filters",
254 * http://algo2.iti.uni-karlsruhe.de/singler/publications/cacheefficientbloomfilters-wea2007.pdf
[6/23/26 9:58PM] Arseny: Сайта нет, но статью поискать https://t.me/c/1622441719/1359/9771
[6/23/26 9:59PM] Arseny: И ещё наследие Алексея Турбина:
https://ftp.altlinux.org/pub/people/at/
https://github.com/svpv
-444
View File
@@ -1,444 +0,0 @@
# `rpmquery` в ALT Linux
## Краткий вывод
`rpmquery` — отдельная точка входа в тот же механизм запросов RPM, что и
`rpm -q`. В ALT Linux p11 `/usr/bin/rpmquery` является символьной ссылкой на
ELF-программу `/usr/lib/rpm/rpmq`; имя запуска заранее включает режим запроса,
поэтому `-q` обычно не нужен:
```sh
rpmquery rpm
rpmquery -q rpm
rpm -q rpm
```
Все три команды дают один и тот же результат. При этом `rpmquery` не является
интерфейсом к репозиторию APT: без `-p` он читает только установленную базу RPM,
по умолчанию `/var/lib/rpm`. Для поиска доступных, но не установленных пакетов
нужны `apt-cache`, `apt-get` или веб/RDB-интерфейсы ALT.
Исследование проверено на официальном контейнере `alt:p11`, обновлённом до
`rpm-4.13.0.1-alt45.x86_64`, и по ветке `p11` исходников RPM, commit
`7e71ed3da438289d916f1cccc9c0a83e594455dc`.
## Где ищет `rpmquery`
| Вызов | Источник данных |
|---|---|
| `rpmquery NAME` | установленная база RPM |
| `rpmquery -a` | все пакеты из установленной базы RPM |
| `rpmquery -p FILE.rpm` | заголовок указанного RPM-файла, установка не требуется |
| `rpmquery --root ROOT ...` | база и файловая система относительно `ROOT` |
| `rpmquery --dbpath DIR ...` | явно указанная база RPM |
Практическая граница была проверена так: `apt-cache show strace` находил пакет
в p11, тогда как `rpmquery strace` отвечал `package strace is not installed` и
завершался с кодом `1`.
## Работа с APT-репозиторием
APT-RPM загружает индексы подключённых репозиториев в `/var/lib/apt/lists`.
Обычный порядок работы:
```sh
# Показать настроенные источники ALT, если установлен пакет apt-repo.
apt-repo
# Обновить локальные индексы после изменения источников.
apt-get update
```
В минимальных контейнерах `apt-repo` может отсутствовать; источники всё равно
задаются файлами `/etc/apt/sources.list` и `/etc/apt/sources.list.d/*.list`.
Сами `pkglist.*` являются бинарными header-list файлами RPM, поэтому искать в
них обычным `grep` не следует. Для доступа к кэшу предназначен `apt-cache`:
| Задача | Команда |
|---|---|
| Поиск по имени и описанию | `apt-cache search REGEX` |
| Перечень имён | `apt-cache pkgnames` |
| Выбранная версия и приоритеты источников | `apt-cache policy PACKAGE` |
| Полная доступная запись, включая версии зависимостей | `apt-cache show PACKAGE` |
| Граф разрешения зависимостей | `apt-cache depends PACKAGE` |
| Обратные зависимости | `apt-cache whatdepends PACKAGE` |
| Низкоуровневое представление версий и зависимостей | `apt-cache showpkg PACKAGE` |
| Метаданные исходного пакета | `apt-cache showsrc PACKAGE` |
| Все доступные записи репозиториев | `apt-cache dumpavail` |
| Проверка плана установки без изменений | `apt-get -s install PACKAGE` |
| Только скачать бинарный пакет и зависимости | `apt-get -d install PACKAGE` |
| Скачать исходники | `apt-get source PACKAGE` |
`apt-cache depends` удобен для просмотра найденных провайдеров, но в его
обычном выводе set-версия зависимости теряется. Для полных set-строк нужны
`apt-cache show`, `apt-cache showpkg`, скачанный RPM или RDB API.
## Set-строки пакета из APT-репозитория
### Способ 1: прямо из локального кэша APT
Это самый короткий способ, установка и скачивание RPM не нужны:
```sh
pkg=libdw
# Сначала проверить, какую версию и из какого источника выбрал APT.
apt-cache policy "$pkg"
# Полные поля с set-Requires и set-Provides.
apt-cache show "$pkg" |
sed -n -E '/^(Pre-Depends|Depends|Provides):/p' |
grep -F 'set:'
```
В проверенном p11 поля имели вид:
```text
Depends: ... liblzma.so.5()(64bit) (>= set:kiyIz7cr3p0), ...
Provides: libdw.so.1()(64bit) (= set:ldm5ZH1pjPBqjZ9bdJbLDnKGbC1hkJSX...)
```
`Depends` и `Pre-Depends` соответствуют требованиям пакета, `Provides` — тому,
что пакет предоставляет. `apt-cache show` может вывести несколько записей,
если в подключённых источниках доступны разные версии; выбранный кандидат
виден в `apt-cache policy`.
Для выгрузки всего подключённого репозитория можно использовать:
```sh
apt-cache dumpavail >available.txt
grep -E '^(Pre-Depends|Depends|Provides):.*set:' available.txt
```
В отличие от HTML-страниц `packages.altlinux.org`, кэш APT содержит полные
set-строки без визуального многоточия.
### Способ 2: скачать RPM через APT и запросить его заголовок
Это наиболее точный локальный способ: выводятся исходные массивы тегов RPM и
их удобно преобразовать в TSV. `apt-get -d` скачивает также отсутствующие
зависимости, но ничего не устанавливает.
```sh
pkg=strace
cache=$(mktemp -d)
trap 'rm -rf "$cache"' EXIT
mkdir -p "$cache/partial"
apt-get -y -d \
-o "Dir::Cache::archives=$cache" \
install "$pkg"
rpmfile=$(find "$cache" -maxdepth 1 -type f \
-name "${pkg}_*.rpm" -print -quit)
```
Set-Requires выбранного пакета:
```sh
rpmquery -p --qf \
'[%{REQUIRENAME}\t%{REQUIREFLAGS:depflags}\t%{REQUIREVERSION}\n]' \
"$rpmfile" |
awk -F '\t' '$3 ~ /^set:/'
```
Set-Provides выбранного пакета:
```sh
rpmquery -p --qf \
'[%{PROVIDENAME}\t%{PROVIDEFLAGS:depflags}\t%{PROVIDEVERSION}\n]' \
"$rpmfile" |
awk -F '\t' '$3 ~ /^set:/'
```
Для `strace-7.0-alt1.x86_64.rpm` в p11 получены два требования:
```text
libdw.so.1()(64bit) >= set:lgHyMNBkvSTjjY0en8Yi3vFojgjVM7jRsRF4zCFBzNVmhhGF
libselinux.so.1()(64bit) >= set:liZ0N709Wr2dbKPs2
```
Set-Provides у этого пакета нет: исполняемый `strace` требует библиотеки, но не
экспортирует собственную разделяемую библиотеку. У скачанного вместе с ним
пакета `libdw` присутствует длинный Provide
`libdw.so.1()(64bit) = set:...`.
Если пакет уже установлен и APT не скачивает его повторно, его заголовок можно
прочитать напрямую командой `rpmquery PACKAGE`; для гарантированного получения
конкретного репозиторного RPM удобнее использовать RDB API.
### Способ 3: структурированные данные RDB API
RDB удобен для автоматизации, другой ветки/архитектуры и больших выборок. Он
возвращает полные строки и явно различает `provide`, `require` и `conflict`:
```sh
branch=p11
arch=x86_64
pkg=libdw
hash=$(curl -fsS \
"https://rdb.altlinux.org/api/site/pkghash_by_binary_name?branch=$branch&name=$pkg&arch=$arch" |
jq -r '.pkghash')
curl -fsS \
"https://rdb.altlinux.org/api/dependencies/binary_package_dependencies/$hash" |
jq -r '
.dependencies[]
| select(
(.type == "provide" or .type == "require") and
(.version | startswith("set:"))
)
| [.type, .name, .version]
| @tsv
'
```
Проверенный результат для `libdw` содержит один set-Provide и четыре
set-Requires. Поле `flag_decoded` того же JSON позволяет восстановить оператор:
`RPMSENSE_EQUAL` для обычного Provide и
`RPMSENSE_GREATER` + `RPMSENSE_EQUAL` для `>=` Require.
Через RDB можно также получить URL самого RPM:
```sh
curl -fsS \
"https://rdb.altlinux.org/api/site/package_downloads_bin/$hash?branch=$branch&arch=$arch" |
jq -r '.downloads[].packages[].url'
```
## Основные запросы
### Пакет и его метаданные
```sh
# Версия установленного пакета в стандартном формате NVRA.
rpmquery rpm
# Подробная информация. В ALT вывод включает DistTag.
rpmquery -i rpm
rpmquery --info rpm
# Стабильный формат для скрипта.
rpmquery --qf '%{NAME}|%{EPOCHNUM}|%{VERSION}|%{RELEASE}|%{DISTTAG}|%{ARCH}\n' rpm
# Доступные имена тегов заголовка.
rpmquery --querytags
```
В `rpmquery` короткая опция `-i` означает `--info`, а не установку пакета. Это
ALT-алиас из `rpmpopt`; он появился в changelog RPM в версии
`4.13.0-alt7`. Для установки используется другой режим программы `rpm`, но в
обычной работе с ALT зависимости и репозитории следует поручать APT.
### Файлы
```sh
# Какому установленному пакету принадлежит файл.
rpmquery -f /usr/bin/rpmquery
# Все файлы пакета.
rpmquery -l rpm
# Только конфигурационные или документационные файлы.
rpmquery -c rpm
rpmquery -d rpm
# Пакет из файла без установки.
rpmquery -p ./package.rpm
rpmquery -lp ./package.rpm
rpmquery -ip ./package.rpm
```
Для аргумента-пути `--whatprovides` сначала обращается к индексу установленных
файлов, затем к индексу capability. Поэтому для существующего установленного
файла эти команды обычно совпадают:
```sh
rpmquery -f /usr/bin/rpmquery
rpmquery --whatprovides /usr/bin/rpmquery
```
### Provides, Requires и обратные запросы
```sh
# Что объявляет один пакет.
rpmquery --provides rpm
rpmquery --requires rpm
rpmquery -R rpm
# Какие установленные пакеты объявляют capability или требуют его.
rpmquery --whatprovides 'libpopt.so.0()(64bit)'
rpmquery --whatrequires 'libpopt.so.0()(64bit)'
```
`--provides`, `--requires`, `--info`, `--last`, `--scripts` и ряд других
удобных режимов реализованы как `popt`-алиасы над `--queryformat`, а не как
отдельные алгоритмы запросов.
## Формат запросов для автоматической обработки
Стандартный вывод удобен человеку, но надёжнее не разбирать его пробелами.
Массивы тегов RPM обходятся форматом в квадратных скобках. Чтобы внутри такого
итератора повторять скалярный тег для каждого элемента массива, знак `=` ставят
внутри имени тега: `%{=NAME}`.
### Все set-Requires в TSV
```sh
LC_ALL=C rpmquery -a --qf \
'[%{=NAME}\t%{REQUIRENAME}\t%{REQUIREFLAGS:depflags}\t%{REQUIREVERSION}\n]' |
awk -F '\t' '$4 ~ /^set:/'
```
Поля: пакет, имя capability, оператор, версия зависимости.
Пример:
```text
rpm libpopt.so.0()(64bit) >= set:jgtcU6BLBccTnteGxrE0
```
### Все set-Provides в TSV
```sh
LC_ALL=C rpmquery -a --qf \
'[%{=NAME}\t%{PROVIDENAME}\t%{PROVIDEFLAGS:depflags}\t%{PROVIDEVERSION}\n]' |
awk -F '\t' '$4 ~ /^set:/'
```
Пример:
```text
libpopt libpopt.so.0()(64bit) = set:jdtcJcAdqxTmPJUyuYcVIbLNhqmj...
```
Это предпочтительнее `grep` по человекочитаемому выводу, если нужны имена
пакетов, операторы или однозначное разделение полей.
## Что `rpmquery` делает и не делает с `set:version`
### Показывает сохранённые зависимости
`rpmquery --requires` и `rpmquery --provides` извлекают из заголовков пакетов
имя capability, флаги отношения и строку версии. Они не декодируют `set:` и не
показывают исходные ELF-символы.
Для установленного `rpm` в p11 наблюдалось:
```text
libpopt.so.0()(64bit) >= set:jgtcU6BLBccTnteGxrE0
```
Установленный провайдер находился по одному имени capability:
```sh
rpmquery --whatprovides 'libpopt.so.0()(64bit)'
# libpopt-1.18-alt1.x86_64
```
### Не сравнивает выражение, переданное в `--whatprovides`
Передача полного выражения не запускает `rpmsetcmp()`:
```sh
rpmquery --whatprovides \
'libpopt.so.0()(64bit) >= set:jgtcU6BLBccTnteGxrE0'
```
Проверенный результат:
```text
no package provides libpopt.so.0()(64bit) >= set:jgtcU6BLBccTnteGxrE0
```
Для непутевого аргумента `--whatprovides` делает точный поиск по индексу
`PROVIDENAME`. Поэтому сначала нужно искать провайдера по имени capability, а
его set-строку получать отдельным запросом:
```sh
name='libpopt.so.0()(64bit)'
provider=$(rpmquery --whatprovides "$name" | head -n1)
rpmquery --qf \
'[%{PROVIDENAME}\t%{PROVIDEFLAGS:depflags}\t%{PROVIDEVERSION}\n]' \
"$provider" |
grep -F "$name"
```
Настоящее сравнение двух `set:`-версий происходит в `rpmdsCompareEVR()` при
проверке совместимости зависимостей. Если обе версии начинаются с `set:`, код
вызывает `rpmsetcmp()`; если `set:` имеет только одна сторона, зависимости не
пересекаются. Следовательно, `rpmquery` полезен для извлечения корпуса строк,
но не является CLI для непосредственного сравнения двух наборов.
## Как `rpmquery` используется при сборке самого ALT RPM
В `alt/rpm.spec` ветки p11 после первой сборки выполняется:
```sh
rpmquery -a --provides | fgrep '= set:' | sort >P
rpmquery -a --requires | fgrep '= set:' | sort >R
join -o 1.3,2.3 P R | shuf >setcmp-data
time ./setcmp <setcmp-data >/dev/null
```
Смысл конвейера:
1. Из локальной базы сборочного окружения собираются все set-Provides и
set-Requires.
2. Строка поиска `= set:` захватывает как Provides с `= set:`, так и Requires с
`>= set:`, потому что вторая строка тоже содержит подстроку `= set:`.
3. После сортировки `join` соединяет файлы по первому полю — имени capability.
4. `-o 1.3,2.3` оставляет только третьи поля: set провайдера и set требования.
5. Полученные реальные пары подаются в `tools/setcmp` для замера реализации
`rpmsetcmp()` и профиль-управляемой пересборки `lib/set.c`.
В обновлённом минимальном контейнере p11 точный конвейер дал:
```text
P: 75 строк
R: 109 строк, из них 13 полных дублей
setcmp-data: 109 пар
```
Эти числа описывают только состав минимального контейнера, а не весь p11 или
Sisyphus. Также конвейер spec не сохраняет имя capability и пакет-владелец;
для исследовательского корпуса удобнее TSV-форматы выше.
Современная замена устаревающего `fgrep` без изменения смысла:
```sh
grep -F '= set:'
```
## Практические ограничения
- `rpmquery -a` означает все **установленные** пакеты, а не весь репозиторий.
- `--whatprovides` и `--whatrequires` ищут заголовки по имени capability; они
не принимают полноценное выражение зависимости как запрос сравнения.
- Несколько установленных версий или провайдеров могут дать несколько строк.
- Set-строки крупных библиотек очень длинные; лучше писать полный вывод в файл,
а в терминале показывать только статистику или начало строки.
- Для скриптов следует фиксировать `LC_ALL=C` и использовать `--queryformat`.
- `--requires`/`--provides` отражают метаданные RPM, а не текущее содержимое ELF
на диске. Для восстановления исходных символов нужны `nm`, `readelf` и
генераторы автозависимостей rpm-build.
- При запросе другой системы безопаснее явно использовать `--root`; `--dbpath`
меняет только путь к БД и требует внимательности к относительным путям.
- ALT предоставляет `--nowait-lock` как popt-алиас, отключающий ожидание
блокировки БД. Его стоит применять только когда отказ предпочтительнее
ожидания, а не как способ обходить активную транзакцию RPM/APT.
## Источники
1. [Документация платформы ALT 10.1: утилита RPM](https://docs.altlinux.org/ru-RU/platform/10.1/html/alt-platform/utilita_komandnoj_stroki_rpm.html) — RPM работает с файлами, пакетами, зависимостями и собственной БД, но не знает о репозиториях.
2. [Настройка списка репозиториев APT в ALT](https://docs.altlinux.org/ru-RU/platform/10.1/html/alt-platform/nastrojka_spiska_repozitoriev_apt.html).
3. [Пакет `rpm` в p11](https://packages.altlinux.org/en/p11/srpms/rpm/) и [specfile](https://packages.altlinux.org/en/p11/srpms/rpm/specfiles/).
4. [RDB: текущий бинарный пакет `rpm` для p11/x86_64](https://rdb.altlinux.org/api/site/pkghash_by_binary_name?branch=p11&name=rpm&arch=x86_64).
5. [`rpmqv.c`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=rpmqv.c;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — выбор режима Query по имени `rpmquery`/`rpmq`.
6. [`rpmpopt.in`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=rpmpopt.in;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — ALT-алиасы `--requires`, `--provides`, `--info`, `-i`, `--nowait-lock`.
7. [`lib/query.c`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=lib/query.c;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — индексы для `--whatprovides`, запросы файлов и формат `%{nvra}` по умолчанию.
8. [`lib/rpmds.c`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=lib/rpmds.c;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — вызов `rpmsetcmp()` при сравнении двух set-версий.
9. [`alt/rpm.spec`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=alt/rpm.spec;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — построение `setcmp-data` через `rpmquery`.
10. [Официальный контейнер ALT](https://hub.docker.com/_/alt) — среда воспроизведения `alt:p11`.
-80
View File
@@ -1,80 +0,0 @@
rg rpmsetcmp:
```
tools/setcmp.c
12-{
13: int cmp = rpmsetcmp(s1, s2);
14- switch (cmp) {
alt/rpm.spec
709-_ Mon Nov 25 2019 Andrew Savchenko <bircoph@altlinux.org> 4.13.0.1-alt15
710:- Support rpmsetcmp profiling on E2K.
711-
--
713-- Added triggers circumvention for packagekit offline update (by Aleksei Nikiforov).
714:- Imported rpmsetcmp optimization from rpm-build.
715-
--
1094-- set.c: Increased cache size from 160 to 256 slots, 75 percent hit ratio.
1095:- set.c: Implemented 4-byte and 8-byte steppers for rpmsetcmp main loop.
1096-
--
1314-_ Mon Sep 20 2010 Alexey Tourbin <at@altlinux.ru> 4.0.4-alt98.47
1315:- set.c (rpmsetcmp): Fixed check for set2 decoding error.
1316-- brp-cleanup: Updated for /usr/lib64/perl5 and /usr/share/perl5.
lib/rpmds.c
1087- if (aset && bset) {
1088: sense = rpmsetcmp(AEVR, BEVR);
1089- if (sense < -1) {
---
rg set_new:
none
---
rg set_add
none
---
rg set_fini:
none
---
rg set_free:
none
```
---
rg setcmp:
```
Makefile.am
218-
219:noinst_PROGRAMS = setcmp
220:setcmp_SOURCES = tools/setcmp.c
221:setcmp_LDADD = lib/librpm.la
222-
alt/rpm.spec
311-rpmquery -a --requires |fgrep '= set:' |sort >R
312:join -o 1.3,2.3 P R |shuf >setcmp-data
313:time ./setcmp <setcmp-data >/dev/null
314-rm lib/set.lo lib/librpm.la
--
318-%if_with profile
319:time ./setcmp <setcmp-data >/dev/null
320-rm lib/set.lo lib/librpm.la
321-%make_build -C lib set.lo librpm.la CFLAGS="$set_c_cflags -fprofile-generate"
322:./setcmp <setcmp-data >/dev/null
323-%ifnarch %e2k
--
332-%make_build
333:time ./setcmp <setcmp-data >/dev/null
334-
```
-99
View File
@@ -1,99 +0,0 @@
rg rpmsetcmp:
```
tools/setcmp.c
12-{
13: int cmp = rpmsetcmp(s1, s2);
14- switch (cmp) {
lib/depends.c
124- if (aset && bset) {
125: sense = rpmsetcmp(AEVR, BEVR);
126- if (sense < -1) {
rpm-4_0.spec
1231-- set.c: Increased cache size from 160 to 256 slots, 75 percent hit ratio.
1232:- set.c: Implemented 4-byte and 8-byte steppers for rpmsetcmp main loop.
1233-
--
1451-* Mon Sep 20 2010 Alexey Tourbin <at@altlinux.ru> 4.0.4-alt98.47
1452:- set.c (rpmsetcmp): Fixed check for set2 decoding error.
1453-- brp-cleanup: Updated for /usr/lib64/perl5 and /usr/share/perl5.
build/reqprov.c
175- if (aset && bset) {
176: sense = rpmsetcmp(Aevr, Bevr);
177- if (sense < -1)
```
---
rg set_new:
```
tools/mkset.c
11- assert(bpp <= 32);
12: struct set *set = set_new();
13- char *line = NULL;
```
---
rg set_add:
```
tools/mkset.c
21- continue;
22: set_add(set, line);
23- added++;
```
---
rg set_fini:
```
tools/mkset.c
25- assert(added > 0);
26: const char *str = set_fini(set, bpp);
27- assert(str);
```
---
rg set_free:
none
---
rg mkset:
```
rpm-4_0.spec
431-# set-version helpers
432:%rpmattr %_rpmlibdir/mkset
433-%rpmattr %_rpmlibdir/setcmp
--
1458-- rpmlibprov.c: Added rpmlib(SetVersions) feature.
1459:- %_rpmlibdir/mkset: Command-line helper for making set-versions.
1460-- lib.prov: Implemented soname set-versioning with exported symbols.
tools/Makefile.am
37- relative \
38: mkset \
39- setcmp \
--
62-
63:mkset_SOURCES = mkset.c
64-setcmp_SOURCES = setcmp.c
autodeps/lib.prov.in
112- Info "$f: $n symbols, $bpp bpp"
113: set=$(printf '%s\n' "$sym" |"${RPMB_TOOLS_DIR-@RPMCONFIGDIR@}/mkset" "$bpp")
114- printf '%s = %s\n' "$provname" "$set"
autodeps/lib.req.in
306- #printf '%s\n' "$reqsym" |LC_ALL=C sort -c -u
307: set=$(printf '%s\n' "$reqsym" |"${RPMB_TOOLS_DIR-@RPMCONFIGDIR@}/mkset" "$bpp")
308- printf '%s >= %s\n' "$dep" "$set"
```
+586
View File
@@ -0,0 +1,586 @@
# Реализация set:version в `set9.c`
Документ описывает экспериментальную реализацию [`reimplement/set9.c`](../reimplement/set9.c).
Общий смысл механизма set:version и устройство исходного [`set.c`](../set.c) разобраны в
[`set:version_ru.md`](set:version_ru.md). Здесь основное внимание уделено отличиям `set9.c`:
потоковому кодированию и декодированию, сортировке, двум LRU-кэшам и
новому алгоритму сравнения отсортированных множеств.
## Назначение
ALT RPM записывает наборы ELF-символов в версии зависимостей вида:
```text
libfoo.so.X()(64bit) = set:<encoded-set>
libfoo.so.X()(64bit) >= set:<encoded-set>
```
`Provides` содержит хэши предоставляемых символов, а `Requires` — хэши символов, требуемых
от конкретной библиотеки. `rpmsetcmp()` сравнивает декодированные множества по включению,
а не как обычные RPM-версии.
`set9.c` сохраняет формат строк и Jenkins OAAT из исходной реализации. Поэтому его задача —
изменить внутреннее представление и горячие пути, не меняя смысл корректных set-строк.
Совпадение формата не устраняет фундаментальную вероятность коллизий: сравниваются усечённые
32-битные хэши, а не исходные имена символов.
## Основные отличия от исходного `set.c`
| Область | Исходный `set.c` | `set9.c` |
| ------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------- |
| Хранение имён | отдельный `xstrdup()` для каждого имени | общая растущая строковая арена и массив смещений |
| Сортировка | всегда `qsort()` | `qsort()` для `< 128` элементов, LSD radix sort для остальных |
| Кодирование | отдельные массивы delta, битов и Base62 | один поток `hash -> delta -> Golomb-Rice -> Base62` |
| Декодирование | сложный табличный декодер пар символов | простой потоковый декодер с 64-битным аккумулятором |
| Кэш | один кэш первого операнда на 256 записей | два независимых кэша по 512 записей, по одному на каждый операнд |
| Поиск в кэше | линейный просмотр и `memmove()` | хэш-бакеты и двусвязный LRU со сменой позиции за `O(1)` |
| Нормализация `bpp` | выполняется для каждого сравнения | выполняется при промахе кэша и сохраняется в записи кэша |
| Сравнение | флаги `ge`/`le`, sentinel-элементы и макросы прыжков | проверка мощности, `memcmp()` и `sorted_subset()` |
| Освобождение `struct set` | сама структура не освобождается | освобождаются арена, массив символов и сама структура |
## Публичный API
Как и исходная реализация, файл предоставляет пять функций:
```c
int rpmsetcmp(const char *set1, const char *set2);
struct set *set_new(void);
void set_add(struct set *set, const char *sym);
const char *set_fini(struct set *set, int bpp);
struct set *set_free(struct set *set);
```
### `set_new()`
Создаёт пустой `struct set`. Все счётчики и ёмкости устанавливаются в ноль, указатели — в
`NULL`.
### `set_add()`
Добавляет копию строки символа во внутреннюю строковую арену:
1. массив `symbols_v` увеличивается блоками по 1024 элемента;
2. строковая арена при первом выделении получает 4096 байт;
3. при нехватке места ёмкость арены удваивается, пока не вместит новую строку;
4. в `symbols_v` сохраняются смещение строки в арене и нулевое начальное значение хэша.
В отличие от хранения отдельных указателей, перемещение арены через `xrealloc()` не делает
записи массива недействительными: в них находятся смещения, а не адреса строк.
### `set_fini()`
Хэширует, сортирует и кодирует добавленные имена. Возвращаемая строка выделена через
`xstrdup()` и **не содержит** префикс `set:`. Префикс добавляет вызывающий код, например
`mkset`.
В `set9.c` контракт проверяется через `assert()`:
```text
set != NULL
set->cnt > 0
10 <= bpp <= 32
```
Это отличается от исходного `set.c`, где пустое множество или недопустимый `bpp` приводят к
`NULL`. При сборке с `NDEBUG` проверки `assert()` исчезают, поэтому передавать некорректные
аргументы нельзя.
### `set_free()`
Освобождает строковую арену, массив `symbols_v` и сам `struct set`, затем возвращает `NULL`.
Типичный вызов:
```c
set = set_free(set);
```
### `rpmsetcmp()`
Сравнивает две set-строки. Префикс `set:` у каждого операнда необязателен.
Результаты совместимы с исходным API:
| Код | Значение |
| ---: | ---------------------------------------------------- |
| `1` | первое множество строго содержит второе |
| `0` | множества равны |
| `-1` | первое множество строго содержится во втором |
| `-2` | множества несравнимы по включению |
| `-3` | ошибка метаданных или декодирования первого операнда |
| `-4` | ошибка метаданных или декодирования второго операнда |
В типичном RPM-вызове первым операндом остаётся `Provides`, вторым — `Requires`.
## Формат set-строки
После необязательного `set:` строка имеет структуру:
```text
<bpp><Mshift><payload>
```
Первые два символа кодируют числа формулой:
```c
value = character + 7 - 'a';
character = value - 7 + 'a';
```
Ограничения:
```text
10 <= bpp <= 32
7 <= Mshift <= 31
Mshift < bpp
payload не пуст
```
`payload` использует алфавит:
```text
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
```
Его числовые значения расположены как `0..9`, `a..z`, `A..Z`. Символ `Z` имеет значение
61 и одновременно служит escape-маркером для исходных шестибитных значений 61, 62 и 63.
## Внутреннее представление создаваемого множества
```c
struct set {
size_t cnt;
size_t symbols_cap;
size_t strings_len;
size_t strings_cap;
char *strings;
struct symbols {
size_t offset;
unsigned hash;
} *symbols_v;
};
```
Здесь:
- `cnt` — число добавленных имён;
- `symbols_cap` — ёмкость массива метаданных;
- `strings_len` и `strings_cap` — занятая и выделенная части строковой арены;
- `strings` — последовательность NUL-терминированных имён;
- `offset` — начало конкретного имени относительно `strings`;
- `hash` — хэш, вычисляемый только при `set_fini()`.
Такое представление сокращает число отдельных выделений памяти при построении больших наборов.
## Работа `set_fini()`
Полный путь выглядит так:
```text
строковая арена и offsets
| Jenkins OAAT + mask(bpp)
v
массив struct symbols с хэшами
| qsort или LSD radix sort
v
отсортированные хэши
| предупреждения о коллизиях + удаление повторов
v
массив уникальных хэшей
| потоковый delta + Golomb-Rice + Base62
v
строка <bpp><Mshift><payload>
```
### Jenkins OAAT и усечение
Для каждого имени вычисляется тот же Jenkins one-at-a-time hash, что и в исходном файле.
Начальное состояние равно `0x9e3779b9`. Результат ограничивается младшими `bpp` битами:
```c
unsigned mask = (bpp < 32) ? (1u << bpp) - 1 : ~0u;
hash_value = hash(symbol) & mask;
```
### `sort_symbols()`
Для малых наборов (`count < 128`) используется `qsort()`. Для больших наборов применяется
стабильная LSD radix sort по байтам хэша:
1. число проходов равно `ceil(bpp / 8)`;
2. на каждом проходе строятся 256 счётчиков/смещений;
3. элементы стабильно распределяются по текущему байту;
4. источник и приёмник меняются местами;
5. если итог оказался во временном массиве, он копируется назад.
Поскольку перед сортировкой хэш уже ограничен `bpp` битами, обработки
`ceil(bpp / 8)` байтов достаточно. Временный массив размером `count` размещается на стеке.
### Коллизии и повторяющиеся символы
После сортировки соседние равные хэши проверяются попарно. Для разных строк печатается:
```text
warning: hash collision: <left> <right>
```
Две одинаковые строки предупреждения не создают. Затем все одинаковые **хэш-значения**
схлопываются в одно. Поэтому настоящая хэш-коллизия после предупреждения всё равно становится
одним элементом кодируемого множества — это свойство исходного формата.
### Выбор `Mshift`
Параметр Golomb-Rice вычисляется как:
```text
Mshift = bpp - floor(log2(count)) - 1
```
После этого значение ограничивается диапазоном `7..31` и проверяется условие
`Mshift < bpp`. Идея та же, что в исходной реализации: при примерно равномерном распределении
хэшей средняя дельта близка к `2^bpp / count`.
### Потоковый `encode_set()`
Исходный `set.c` сначала создаёт массив дельт, затем массив по одному байту на бит и лишь потом
Base62-строку. В `set9.c` эти стадии объединены.
Для каждого отсортированного хэша вычисляется:
```c
unsigned delta = current - previous;
unsigned q = delta >> Mshift;
unsigned r = delta & ((1u << Mshift) - 1);
```
В `encode_writer` последовательно добавляются:
```text
q нулевых битов
1 — разделитель
Mshift младших битов r
```
Все биты идут младшими вперёд (LSB-first), как и в исходном формате.
### `struct encode_writer`
"Писатель" содержит:
- `uint64_t bits` — накопленные ещё не выведенные биты;
- `filled` — число занятых битов;
- `escaped` — ожидание второй половины escape-пары;
- `pending_high` — старшие два бита значения 61, 62 или 63;
- `output` — текущую позицию в выходной строке.
`encode_writer_put()` добавляет обычное поле битов. `encode_writer_zeros()` добавляет длинную
unary-последовательность нулей блоками не более 56 бит, чтобы аккумулятор оставался в пределах
`uint64_t`. `encode_writer_flush()` выводит накопленные Base62-цифры.
В обычном состоянии потребляется шесть потоковых битов. Если значение равно 61, 62 или 63:
1. выводится `Z`;
2. различие между 61/62/63 сохраняется как два старших бита следующего символа;
3. из потока для следующего символа берутся только четыре бита.
При завершении неполный символ дополняется нулями. После payload записывается `\0`.
## Метаданные декодируемой строки
Для декодирования используется `struct set_meta`:
```c
struct set_meta {
const char *str;
const char *payload;
size_t len;
size_t payload_len;
int bpp;
int Mshift;
int bit_capacity;
int value_capacity;
};
```
Подготовка разделена на две части.
### `set_meta_init()` — быстрая проверка
Функция читает только начало строки:
1. проверяет наличие двух метасимволов и хотя бы одного символа payload;
2. декодирует и проверяет `bpp`;
3. декодирует и проверяет `Mshift`;
4. сохраняет `str`, `payload`, `bpp` и `Mshift`.
На этой стадии `strlen()` не вызывается. Это позволяет сначала выполнить дешёвую проверку и
поискать уже готовую запись в кэше.
### `set_meta_fini()` — вычисление размеров
Функция вызывается только при промахе кэша:
```text
len = strlen(str)
payload_len = len - 2
bit_capacity = payload_len * 6
value_capacity = bit_capacity / (Mshift + 1)
```
`bit_capacity` является верхней границей: обычный символ даёт шесть битов, а два символа
escape-пары дают десять битов, то есть не больше двенадцати. Каждое Golomb-Rice-значение
требует как минимум `Mshift + 1` бит. Если места нет даже для одного значения,
строка отклоняется.
## Потоковое декодирование
Путь декодирования объединяет три логических стадии:
```text
Base62 payload
| decode_chunk()
v
6- или 10-битные блоки
| q-state + r-state
v
Golomb-Rice delta
| previous += delta
v
отсортированные хэши
```
Отдельные массивы битов и дельт не создаются.
### Таблица `char_to_num[]`
Таблица из 256 элементов классифицирует любой байт:
- `0..60` — обычная Base62-цифра;
- `61``Z`;
- `0xff` — конец строки;
- `0xee` — недопустимый символ.
Индекс берётся как `unsigned char`, поэтому байты с установленным старшим битом тоже безопасно
попадают в диапазон таблицы и отклоняются как недопустимые.
### `decode_chunk()`
Обычный символ со значением меньше 61 возвращает шесть битов. При `Z` функция читает следующий
символ и:
1. отклоняет конец строки сразу после `Z`;
2. отклоняет не-Base62 символ;
3. отклоняет комбинацию старших битов `11`, которая создала бы новый `Z`;
4. собирает один 10-битный блок из значения 61/62/63 и четырёх младших битов второго символа.
Таким образом escape-пара сразу преобразуется в исходные десять потоковых битов.
### `decode_set()`
Декодер хранит ещё не обработанные биты в `uint64_t bits`, а их количество — в `filled`.
Для каждого значения он проходит два состояния.
#### Unary-часть `q`
Если аккумулятор пуст, загружается следующий блок. Полностью нулевой блок целиком прибавляется
к `q`. Иначе `__builtin_ctzll(bits)` находит число нулей до разделительной единицы. Нули и сама
единица удаляются из аккумулятора.
#### Остаток `r`
Пока доступно меньше `Mshift` битов, загружаются следующие блоки. Затем младшие `Mshift` битов
образуют остаток, а дельта восстанавливается как:
```c
unsigned delta = (q << Mshift) | r;
```
Дельта сразу превращается в исходный хэш:
```c
previous += delta;
hash_arr[count++] = previous;
```
#### Завершение строки
Конец payload допустим в состоянии чтения `q`, если осталось не более пяти нулевых битов
Base62-дополнения. Более длинный нулевой хвост даёт внутреннюю ошибку `-10`.
Конец строки во время набора `Mshift` битов остатка означает незавершённое значение и даёт
внутреннюю ошибку `-11`. Ошибки `decode_chunk()` также возвращаются вверх. Публичный
`rpmsetcmp()` скрывает конкретный внутренний код и преобразует его в `-3` или `-4` в зависимости
от операнда.
## Нормализация точности: `downsample_set()`
Строки с разным `bpp` нельзя сравнивать напрямую. Обе стороны приводятся к:
```text
target_bpp = min(bpp1, bpp2)
```
Один вызов `downsample_set()` уменьшает точность с `bpp + 1` до `bpp`:
1. маска равна `(1u << bpp) - 1`;
2. бинарным поиском находится первый элемент с удаляемым старшим битом;
3. исходный отсортированный массив делится на две отсортированные половины;
4. у второй половины удаляется старший бит;
5. половины сливаются как два отсортированных массива;
6. появившиеся после усечения дубликаты удаляются.
Если требуется убрать несколько битов, операция повторяется по одному биту. В `set9.c` результат
нормализации сохраняется в кэше, поэтому та же строка при том же `target_bpp` не проходит этот
цикл повторно.
## Два кэша декодированных множеств
`cache_decode_set()` обслуживает два независимых кэша:
```text
cache_id = 0 — первый операнд
cache_id = 1 — второй операнд
```
Каждый кэш содержит до `CACHE_SIZE = 512` записей и 1024 бакета. Разделение не даёт потоку
часто повторяющихся `Requires` вытеснять кэш `Provides` и наоборот, хотя реальный эффект зависит
от порядка вызовов решателя зависимостей.
### Ключ записи
Для быстрого предварительного отбора вычисляется fingerprint:
```c
str[0] | (str[2] << 8) | (str[3] << 16)
```
Он смешивается с `target_bpp`, после чего выбирается бакет. Совпадения fingerprint недостаточно:
успешный hit требует также равного `target_bpp` и полного `strcmp()` строки. Поэтому коллизия
fingerprint влияет только на длину цепочки, но не подменяет декодированное множество.
`target_bpp` входит в ключ, поскольку одна и та же исходная строка может участвовать в
сравнениях с операндами разной точности и, следовательно, иметь разные нормализованные массивы.
### Устройство записи
Одним `xmalloc()` выделяются:
```text
struct cache_ent
массив unsigned для хэшей
копия исходной строки
```
Запись хранит fingerprint, исходную строку, число элементов и `target_bpp`.
### Промах
При промахе происходят следующие шаги:
1. `set_meta_fini()` вычисляет длины и верхнюю границу числа значений;
2. выделяется новая запись;
3. `decode_set()` создаёт отсортированный массив хэшей;
4. при необходимости массив поэтапно уменьшается до `target_bpp`;
5. строка и метаданные копируются в запись;
6. запись добавляется в хэш-бакет и в начало логического LRU.
### LRU
Все записи одного кэша образуют двусвязный список от `oldest` к `newest`. При hit запись
отцепляется от текущего места и становится `newest`; это не требует сдвига массива записей.
При заполненном кэше удаляется `oldest`, включая поиск ссылки на него в соответствующей цепочке
бакета.
## Сравнение в `rpmsetcmp()`
Последовательность работы:
1. снять `set:`, если он присутствует;
2. выполнить `set_meta_init()` для обеих строк;
3. выбрать минимальный `target_bpp`;
4. получить первый операнд из кэша `0` или декодировать его;
5. если строки полностью одинаковы, вернуть `0`;
6. получить второй операнд из кэша `1` или декодировать его;
7. сравнить мощности и содержимое нормализованных массивов.
Быстрый возврат для одинаковых строк выполняется после полноценного декодирования первого
операнда. Поэтому равенство строк не позволяет принять некорректный payload без проверки.
Метаданные второго операнда к этому моменту также уже прошли начальную проверку.
### Равная мощность
Нормализованные массивы отсортированы и не содержат дубликатов. Если `cnt1 == cnt2`, два
множества могут быть либо равны, либо несравнимы. Поэтому достаточно:
```c
memcmp(hash_arr1, hash_arr2, cnt1 * sizeof(unsigned))
```
Совпадение даёт `0`, различие — `-2`.
### Разная мощность
Множество с большим числом уникальных хэшей не может быть строгим подмножеством меньшего.
Поэтому проверяется только одно возможное направление:
```text
cnt1 > cnt2: set2 ⊆ set1 ? 1 : -2
cnt1 < cnt2: set1 ⊆ set2 ? -1 : -2
```
Саму проверку выполняет `sorted_subset(small, large)`.
### `sorted_subset()`
Алгоритм выбирается по отношению размеров:
```c
jump = large_count / small_count;
```
Если `jump < 4`, используется обычное линейное слияние: указатель большого массива двигается
до текущего элемента малого. Для близких по размеру множеств это последовательный проход с
хорошей локальностью.
Если `jump >= 4`, вызывается `step_lower_bound()`. Функция сначала делает шаги примерно на
среднее расстояние между искомыми элементами, затем делит шаг пополам, пока не найдёт первый
элемент, который не меньше искомого. Это позволяет пропускать части большого массива при
разреженном малом множестве без sentinel-значений и выхода за границы.
При первом отсутствующем элементе `sorted_subset()` возвращает ложь.
## `SELF_TEST`
При `SELF_TEST` компилируется `main()`, который проверяет только публичный API на коротких
наборах:
1. строгое надмножество возвращает `1`;
2. равные множества возвращают `0`;
3. строгое подмножество возвращает `-1`;
4. пересекающиеся несравнимые множества возвращают `-2`;
5. выделенные строки и оба объекта освобождаются.
В отличие от исходного `set.c`, встроенный тест является smoke-тестом, а не полной проверкой совместимости.
## "Карта функций"
| Функция | Роль |
| ------------------------ | -------------------------------------------- |
| `set_new()` | создание контейнера символов |
| `set_add()` | добавление имени в строковую арену |
| `set_free()` | освобождение контейнера |
| `hash()` | Jenkins OAAT |
| `sort_symbols()` | выбор `qsort()` или radix sort |
| `encode_golomb_Mshift()` | вычисление параметра Golomb-Rice |
| `encode_writer_*()` | потоковая упаковка битов в Base62 |
| `encode_set()` | объединённые delta, Golomb-Rice и Base62 |
| `set_fini()` | полный путь от имён до set-строки |
| `set_meta_init()` | быстрая проверка заголовка строки |
| `set_meta_fini()` | длины и верхние границы буферов |
| `decode_chunk()` | чтение обычного или escape Base62-блока |
| `decode_set()` | объединённые Base62, Golomb-Rice и delta |
| `cache_decode_set()` | два бакетных LRU-кэша |
| `downsample_set()` | уменьшение точности на один бит |
| `step_lower_bound()` | прыжок и уточнение позиции в большом массиве |
| `sorted_subset()` | проверка включения отсортированных множеств |
| `rpmsetcmp()` | публичное сравнение set-строк |
+1
View File
@@ -3,6 +3,7 @@
Research on ALT Linux RPM set.c and dependency comparison using encoded symbol sets. Research on ALT Linux RPM set.c and dependency comparison using encoded symbol sets.
- [Docs about current set:version implementation](Docs/set:version.md) - [Docs about current set:version implementation](Docs/set:version.md)
- [Docs about rewrite implementation (ru)](Docs/set9_ru.md)
# Compare results # Compare results
-21
View File
@@ -13,7 +13,6 @@
#include "system.h" #include "system.h"
#define CACHE_SIZE 512 #define CACHE_SIZE 512
#define PIVOT_SIZE 486
struct set { struct set {
size_t cnt; size_t cnt;
@@ -483,26 +482,6 @@ static const unsigned char char_to_num[255 + 1] = {[0] = 0xff, /* конец с
[('z' + 1)... 255] = 0xee}; [('z' + 1)... 255] = 0xee};
static char* put6bits(int c, char* bit_pt) {
*bit_pt++ = (c >> 0) & 1;
*bit_pt++ = (c >> 1) & 1;
*bit_pt++ = (c >> 2) & 1;
*bit_pt++ = (c >> 3) & 1;
*bit_pt++ = (c >> 4) & 1;
*bit_pt++ = (c >> 5) & 1;
return bit_pt;
}
static char* put4bits(int c, char* bit_pt) {
*bit_pt++ = (c >> 0) & 1;
*bit_pt++ = (c >> 1) & 1;
*bit_pt++ = (c >> 2) & 1;
*bit_pt++ = (c >> 3) & 1;
return bit_pt;
}
// Decode base62 and Golomb-Rice in one pass. Base62 is LSB-first; a Z escape contributes 10 stream // Decode base62 and Golomb-Rice in one pass. Base62 is LSB-first; a Z escape contributes 10 stream
// bits. // bits.
static inline int decode_chunk(const unsigned char** input, uint64_t* chunk, unsigned* width) { static inline int decode_chunk(const unsigned char** input, uint64_t* chunk, unsigned* width) {