Compare commits
3
Commits
60ffed1f6f
...
9f3b01f445
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f3b01f445 | ||
|
|
74e3c9d6d3 | ||
|
|
4eb56ae891 |
@@ -0,0 +1,141 @@
|
|||||||
|
# Исследование и улучшение механизма работы `set`-строк в ALT RPM
|
||||||
|
|
||||||
|
Ориентир на 700 слов, 9к символов
|
||||||
|
(!!!)/(???) - дополнить/уточнить
|
||||||
|
всё ещё куда-то надо включить "хэш клёвый, но медленный, вот тесты"
|
||||||
|
Проверить, чтобы всё, что нужно в `это` попало
|
||||||
|
|
||||||
|
## 1. Зачем нужны set-строки
|
||||||
|
|
||||||
|
Обычная зависимость от версии библиотеки не гарантирует, что в ней остались все нужные программе символы: символ можно удалить, не сменив SONAME, а одинаковые SONAME могут скрывать разные наборы экспортов. Поэтому ALT RPM использует версии зависимостей вида `set:<encoded-set>`. Для `Provides` такая строка описывает символы, предоставляемые библиотекой, а для `Requires` - символы, которые конкретный потребитель требует от неё. В такой конфигурации сравниваются не номера версий, а включение множеств: все хэши символов из `Requires` должны присутствовать в `Provides`.
|
||||||
|
Гарантия вероятностная, поскольку вместо полноценных имён символов хранятся усечённые хэши, но ответ "символ отсутствует", когда он есть мы не получим
|
||||||
|
|
||||||
|
## 2. Структура set-строки
|
||||||
|
|
||||||
|
set-строка формируется следующим образом:
|
||||||
|
|
||||||
|
1. Список символов формируется автодепами `rpm-build`.
|
||||||
|
2. По количеству `Provides` символов (`cnt`) вычисляется `bpp` - количество бит до которых обрезается хэш символа при формировании строки. (!!!)
|
||||||
|
3. Для каждого символа считается хэш-функция (используется Jenkins OAAT), хэш обрезается до `bpp` бит.
|
||||||
|
4. Массив хэшей сортируются, повторы удаляются.
|
||||||
|
5. Абсолютные значения заменяются дельтами между значениями
|
||||||
|
6. Для кодировки Golomb-Rice вычисляется параметр `Mshift=bpp - log2(cnt) - 1`.
|
||||||
|
7. Массив дельт сжимаются кодировкой Golomb-Rice.
|
||||||
|
8. Битовый поток преобразуется в Base62-строку.
|
||||||
|
|
||||||
|
Примечание: при формировании set-строки для `req` символов, `bpp` вычисляется по количеству `prov` символов в актуальной (???) версии библиотеки для избежания сильного усечения хэшей при небольшом количестве требуемых символов.
|
||||||
|
|
||||||
|
Итоговая строка выглядит следующим образом:
|
||||||
|
|
||||||
|
```text
|
||||||
|
set:<bpp_char><Mshift_char><base62 строка>
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
bpp_char = bpp - 7 + 'a'
|
||||||
|
Mshift_char = Mshift - 7 + 'a'
|
||||||
|
```
|
||||||
|
|
||||||
|
Краткая схема:
|
||||||
|
|
||||||
|
```
|
||||||
|
массив строк
|
||||||
|
| (Jenkins OAAT)
|
||||||
|
v
|
||||||
|
массив усечённых хэшей
|
||||||
|
| (qsort)
|
||||||
|
v
|
||||||
|
отсортированный массив хэшей
|
||||||
|
| (вычисление разницы между элементами)
|
||||||
|
v
|
||||||
|
массив delta
|
||||||
|
| (Rice-Golomb преобразование)
|
||||||
|
v
|
||||||
|
битовый массив
|
||||||
|
| (base62 преобразование)
|
||||||
|
v
|
||||||
|
set-строка
|
||||||
|
```
|
||||||
|
|
||||||
|
### Механизм сравнения set-строк
|
||||||
|
|
||||||
|
Для проверки включения множества символов `req` в множество `prov` необходимо выполнить обратное декодирование следующим образом:
|
||||||
|
|
||||||
|
```
|
||||||
|
set-строка
|
||||||
|
| (обратное base62 преобразование)
|
||||||
|
v
|
||||||
|
битовый массив
|
||||||
|
| (обратное Rice-Golomb преобразование)
|
||||||
|
v
|
||||||
|
массив delta
|
||||||
|
| (вычисление изначальных значений)
|
||||||
|
v
|
||||||
|
массив усечённых хэшей (отсортированный)
|
||||||
|
```
|
||||||
|
|
||||||
|
Значения хэшей в массивах приводятся к минимальному `bpp` из двух set-строк (сохраняется отсортированность и дистинктивность(???)).
|
||||||
|
|
||||||
|
После получения отсортированных массивов хэшей из set-строк, включение символов (а точнее их усечённых хэш-значений) одного множества во второе проверить не составляет труда.
|
||||||
|
|
||||||
|
## 3. Практическая реализация
|
||||||
|
|
||||||
|
Несмотря на описанную структуру set-строки, на практике в текущем `lib/set.c` применяется множество оптимизаций и улучшений, направленных на ускорение работы `rpmsetcmp(const char *set1, const char *set2)` (функции, выдающей результат включения множеств). Рассмотрим основные из них.
|
||||||
|
|
||||||
|
### 3.1. Слитый декодер
|
||||||
|
|
||||||
|
Вместо описанной выше последовательности декодирования set-строк применяется функция, объединяющая этапы `base62` и `golomb`.
|
||||||
|
|
||||||
|
`decode_base62_golomb()` - оптимизированная версия стадий `decode_base62` и `decode_golomb`. Функция считывает сразу по два байта, с помощью пре-compiled таблицы преобразует их в битовую последовательность, набирая блоки до 24 бит, после декодирует по `Rice-Golomb`.
|
||||||
|
|
||||||
|
Благодаря такому подходу удаётся ускорить работу алгоритма на сравнении строк в ~2 раза. (!!!)
|
||||||
|
|
||||||
|
### 3.2. Кэширование `Provides` set-строк
|
||||||
|
|
||||||
|
При передачи первого параметра (`const char *set1`) в функцию сравнения set-строк (`rpmsetcmp()`), set-строка кэшируется.
|
||||||
|
Используется простой LRU кэш (массив размером `256`), который сохраняет fingerprint оригинальной set-строки, саму set-строку и декодированный массив хэшей.
|
||||||
|
|
||||||
|
При cache_hit элемент смещается на первую позицию, а при первом попадании попадает на позицию `min(243, len(cache))`.
|
||||||
|
|
||||||
|
Очевидным недостатком такого подхода является:
|
||||||
|
|
||||||
|
1. Малый размер кэша
|
||||||
|
при увеличении размера кэша до 512 элементов, производительность увеличилась на X% (!!!)
|
||||||
|
|
||||||
|
2. Затраты на `realloc` при cache_hit
|
||||||
|
из-за хранения элементов кэша как массив (а не списком, например), после каждого попадания кэша приходится смещать до 255 записей.
|
||||||
|
|
||||||
|
### 3.3. Быстрое сравнение включения множеств символов
|
||||||
|
|
||||||
|
После получения отсортированных и усечённых до одинакового `bpp` масивов хэшей, необходимо проверить включение множеств. Отметим, что множество `req` символов будет, как правило, разреженным относительно множества `prov` символов.
|
||||||
|
|
||||||
|
`lib/set.c` делает это с помощью макроса `IFLT4`(`IFLT8`).
|
||||||
|
|
||||||
|
Данный макрос отвечает за быстрые прыжки на 4(8) элементов массива, и последующее уточнение на 2(4), 1(2) и 0(1) элемента, пока не найдём позицию, где `hash_arr1[i] <= hash_arr2[j] > hash_arr1[i+1]`.
|
||||||
|
|
||||||
|
Макрос `IFLT8` с первоначальным прыжком на 8 элементов выбирается при `len(hash_arr1) >= 16 * len(hash_arr2)`. В остальных случаях используется макрос `IFLT4`.
|
||||||
|
|
||||||
|
Из недостатков данного способа выделяется фиксированная длина прыжка, которую имеет смысл увеличивать пропорционально `len(hash_arr1) / len(hash_arr2)`.
|
||||||
|
|
||||||
|
## 4. Дальнейшие оптимизации
|
||||||
|
|
||||||
|
(???) Говорить ли про python-реализацию вовсе
|
||||||
|
|
||||||
|
Текущая реализация `lib/set.c` трудночитаемая и труднопонимаемая, а также не содержит некоторых оптимизаций, которые могли бы сильнее ускорить работу библиотеки.
|
||||||
|
|
||||||
|
В связи с этим, было принято решение о реимплементации кода с сохранением совместимости к текущему формату set-строк.
|
||||||
|
|
||||||
|
### 4.1. Слитый энкодер
|
||||||
|
|
||||||
|
В прошлом разделе говорилось о ускорении работы функции `rpmsetcmp()`, однако для части кода отвечающей за создание set-строк как таковых оптимизаций не существует.
|
||||||
|
Поэтому энкодер в новой версии пропускает стадию создания битового масива и напрямую декодирует base62 строки в массив delta.
|
||||||
|
|
||||||
|
### 4.2. Общая память под строки
|
||||||
|
|
||||||
|
Для улучшения encode составляющей также была изменена работа с памятью под символы. В новой версии вместо множества указателей на строки, под каждый из которых требуется свой `malloc`, введён единый указатель, в котором хранятся все символы последовательно, а индекс начала каждого из символа хранится отдельно.
|
||||||
|
|
||||||
|
Это позволяет снизить часть расходов на `malloc`.
|
||||||
|
|
||||||
|
### 4.3. Radix-sort
|
||||||
|
|
||||||
|
### 4.3. Изменённый кэш
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdatomic.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -34,6 +33,7 @@ struct set {
|
|||||||
char* strings;
|
char* strings;
|
||||||
struct symbols {
|
struct symbols {
|
||||||
size_t offset;
|
size_t offset;
|
||||||
|
unsigned full_hash;
|
||||||
unsigned hash;
|
unsigned hash;
|
||||||
}* symbols_v;
|
}* symbols_v;
|
||||||
};
|
};
|
||||||
@@ -47,14 +47,6 @@ struct decoded_set {
|
|||||||
enum {
|
enum {
|
||||||
DECODED_CACHE_SIZE = 512,
|
DECODED_CACHE_SIZE = 512,
|
||||||
DECODED_CACHE_BUCKETS = 1024,
|
DECODED_CACHE_BUCKETS = 1024,
|
||||||
PAIR_CACHE_SIZE = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct decoded_cache_entry;
|
|
||||||
|
|
||||||
struct pair_cache_entry {
|
|
||||||
uint64_t other_identity;
|
|
||||||
int result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct decoded_cache_entry {
|
struct decoded_cache_entry {
|
||||||
@@ -66,11 +58,8 @@ struct decoded_cache_entry {
|
|||||||
size_t len;
|
size_t len;
|
||||||
size_t count;
|
size_t count;
|
||||||
uint32_t fingerprint;
|
uint32_t fingerprint;
|
||||||
uint64_t identity;
|
|
||||||
unsigned bucket;
|
unsigned bucket;
|
||||||
unsigned target_bpp;
|
unsigned target_bpp;
|
||||||
unsigned pair_next;
|
|
||||||
struct pair_cache_entry pairs[PAIR_CACHE_SIZE];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct set_meta {
|
struct set_meta {
|
||||||
@@ -83,10 +72,8 @@ static unsigned decoded_cache_count[2];
|
|||||||
static struct decoded_cache_entry* decoded_cache_buckets[2][DECODED_CACHE_BUCKETS];
|
static struct decoded_cache_entry* decoded_cache_buckets[2][DECODED_CACHE_BUCKETS];
|
||||||
static struct decoded_cache_entry* decoded_cache_newest[2];
|
static struct decoded_cache_entry* decoded_cache_newest[2];
|
||||||
static struct decoded_cache_entry* decoded_cache_oldest[2];
|
static struct decoded_cache_entry* decoded_cache_oldest[2];
|
||||||
static uint64_t decoded_cache_next_identity = 1;
|
|
||||||
/* Cached arrays remain in use until comparison completes, so lookup, eviction,
|
static unsigned hash(const char* str);
|
||||||
* and comparison share one lock. */
|
|
||||||
static atomic_flag decoded_cache_lock = ATOMIC_FLAG_INIT;
|
|
||||||
|
|
||||||
struct set* set_new(void) {
|
struct set* set_new(void) {
|
||||||
struct set* set = xmalloc(sizeof(*set));
|
struct set* set = xmalloc(sizeof(*set));
|
||||||
@@ -116,7 +103,8 @@ void set_add(struct set* set, const char* sym) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set->symbols_v[set->cnt].offset = set->strings_len;
|
set->symbols_v[set->cnt].offset = set->strings_len;
|
||||||
set->symbols_v[set->cnt].hash = 0;
|
set->symbols_v[set->cnt].full_hash = hash(sym);
|
||||||
|
set->symbols_v[set->cnt].hash = set->symbols_v[set->cnt].full_hash;
|
||||||
memcpy(set->strings + set->strings_len, sym, length);
|
memcpy(set->strings + set->strings_len, sym, length);
|
||||||
set->strings_len = required;
|
set->strings_len = required;
|
||||||
++set->cnt;
|
++set->cnt;
|
||||||
@@ -241,18 +229,42 @@ static void base64_encode(const unsigned char* input, size_t input_len, char* ou
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned char* pack_hashes(const unsigned* hashes, size_t count, unsigned bpp,
|
static size_t compact_unique_hashes(struct symbols* symbols, size_t count) {
|
||||||
size_t* byte_count) {
|
size_t unique_count = 0;
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
while (i + 1 < count && symbols[i].hash == symbols[i + 1].hash) ++i;
|
||||||
|
symbols[unique_count++].hash = symbols[i].hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return unique_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char* pack_symbol_hashes(const struct symbols* symbols, size_t count,
|
||||||
|
unsigned bpp, size_t* byte_count) {
|
||||||
if (count > (SIZE_MAX - 7) / bpp) abort();
|
if (count > (SIZE_MAX - 7) / bpp) abort();
|
||||||
size_t bit_count = count * bpp;
|
size_t bit_count = count * bpp;
|
||||||
*byte_count = (bit_count + 7) / 8;
|
*byte_count = (bit_count + 7) / 8;
|
||||||
unsigned char* bytes = xmalloc(*byte_count);
|
unsigned char* bytes = xmalloc(*byte_count);
|
||||||
unsigned char* output = bytes;
|
unsigned char* output = bytes;
|
||||||
|
|
||||||
|
#if UINT_MAX == UINT32_MAX && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
|
||||||
|
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
if (bpp == 32 && sizeof(unsigned) == 4) {
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
unsigned hash = symbols[i].hash;
|
||||||
|
memcpy(output, &hash, sizeof(hash));
|
||||||
|
output += sizeof(hash);
|
||||||
|
}
|
||||||
|
assert((size_t)(output - bytes) == *byte_count);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
uint64_t bits = 0;
|
uint64_t bits = 0;
|
||||||
unsigned filled = 0;
|
unsigned filled = 0;
|
||||||
|
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
bits |= (uint64_t)hashes[i] << filled;
|
bits |= (uint64_t)symbols[i].hash << filled;
|
||||||
filled += bpp;
|
filled += bpp;
|
||||||
|
|
||||||
while (filled >= 8) {
|
while (filled >= 8) {
|
||||||
@@ -275,7 +287,7 @@ const char* set_fini(struct set* set, int bpp) {
|
|||||||
|
|
||||||
unsigned mask = bpp < 32 ? (UINT32_C(1) << bpp) - 1 : UINT32_MAX;
|
unsigned mask = bpp < 32 ? (UINT32_C(1) << bpp) - 1 : UINT32_MAX;
|
||||||
for (size_t i = 0; i < set->cnt; ++i) {
|
for (size_t i = 0; i < set->cnt; ++i) {
|
||||||
set->symbols_v[i].hash = hash(set->strings + set->symbols_v[i].offset) & mask;
|
set->symbols_v[i].hash = set->symbols_v[i].full_hash & mask;
|
||||||
}
|
}
|
||||||
sort_symbols(set->symbols_v, set->cnt, (unsigned)bpp);
|
sort_symbols(set->symbols_v, set->cnt, (unsigned)bpp);
|
||||||
|
|
||||||
@@ -286,27 +298,9 @@ const char* set_fini(struct set* set, int bpp) {
|
|||||||
if (strcmp(left, right) != 0) fprintf(stderr, "warning: hash collision: %s %s\n", left, right);
|
if (strcmp(left, right) != 0) fprintf(stderr, "warning: hash collision: %s %s\n", left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned* unique_hashes = xmalloc(set->cnt * sizeof(*unique_hashes));
|
|
||||||
size_t unique_count = 0;
|
|
||||||
for (size_t i = 0; i < set->cnt; ++i) {
|
|
||||||
while (i + 1 < set->cnt && set->symbols_v[i].hash == set->symbols_v[i + 1].hash) ++i;
|
|
||||||
unique_hashes[unique_count++] = set->symbols_v[i].hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t byte_count;
|
size_t byte_count;
|
||||||
unsigned char* allocated_bytes = NULL;
|
size_t unique_count = compact_unique_hashes(set->symbols_v, set->cnt);
|
||||||
const unsigned char* bytes;
|
unsigned char* bytes = pack_symbol_hashes(set->symbols_v, unique_count, (unsigned)bpp, &byte_count);
|
||||||
#if UINT_MAX == UINT32_MAX && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \
|
|
||||||
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
if (bpp == 32 && sizeof(unsigned) == 4) {
|
|
||||||
byte_count = unique_count * sizeof(*unique_hashes);
|
|
||||||
bytes = (const unsigned char*)unique_hashes;
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
allocated_bytes = pack_hashes(unique_hashes, unique_count, (unsigned)bpp, &byte_count);
|
|
||||||
bytes = allocated_bytes;
|
|
||||||
}
|
|
||||||
size_t payload_len = base64_encoded_size(byte_count);
|
size_t payload_len = base64_encoded_size(byte_count);
|
||||||
char* output = xmalloc(FORMAT_HEADER_LEN + payload_len + 1);
|
char* output = xmalloc(FORMAT_HEADER_LEN + payload_len + 1);
|
||||||
memcpy(output, FORMAT_PREFIX, sizeof(FORMAT_PREFIX) - 1);
|
memcpy(output, FORMAT_PREFIX, sizeof(FORMAT_PREFIX) - 1);
|
||||||
@@ -315,8 +309,7 @@ const char* set_fini(struct set* set, int bpp) {
|
|||||||
base64_encode(bytes, byte_count, output + FORMAT_HEADER_LEN);
|
base64_encode(bytes, byte_count, output + FORMAT_HEADER_LEN);
|
||||||
output[FORMAT_HEADER_LEN + payload_len] = '\0';
|
output[FORMAT_HEADER_LEN + payload_len] = '\0';
|
||||||
|
|
||||||
_free(allocated_bytes);
|
_free(bytes);
|
||||||
_free(unique_hashes);
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -357,8 +350,7 @@ static int set_meta_init(const char* source, struct set_meta* meta) {
|
|||||||
if (has_set_prefix(str)) str += 4;
|
if (has_set_prefix(str)) str += 4;
|
||||||
if (has_set_prefix(str)) return -1;
|
if (has_set_prefix(str)) return -1;
|
||||||
|
|
||||||
/* With bpp >= 10, a valid direct set has at least three Base64 characters.
|
/* With bpp >= 10, a valid direct set has at least three Base64 characters. */
|
||||||
* Checking this fixed prefix makes cache hits independent of total key length. */
|
|
||||||
if (str[0] != FORMAT_PREFIX[0] || str[1] != FORMAT_PREFIX[1]) return -1;
|
if (str[0] != FORMAT_PREFIX[0] || str[1] != FORMAT_PREFIX[1]) return -1;
|
||||||
if (str[2] < '0' || str[2] > '9' || str[3] < '0' || str[3] > '9') return -1;
|
if (str[2] < '0' || str[2] > '9' || str[3] < '0' || str[3] > '9') return -1;
|
||||||
if (str[4] == '\0' || str[5] == '\0' || str[6] == '\0') return -1;
|
if (str[4] == '\0' || str[5] == '\0' || str[6] == '\0') return -1;
|
||||||
@@ -367,7 +359,7 @@ static int set_meta_init(const char* source, struct set_meta* meta) {
|
|||||||
if (bpp < 10 || bpp > 32) return -1;
|
if (bpp < 10 || bpp > 32) return -1;
|
||||||
|
|
||||||
meta->str = str;
|
meta->str = str;
|
||||||
meta->len = 0;
|
meta->len = strlen(str);
|
||||||
meta->bpp = bpp;
|
meta->bpp = bpp;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -739,12 +731,24 @@ static void downsample_to(struct decoded_set* set, unsigned target_bpp) {
|
|||||||
|
|
||||||
static uint32_t decoded_cache_fingerprint(const struct set_meta* meta, unsigned target_bpp) {
|
static uint32_t decoded_cache_fingerprint(const struct set_meta* meta, unsigned target_bpp) {
|
||||||
const unsigned char* str = (const unsigned char*)meta->str;
|
const unsigned char* str = (const unsigned char*)meta->str;
|
||||||
uint32_t fingerprint = (uint32_t)str[4] | ((uint32_t)str[5] << 8) |
|
uint32_t fingerprint = UINT32_C(2166136261);
|
||||||
((uint32_t)str[6] << 16) | ((uint32_t)str[7] << 24);
|
fingerprint = (fingerprint ^ (uint32_t)meta->len) * UINT32_C(16777619);
|
||||||
fingerprint ^= meta->bpp * UINT32_C(0x27d4eb2d);
|
fingerprint = (fingerprint ^ (meta->bpp * UINT32_C(0x27d4eb2d))) * UINT32_C(16777619);
|
||||||
fingerprint ^= target_bpp * UINT32_C(0x85ebca6b);
|
fingerprint = (fingerprint ^ (target_bpp * UINT32_C(0x85ebca6b))) * UINT32_C(16777619);
|
||||||
fingerprint ^= fingerprint >> 11;
|
|
||||||
fingerprint *= UINT32_C(0x9e3779b1);
|
size_t prefix_len = meta->len < 8 ? meta->len : 8;
|
||||||
|
for (size_t i = 0; i < prefix_len; ++i) {
|
||||||
|
fingerprint = (fingerprint ^ str[i]) * UINT32_C(16777619);
|
||||||
|
}
|
||||||
|
size_t suffix_start = meta->len > 8 ? meta->len - 8 : prefix_len;
|
||||||
|
for (size_t i = suffix_start; i < meta->len; ++i) {
|
||||||
|
fingerprint = (fingerprint ^ str[i]) * UINT32_C(16777619);
|
||||||
|
}
|
||||||
|
|
||||||
|
fingerprint ^= fingerprint >> 16;
|
||||||
|
fingerprint *= UINT32_C(0x7feb352d);
|
||||||
|
fingerprint ^= fingerprint >> 15;
|
||||||
|
fingerprint *= UINT32_C(0x846ca68b);
|
||||||
fingerprint ^= fingerprint >> 16;
|
fingerprint ^= fingerprint >> 16;
|
||||||
return fingerprint;
|
return fingerprint;
|
||||||
}
|
}
|
||||||
@@ -783,19 +787,8 @@ static void decoded_cache_remove(struct decoded_cache_entry* victim, unsigned ca
|
|||||||
_free(victim);
|
_free(victim);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void decoded_cache_reset_pair_identities(void) {
|
|
||||||
for (unsigned bucket = 0; bucket < DECODED_CACHE_BUCKETS; ++bucket) {
|
|
||||||
for (struct decoded_cache_entry* provider = decoded_cache_buckets[0][bucket]; provider;
|
|
||||||
provider = provider->bucket_next) {
|
|
||||||
for (unsigned i = 0; i < PAIR_CACHE_SIZE; ++i) provider->pairs[i].other_identity = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
decoded_cache_next_identity = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cache_decode_set(const struct set_meta* meta, unsigned target_bpp, unsigned cache_id,
|
static int cache_decode_set(const struct set_meta* meta, unsigned target_bpp, unsigned cache_id,
|
||||||
const unsigned** hashes, size_t* count,
|
const unsigned** hashes, size_t* count) {
|
||||||
struct decoded_cache_entry** cache_entry) {
|
|
||||||
assert(cache_id < 2);
|
assert(cache_id < 2);
|
||||||
assert(target_bpp <= meta->bpp);
|
assert(target_bpp <= meta->bpp);
|
||||||
|
|
||||||
@@ -804,40 +797,35 @@ static int cache_decode_set(const struct set_meta* meta, unsigned target_bpp, un
|
|||||||
for (struct decoded_cache_entry* entry = decoded_cache_buckets[cache_id][bucket]; entry;
|
for (struct decoded_cache_entry* entry = decoded_cache_buckets[cache_id][bucket]; entry;
|
||||||
entry = entry->bucket_next) {
|
entry = entry->bucket_next) {
|
||||||
if (entry->fingerprint != fingerprint || entry->target_bpp != target_bpp ||
|
if (entry->fingerprint != fingerprint || entry->target_bpp != target_bpp ||
|
||||||
strcmp(entry->str, meta->str) != 0)
|
entry->len != meta->len || memcmp(entry->str, meta->str, meta->len + 1) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
decoded_cache_touch(entry, cache_id);
|
decoded_cache_touch(entry, cache_id);
|
||||||
*hashes = entry->hashes;
|
*hashes = entry->hashes;
|
||||||
*count = entry->count;
|
*count = entry->count;
|
||||||
*cache_entry = entry;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t len = strlen(meta->str);
|
|
||||||
|
|
||||||
struct decoded_set decoded;
|
struct decoded_set decoded;
|
||||||
if (decode_set_sized(meta->str, len, &decoded) < 0) return -1;
|
if (decode_set_sized(meta->str, meta->len, &decoded) < 0) return -1;
|
||||||
if (decoded.bpp != meta->bpp) {
|
if (decoded.bpp != meta->bpp) {
|
||||||
_free(decoded.hashes);
|
_free(decoded.hashes);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
downsample_to(&decoded, target_bpp);
|
downsample_to(&decoded, target_bpp);
|
||||||
|
|
||||||
if (len > SIZE_MAX - sizeof(struct decoded_cache_entry) - 1) {
|
if (meta->len > SIZE_MAX - sizeof(struct decoded_cache_entry) - 1) {
|
||||||
_free(decoded.hashes);
|
_free(decoded.hashes);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
struct decoded_cache_entry* entry = xmalloc(sizeof(*entry) + len + 1);
|
struct decoded_cache_entry* entry = xmalloc(sizeof(*entry) + meta->len + 1);
|
||||||
memset(entry, 0, sizeof(*entry));
|
memset(entry, 0, sizeof(*entry));
|
||||||
entry->str = (char*)(entry + 1);
|
entry->str = (char*)(entry + 1);
|
||||||
memcpy(entry->str, meta->str, len + 1);
|
memcpy(entry->str, meta->str, meta->len + 1);
|
||||||
entry->hashes = decoded.hashes;
|
entry->hashes = decoded.hashes;
|
||||||
entry->len = len;
|
entry->len = meta->len;
|
||||||
entry->count = decoded.count;
|
entry->count = decoded.count;
|
||||||
entry->fingerprint = fingerprint;
|
entry->fingerprint = fingerprint;
|
||||||
if (decoded_cache_next_identity == 0) decoded_cache_reset_pair_identities();
|
|
||||||
entry->identity = decoded_cache_next_identity++;
|
|
||||||
entry->bucket = bucket;
|
entry->bucket = bucket;
|
||||||
entry->target_bpp = target_bpp;
|
entry->target_bpp = target_bpp;
|
||||||
|
|
||||||
@@ -858,7 +846,6 @@ static int cache_decode_set(const struct set_meta* meta, unsigned target_bpp, un
|
|||||||
|
|
||||||
*hashes = entry->hashes;
|
*hashes = entry->hashes;
|
||||||
*count = entry->count;
|
*count = entry->count;
|
||||||
*cache_entry = entry;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -912,48 +899,28 @@ static int sorted_subset(const unsigned* small, size_t small_count, const unsign
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rpmsetcmp_locked(const char* str1, const char* str2) {
|
int rpmsetcmp(const char* str1, const char* str2) {
|
||||||
struct set_meta meta1;
|
struct set_meta meta1;
|
||||||
if (set_meta_init(str1, &meta1) < 0) return -3;
|
if (set_meta_init(str1, &meta1) < 0) return -3;
|
||||||
|
|
||||||
struct set_meta meta2;
|
struct set_meta meta2;
|
||||||
int meta2_status = set_meta_init(str2, &meta2);
|
if (set_meta_init(str2, &meta2) < 0) return -4;
|
||||||
unsigned target_bpp =
|
unsigned target_bpp = meta2.bpp < meta1.bpp ? meta2.bpp : meta1.bpp;
|
||||||
meta2_status == 0 && meta2.bpp < meta1.bpp ? meta2.bpp : meta1.bpp;
|
|
||||||
|
|
||||||
const unsigned* hashes1;
|
const unsigned* hashes1;
|
||||||
size_t count1;
|
size_t count1;
|
||||||
struct decoded_cache_entry* entry1;
|
if (cache_decode_set(&meta1, target_bpp, 0, &hashes1, &count1) < 0) return -3;
|
||||||
if (cache_decode_set(&meta1, target_bpp, 0, &hashes1, &count1, &entry1) < 0) return -3;
|
|
||||||
if (meta2_status < 0) return -4;
|
if (meta1.len == meta2.len && memcmp(meta1.str, meta2.str, meta1.len + 1) == 0) return 0;
|
||||||
|
|
||||||
const unsigned* hashes2;
|
const unsigned* hashes2;
|
||||||
size_t count2;
|
size_t count2;
|
||||||
struct decoded_cache_entry* entry2;
|
if (cache_decode_set(&meta2, target_bpp, 1, &hashes2, &count2) < 0) return -4;
|
||||||
if (cache_decode_set(&meta2, target_bpp, 1, &hashes2, &count2, &entry2) < 0) return -4;
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < PAIR_CACHE_SIZE; ++i) {
|
|
||||||
if (entry1->pairs[i].other_identity == entry2->identity) return entry1->pairs[i].result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int result;
|
|
||||||
if (count1 == count2)
|
if (count1 == count2)
|
||||||
result = memcmp(hashes1, hashes2, count1 * sizeof(*hashes1)) == 0 ? 0 : -2;
|
return memcmp(hashes1, hashes2, count1 * sizeof(*hashes1)) == 0 ? 0 : -2;
|
||||||
else if (count1 > count2)
|
else if (count1 > count2)
|
||||||
result = sorted_subset(hashes2, count2, hashes1, count1) ? 1 : -2;
|
return sorted_subset(hashes2, count2, hashes1, count1) ? 1 : -2;
|
||||||
else
|
else
|
||||||
result = sorted_subset(hashes1, count1, hashes2, count2) ? -1 : -2;
|
return sorted_subset(hashes1, count1, hashes2, count2) ? -1 : -2;
|
||||||
|
|
||||||
struct pair_cache_entry* pair = &entry1->pairs[entry1->pair_next++ % PAIR_CACHE_SIZE];
|
|
||||||
pair->other_identity = entry2->identity;
|
|
||||||
pair->result = result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rpmsetcmp(const char* str1, const char* str2) {
|
|
||||||
while (atomic_flag_test_and_set_explicit(&decoded_cache_lock, memory_order_acquire)) {
|
|
||||||
}
|
|
||||||
int result = rpmsetcmp_locked(str1, str2);
|
|
||||||
atomic_flag_clear_explicit(&decoded_cache_lock, memory_order_release);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user