doc for set9.c
This commit is contained in:
+586
@@ -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-строк |
|
||||
Reference in New Issue
Block a user