From 0bf78d4c6898e18f33a474a590022af152128e8c Mon Sep 17 00:00:00 2001 From: Dmitrii Krosh Date: Sat, 4 Jul 2026 16:52:13 +0300 Subject: [PATCH] describe decode helper func --- Docs/set:version.md | 128 +++++++++++++++++++++++++++++++++++++++++--- for_me.md | 12 +++++ 2 files changed, 133 insertions(+), 7 deletions(-) diff --git a/Docs/set:version.md b/Docs/set:version.md index fccc74c..01231a9 100644 --- a/Docs/set:version.md +++ b/Docs/set:version.md @@ -35,7 +35,7 @@ struct set *set_free(struct set *set); - 1: set1 > set2 - 0: set1 == set2 -- -1: set1 < set2 (aka set1 \subset set2) +- -1: set1 < set2 - -2: set1 != set2 - -3: set1 decoder error - -4: set2 decoder error @@ -44,9 +44,7 @@ struct set *set_free(struct set *set); ### `set_new()` -Создаёт пустой объект `struct set`. - -Внутри `struct set` — это временный контейнер для строк символов и их будущих hash-значений. +Создаёт пустой объект `struct set` — контейнер для строк символов и их будущих hash-значений. Из релизации: > internally struct set is just a bag of strings and their hash values. @@ -102,9 +100,10 @@ set-строка ### hash -Для каждого элемента (строки) высчитывается 32-битный хэш и обрезается до `bpp` бит +Для каждого элемента (строки) высчитывается 32-битный хэш и обрезается до `bpp` младщих бит ```c +unsigned mask = (1u << bpp) - 1; set->sv[i].v = hash(set->sv[i].s) & mask; ``` @@ -135,7 +134,7 @@ unsigned *v = {1, 3, 12, 8}; ### Rice-Golomb -> `encode_golomb()` - Main golomb encoding routine: package integers into bits. +> `encode_golomb()` - main golomb encoding routine: package integers into bits. Для сокращения длины итоговой строки применяется Rice-Golomb кодирование, которое позволяет с параметром `Mshift` записать число `n` как `n >> Mshift`, записанный последовательностью нулей и `n & 2^Mshift` записанный стандартным двоичным кодированием. Целая часть и остаток разделяется единичном битом. @@ -220,7 +219,115 @@ set-строка массив хэшей ``` -После массивы хэшей сравниваются между собой на наличие жлементов одного массива в другом. +После массивы хэшей сравниваются между собой на наличие элементов одного массива в другом. + +## Магия внутри `rpmsetcmp()` + +1. У строки обрезается префикс `set:`, если он присутствует, декодируется значение `bpp` и `Mshift` с помощью `decode_set_init()` +2. `set1` декодируется с помощью `cache_decode_set()` +3. `set2` декодируется с помощью `decode_set()` +4. для каждого массива хэшей (`v1` и `v2`) создаётся два буферных массива `v(1|2)buf(A|B)` для функции `downsample_set()` +5. С помощью функции `downsample_set()` `bpp` обоих хэшей выравнивается до минимального из `bpp1` и `bpp2`. +6. Создаётся два флага `ge` и `le`, для определения вложенности множеств +7. Вложенность множеств проверяется с помощью 3х макросов: `IFGE` `IFLT4` `IFLT8` +8. Возвращается значение в зависимости от вложенности: + - 1: set1 > set2 + - 0: set1 == set2 + - -1: set1 < set2 + - -2: set1 != set2 + +### `decode_set()` + +### `cache_decode_set()` + +> `cache_decode_set()` - special decode_set version with LRU caching. + +Возвращает количество элементов хэша в массиве, помещает указатель `*pv` на массив хэшей. + +Внутри `cache_decode_set()` создаются статические массивы: + +- `static unsigned hv[CACHE_SIZE]` - для хранения fingerprint set-строки +- `static struct cache_ent *ev[CACHE_SIZE]` - для хранения полной информации о декодированной строке + +В качестве fingerprint используется простой и быстро вычисляемый + +```c +unsigned hash = str[0] | (str[2] << 8) | (str[3] << 16); +``` + +массивы `hv` и `ev` представляют из себя аналог LRU кэша размером `CACHE_SIZE=256`. + +Если fingerprint set-строки на декодирование совпал с имеющимся в `hv` массиве, проверяется полное соответствие строки с `ev[i]->str`. В случае hit - элемент помещается на нулевую позицию, остальные элементы сдвигаются. +При неудачной проверке полной set-строки, поиск по кэшу продолжается. + +В случае, если элемента не оказалось в кэше, строка декодируется функцией `decode_set()`, добавляется `SENTINELS=8` бит и помещается в кэш следующим образом: + +- Если кэш не заполнен, декодируемая строка записывается на первую свободную позицию +- Если кэш заполнен, элемент ставится на `PIVOT_SIZE=243` позицию, элементы с этой позиции сдвигаются. + +#### `SENTINELS` биты + +Данные `~0u` биты необходимы при дальнейшем проходе по массиву внутри функции `rpmsetcmp()`, т.к. там происходят прыжки по 4 и 8 элементов. + +### `downsample_set()` + +> `downsample_set()` - Reduce a set of (bpp + 1) values to a set of bpp values + +Входной массив для работы - `v`. +Итоговый массив будет доступен по указателю `w`. +Возвращаемое значение - количество элементов в новом массиве `w`. + +Т.к. первоначальный входной массив отсортирован, после обрезания до `bpp` бит, массив будет поделён на две части, обе из которых будут отсортированны. Деление массива будет происходить в месте, где старший бит на позции `bpp+1` становится равным `1`. + +Далее обе половины массива объединяются в буфере `w`, дубликаты значений удаляются. + +Пример: + +```text +v = [1, 3, 6, 8, 10, 14] +bpp = 3 +mask = 7 +v_mask = [1, 3, 6, 0, 2, 6] +w = [0, 1, 2, 3, 6] +return value = 5 +``` + +### Макросы `IFGE` `IFLT4` `IFLT8` + +#### Макросы `IFLT*` + +`IFLT8` быстро продвигают `v1`, пока `*v1 < v2val`. Сперва макрос "грубо" прыгает по 8 элементов, далее уточняет с шагом 4, 2, 1. + +```text ++8 +8 +8 ... // грубый поиск +-4 // откат +±2 // уточнение +±1 // уточнение ++1 возможно // финальная коррекция +``` + +В итоге `v1` оказывается на первом элементе, который не меньше `v2val`. + +`IFLT4` делает аналогичную работу, но с шагом в `4`. `IFLT8` выбирается в случае, когда массив `v1` превосходит по размеру массив `v2` более чем в `16` раз. + +#### Макрос `IFGE` + +После `IFLT*` вариант `*v1 < v2val` уже невозможен. + +Значит остаётся: + +```c +*v1 > v2val +``` + +То есть текущий элемент `v2val` есть во втором множестве, но отсутствует в первом. + +Следовательно `v1` уже не может быть над множеством `v2`: + +```c +ge = 0; +v2++; +``` ## Комментарии @@ -236,3 +343,10 @@ some funny [msg's](https://lists.pld-linux.org/mailman/pipermail/pld-devel-en/20 про "отрицательные значения = код ошибки" встречается в многих местах, стоит вынести отдельно проверить кодом примеры, особенно base62 + +кэш вечно копируется и переносится, немного странно +PIVOT_SIZE странный + +прыжки IFLT8 и IFLT4 я бы возможно делал как c1/c2 + +учитывая оптимизации, возможно стоит самостоятельно менять массивы местами до начала всех операций (но проблема с кэшем возможна) diff --git a/for_me.md b/for_me.md index 44a3fa8..3962882 100644 --- a/for_me.md +++ b/for_me.md @@ -24,3 +24,15 @@ xxHash64 (XXH64): относительно простая имплементац в случае 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