article.tex
This commit is contained in:
+101
-115
@@ -1,47 +1,60 @@
|
||||
|
||||
% Подключаемый фрагмент по образцу abstract.tex.
|
||||
% Авторские данные, аннотация и ключевые слова в исходном тексте не заданы.
|
||||
\author{Дмитрий Гудов}
|
||||
|
||||
% Город
|
||||
\city{Москва}
|
||||
|
||||
% Организация
|
||||
\affiliation{ALT Linux}
|
||||
|
||||
% Название проекта, которому посвящён доклад (необязательно,
|
||||
% но желательно).
|
||||
% \projecttitle{set-строки в rpm}
|
||||
% Сайт(ы) проекта
|
||||
\projecturl{\url{https://github.com/kr0sh512/alt-rpm-set-version}}
|
||||
|
||||
\title{Исследование и улучшение механизма работы \texttt{set}-строк в ALT RPM}
|
||||
\maketitle
|
||||
|
||||
\begingroup
|
||||
\setlength{\emergencystretch}{3em}
|
||||
\begin{abstract}
|
||||
В ALT RPM версии зависимостей \texttt{set:} кодируют множества ELF-символов и
|
||||
позволяют проверять совместимость библиотек точнее, чем по одному SONAME.
|
||||
В работе разобраны формат строк и оптимизации исходного \texttt{lib/set.c},
|
||||
описана совместимая реимплементация \texttt{set9.c} и приведены результаты её
|
||||
проверки на метаданных Sisyphus и в симуляциях APT. Отдельно рассмотрены
|
||||
несовместимые альтернативные форматы и замена хэш-функции.
|
||||
\end{abstract}
|
||||
|
||||
Ориентир на 700 слов, 9к символов
|
||||
(!!!)/(???) - дополнить/уточнить
|
||||
всё ещё куда-то надо включить "хэш клёвый, но медленный, вот тесты"
|
||||
Проверить, чтобы всё, что нужно в \texttt{это} попало
|
||||
\keywords{ALT RPM, ELF-символы, set:version, Golomb--Rice}
|
||||
|
||||
\section*{1. Зачем нужны set-строки}
|
||||
|
||||
Обычная зависимость от версии библиотеки не гарантирует, что в ней остались все нужные программе символы: символ можно удалить, не сменив SONAME, а одинаковые SONAME могут скрывать разные наборы экспортов. Поэтому ALT RPM использует версии зависимостей вида \texttt{set:<encoded-set>}. Для \texttt{Provides} такая строка описывает символы, предоставляемые библиотекой, а для \texttt{Requires} - символы, которые конкретный потребитель требует от неё. В такой конфигурации сравниваются не номера версий, а включение множеств: все хэши символов из \texttt{Requires} должны присутствовать в \texttt{Provides}.
|
||||
Гарантия вероятностная, поскольку вместо полноценных имён символов хранятся усечённые хэши, но ответ "символ отсутствует", когда он есть мы не получим
|
||||
Обычная зависимость от версии библиотеки не гарантирует, что в ней остались все нужные программе символы: символ можно удалить, не сменив SONAME, а одинаковые SONAME могут скрывать разные наборы экспортов. Поэтому ALT RPM использует версии зависимостей вида \texttt{set:<encoded-set>}. Для \texttt{Provides} такая строка описывает символы, предоставляемые библиотекой, а для \texttt{Requires} --- символы, которые конкретный потребитель требует от неё. В такой конфигурации сравниваются не номера версий, а включение множеств: все хэши символов из \texttt{Requires} должны присутствовать в \texttt{Provides}.
|
||||
|
||||
Гарантия вероятностная, поскольку вместо полных имён символов хранятся усечённые хэши. Ложное отрицание невозможно: совпадающее имя всегда даёт то же хэш-значение. Возможны ложные положительные результаты, если разные имена имеют одинаковый усечённый хэш.
|
||||
|
||||
\section*{2. Структура set-строки}
|
||||
|
||||
set-строка формируется следующим образом:
|
||||
Set-строка формируется следующим образом:
|
||||
|
||||
\begin{enumerate}
|
||||
\item Список символов формируется автодепами \texttt{rpm-build}.
|
||||
\item По количеству \texttt{Provides} символов (\texttt{cnt}) вычисляется \texttt{bpp} - количество бит до которых обрезается хэш символа при формировании строки. (!!!)
|
||||
\item Для каждого символа считается хэш-функция (используется Jenkins OAAT), хэш обрезается до \texttt{bpp} бит.
|
||||
\item Массив хэшей сортируются, повторы удаляются.
|
||||
\item Абсолютные значения заменяются дельтами между значениями
|
||||
\item Для кодировки Golomb-Rice вычисляется параметр \texttt{Mshift=bpp - log2(cnt) - 1}.
|
||||
\item Массив дельт сжимаются кодировкой Golomb-Rice.
|
||||
\item Битовый поток преобразуется в Base62-строку.
|
||||
\item Список символов формируется автодепами \texttt{rpm-build}. Для \texttt{Provides} это отфильтрованные экспортируемые базовые имена ELF-символов; для \texttt{Requires} \texttt{ldd --bindings} связывает сильные неопределённые символы с конкретной библиотекой.
|
||||
\item Для каждого имени вычисляется Jenkins OAAT; при формировании строки оставляются младшие \texttt{bpp} бит хэша. Допустимый диапазон \texttt{bpp} --- от 10 до 32.
|
||||
\item Массив хэшей сортируется, повторы удаляются, а абсолютные значения заменяются дельтами между соседними значениями.
|
||||
\item Для кодирования Golomb--Rice вычисляется параметр \texttt{Mshift = bpp - log2(cnt) - 1}; затем он ограничивается диапазоном 7--31 так, чтобы \texttt{Mshift < bpp}.
|
||||
\item Массив дельт сжимается кодировкой Golomb--Rice, а битовый поток преобразуется в Base62-строку.
|
||||
\end{enumerate}
|
||||
|
||||
Примечание: при формировании set-строки для \texttt{req} символов, \texttt{bpp} вычисляется по количеству \texttt{prov} символов в актуальной (???) версии библиотеки для избежания сильного усечения хэшей при небольшом количестве требуемых символов.
|
||||
При формировании строки \texttt{Requires} точность \texttt{bpp} выбирается по числу символов соответствующей предоставляющей библиотеки, а не по числу требуемых символов. Это сохраняет одинаковую точность представления для обеих сторон зависимости и уменьшает риск коллизий в малом множестве \texttt{Requires}.
|
||||
|
||||
Итоговая строка выглядит следующим образом:
|
||||
Итоговая строка имеет вид:
|
||||
|
||||
\begin{verbatim}
|
||||
set:<bpp_char><Mshift_char><base62 строка>
|
||||
set:<bpp_char><Mshift_char><base62-полезная нагрузка>
|
||||
\end{verbatim}
|
||||
|
||||
\begin{verbatim}
|
||||
bpp_char = bpp - 7 + 'a'
|
||||
bpp_char = bpp - 7 + 'a'
|
||||
Mshift_char = Mshift - 7 + 'a'
|
||||
\end{verbatim}
|
||||
|
||||
@@ -51,125 +64,82 @@ Mshift_char = Mshift - 7 + 'a'
|
||||
массив строк
|
||||
| (Jenkins OAAT)
|
||||
v
|
||||
массив усечённых хэшей
|
||||
| (qsort)
|
||||
массив усечённых хэшей -- сортировка и удаление повторов
|
||||
| (разности соседних значений)
|
||||
v
|
||||
отсортированный массив хэшей
|
||||
| (вычисление разницы между элементами)
|
||||
массив дельт
|
||||
| (преобразование Golomb--Rice)
|
||||
v
|
||||
массив delta
|
||||
| (Rice-Golomb преобразование)
|
||||
v
|
||||
битовый массив
|
||||
| (base62 преобразование)
|
||||
битовый поток
|
||||
| (Base62)
|
||||
v
|
||||
set-строка
|
||||
\end{verbatim}
|
||||
|
||||
\subsection*{Механизм сравнения set-строк}
|
||||
|
||||
Для проверки включения множества символов \texttt{req} в множество \texttt{prov} необходимо выполнить обратное декодирование следующим образом:
|
||||
Для проверки включения множества символов \texttt{Requires} в множество \texttt{Provides} выполняется обратное декодирование:
|
||||
|
||||
\begin{verbatim}
|
||||
set-строка
|
||||
| (обратное base62 преобразование)
|
||||
| (обратное Base62-преобразование)
|
||||
v
|
||||
битовый массив
|
||||
| (обратное Rice-Golomb преобразование)
|
||||
битовый поток
|
||||
| (обратное преобразование Golomb--Rice)
|
||||
v
|
||||
массив delta
|
||||
| (вычисление изначальных значений)
|
||||
массив дельт
|
||||
| (накопление дельт)
|
||||
v
|
||||
массив усечённых хэшей (отсортированный)
|
||||
отсортированный массив усечённых хэшей
|
||||
\end{verbatim}
|
||||
|
||||
Значения хэшей в массивах приводятся к минимальному \texttt{bpp} из двух set-строк (сохраняется отсортированность и дистинктивность(???)).
|
||||
|
||||
После получения отсортированных массивов хэшей из set-строк, включение символов (а точнее их усечённых хэш-значений) одного множества во второе проверить не составляет труда.
|
||||
Если точности строк различаются, значения приводятся к минимальному \texttt{bpp} из двух строк. После проекции сохраняются сортировка и уникальность: части массива по разные стороны удаляемого старшего бита сливаются с удалением повторов. Затем включение одного отсортированного множества усечённых хэш-значений в другое проверяется линейным проходом с пропусками.
|
||||
|
||||
\section*{3. Практическая реализация}
|
||||
|
||||
Несмотря на описанную структуру set-строки, на практике в текущем \texttt{lib/set.c} применяется множество оптимизаций и улучшений, направленных на ускорение работы \texttt{rpmsetcmp(const char *set1, const char *set2)} (функции, выдающей результат включения множеств). Рассмотрим основные из них.
|
||||
Несмотря на описанную структуру set-строки, в текущем \texttt{lib/set.c} применяется множество оптимизаций, направленных на ускорение \texttt{rpmsetcmp(const char *set1, const char *set2)}. В рабочем вызове первым аргументом передаётся \texttt{Provides}, вторым --- \texttt{Requires}; результат \texttt{1} означает строгое включение первого множества во второе, \texttt{0} --- равенство, а \texttt{-1} и \texttt{-2} --- обратное включение и несравнимость соответственно.
|
||||
|
||||
\subsection*{3.1. Слитый декодер}
|
||||
|
||||
Вместо описанной выше последовательности декодирования set-строк применяется функция, объединяющая этапы \texttt{base62} и \texttt{golomb}.
|
||||
Вместо последовательного декодирования Base62 и Golomb--Rice применяется функция \texttt{decode\_base62\_golomb()}, объединяющая оба этапа. Она считывает Base62-полезную нагрузку парами символов, с помощью заранее вычисленной таблицы получает биты, накапливает блоки до 24 бит и сразу декодирует значения Golomb--Rice. После этого восстанавливаются абсолютные значения из дельт.
|
||||
|
||||
\texttt{decode\_base62\_golomb()} - оптимизированная версия стадий \texttt{decode\_base62} и \texttt{decode\_golomb}. Функция считывает сразу по два байта, с помощью пре-compiled таблицы преобразует их в битовую последовательность, набирая блоки до 24 бит, после декодирует по \texttt{Rice-Golomb}.
|
||||
|
||||
Благодаря такому подходу удаётся ускорить работу алгоритма на сравнении строк в \textasciitilde{}2 раза. (!!!)
|
||||
Роль этой оптимизации подтверждается отдельной контрольной серией: вариант без оптимизированного декодера расходовал в APT-симуляциях в 1,47--2,82 раза больше суммарного процессорного времени, чем исходный \texttt{set.c}.
|
||||
|
||||
\subsection*{3.2. Кэширование \texttt{Provides} set-строк}
|
||||
|
||||
При передачи первого параметра (\texttt{const char *set1}) в функцию сравнения set-строк (\texttt{rpmsetcmp()}), set-строка кэшируется.
|
||||
Используется простой LRU кэш (массив размером \texttt{256}), который сохраняет fingerprint оригинальной set-строки, саму set-строку и декодированный массив хэшей.
|
||||
При передаче первого параметра в \texttt{rpmsetcmp()} его строка кэшируется. Исходная реализация использует массив из 256 записей, содержащих короткий отпечаток, исходную строку и декодированный массив хэшей; совпадение отпечатка подтверждается полным сравнением строки. При попадании запись перемещается в начало двумя вызовами \texttt{memmove()}; при заполнении кэша последняя запись освобождается, а новая помещается в позицию 243. Поэтому этот кэш близок к LRU, но не является строгим LRU.
|
||||
|
||||
При cache\_hit элемент смещается на первую позицию, а при первом попадании попадает на позицию \texttt{min(243, len(cache))}.
|
||||
|
||||
Очевидным недостатком такого подхода является:
|
||||
|
||||
\begin{enumerate}
|
||||
\item Малый размер кэша
|
||||
при увеличении размера кэша до 512 элементов, производительность увеличилась на X\% (!!!)
|
||||
|
||||
\item Затраты на \texttt{realloc} при cache\_hit
|
||||
из-за хранения элементов кэша как массив (а не списком, например), после каждого попадания кэша приходится смещать до 255 записей.
|
||||
\end{enumerate}
|
||||
Кэшируется только первый операнд, а второй декодируется при каждом сравнении. Следовательно, попадание в кэш устраняет не всё сравнение, а только повторное декодирование \texttt{Provides}; нормализация \texttt{bpp} также выполняется заново.
|
||||
|
||||
\subsection*{3.3. Быстрое сравнение включения множеств символов}
|
||||
|
||||
После получения отсортированных и усечённых до одинакового \texttt{bpp} масивов хэшей, необходимо проверить включение множеств. Отметим, что множество \texttt{req} символов будет, как правило, разреженным относительно множества \texttt{prov} символов.
|
||||
После получения отсортированных и приведённых к одинаковому \texttt{bpp} массивов хэшей необходимо проверить включение множеств. Как правило, множество \texttt{Requires} разрежено относительно \texttt{Provides}. Для этого \texttt{lib/set.c} использует макросы \texttt{IFLT4} и \texttt{IFLT8}: они делают прыжки на 4 или 8 элементов и уточняют позицию меньшими шагами. Вариант \texttt{IFLT8} выбирается при \texttt{len(Provides) >= 16 * len(Requires)}, в остальных случаях используется \texttt{IFLT4}.
|
||||
|
||||
\texttt{lib/set.c} делает это с помощью макроса \texttt{IFLT4}(\texttt{IFLT8}).
|
||||
Фиксированные шаги являются компромиссом между простотой и числом сравнений. Они хорошо работают для разреженных требований, но не учитывают точное отношение размеров множеств.
|
||||
|
||||
Данный макрос отвечает за быстрые прыжки на 4(8) элементов массива, и последующее уточнение на 2(4), 1(2) и 0(1) элемента, пока не найдём позицию, где \texttt{hash\_arr1[i] <= hash\_arr2[j] > hash\_arr1[i+1]}.
|
||||
\section*{4. Совместимая реимплементация}
|
||||
|
||||
Макрос \texttt{IFLT8} с первоначальным прыжком на 8 элементов выбирается при \texttt{len(hash\_arr1) >= 16 * len(hash\_arr2)}. В остальных случаях используется макрос \texttt{IFLT4}.
|
||||
Для упрощения сопровождения была создана совместимая с форматом и публичным API реализация \texttt{set9.c}. В \texttt{set9.c} были реализованы следующие изменения.
|
||||
|
||||
Из недостатков данного способа выделяется фиксированная длина прыжка, которую имеет смысл увеличивать пропорционально \texttt{len(hash\_arr1) / len(hash\_arr2)}.
|
||||
\subsection*{4.1. Потоковое кодирование и хранение символов}
|
||||
|
||||
\section*{4. Дальнейшие оптимизации}
|
||||
Энкодер \texttt{set9.c} передаёт значения по цепочке «хэш --- дельта --- Golomb--Rice --- Base62» без промежуточных массивов битов и дельт. Вместо отдельных выделений памяти под каждое имя используется растущая строковая арена, а в массиве символов хранятся смещения. Это позволет существенно уменьшить число вызовов \texttt{malloc()}.
|
||||
|
||||
(???) Говорить ли про python-реализацию вовсе
|
||||
\subsection*{4.2. Сортировка}
|
||||
|
||||
Текущая реализация \texttt{lib/set.c} трудночитаемая и труднопонимаемая, а также не содержит некоторых оптимизаций, которые могли бы сильнее ускорить работу библиотеки.
|
||||
Для наборов меньше 128 значений сохраняется \texttt{qsort()}, а для больших используется стабильная побайтовая LSD radix sort. Число проходов равно \texttt{ceil(bpp/8)} и не превышает четырёх; дополнительная память имеет размер \texttt{O(n + 256)}.
|
||||
|
||||
В связи с этим, было принято решение о реимплементации кода с сохранением совместимости к текущему формату set-строк.
|
||||
\subsection*{4.3. Изменённый кэш и декодер}
|
||||
|
||||
\subsection*{4.1. Слитый энкодер}
|
||||
В новой реализации используются два независимых кэша по 512 записей --- по одному для каждого операнда. Поиск выполняется через хэш-бакеты, а двусвязный LRU-список перемещает запись за \texttt{O(1)}. Ключ включает исходную строку и целевой \texttt{bpp}, поэтому результат нормализации можно повторно использовать.
|
||||
|
||||
В прошлом разделе говорилось о ускорении работы функции \texttt{rpmsetcmp()}, однако для части кода отвечающей за создание set-строк как таковых оптимизаций не существует.
|
||||
Поэтому энкодер в новой версии пропускает стадию создания битового масива и напрямую декодирует base62 строки в массив delta.
|
||||
Потоковый декодер использует 64-битный аккумулятор и более простую таблицу Base62. Для сравнения равных по размеру множеств применяется \texttt{memcmp()}, а для неравных проверяется только потенциальное включение меньшего в большее; разреженный случай использует монотонный поиск с шагом, зависящим от отношения размеров.
|
||||
|
||||
\subsection*{4.2. Общая память под строки}
|
||||
\subsection*{4.4. Проверка корректности и производительности}
|
||||
|
||||
Для улучшения encode составляющей также была изменена работа с памятью под символы. В новой версии вместо множества указателей на строки, под каждый из которых требуется свой \texttt{malloc}, введён единый указатель, в котором хранятся все символы последовательно, а индекс начала каждого из символа хранится отдельно.
|
||||
\texttt{set9.c} был собран с \texttt{-Wall -Wextra -Werror}; встроенный \texttt{SELF\_TEST} успешно проверил хэширование, сортировку, кодирование и декодирование, метаданные, понижение \texttt{bpp}, включение, кэш, построитель и API. На снимке Sisyphus для \texttt{x86\_64/noarch} от 21 августа 2026 года проверены 68\,494 пары \texttt{set:} с одинаковым capability: 232 пары равны, в 68\,260 случаях \texttt{Provides} строго содержит \texttt{Requires}, а две несравнимые пары \texttt{python3(ast)} дополнительно имеют обычный unversioned \texttt{Provides}. С учётом 661 такого unversioned \texttt{Provides} все 69\,153 set-требования снимка удовлетворяются.
|
||||
|
||||
Это позволяет снизить часть расходов на \texttt{malloc}.
|
||||
|
||||
\subsection*{4.3. Radix sort}
|
||||
|
||||
Вместо \texttt{qsort} при сортировке хэшей символов теперь используется \texttt{radix sort} на количестве элементов массива \textgreater{}128. (при \textless{}=128 остаётся \texttt{qsort})
|
||||
|
||||
\subsection*{4.4. Изменённый кэш}
|
||||
|
||||
Кэш претерпел множество изменений, т.к. давал сильный прирост в скорости (!!!)
|
||||
|
||||
\begin{enumerate}
|
||||
\item Изменён размер до 512 значений.
|
||||
\item Добавлен кэш также и для \texttt{req} set-строк. Теперь порядок аргументов не имеет значения для производительности.
|
||||
\item Кэш теперь строится на списках, а не на массиве, благодаря чему более нет затрат на \texttt{realloc} при cache hit.
|
||||
\end{enumerate}
|
||||
|
||||
\subsection*{4.5. Изменённый декодер}
|
||||
|
||||
Оставляя изначальную идею слитого декодера, была написана его реимлементация, использующая более простую логику, но использующая 64-битные блоки, а также упрощённую precompiled таблицу. (???) если есть результаты ускорения, сюда надо
|
||||
|
||||
\subsection*{4.6. Тесты производительности}
|
||||
|
||||
Для проверки производительности сравнивались две локальные сборки \texttt{librpm}: исходная реализация и версия с описанными изменениями. Замеры выполнялись на одинаковых симуляционных командах \texttt{apt-get}.
|
||||
|
||||
Коэффициент считается как отношение времени исходной реализации к времени изменённой. Значение больше \texttt{1} - ускорение, меньше \texttt{1} - замедление.
|
||||
Для производительности сравнивались две локальные сборки \texttt{librpm}: исходная реализация и версия с описанными изменениями. Замеры выполнялись на одинаковых симуляционных командах \texttt{apt-get}. В таблице приведены средние процессорные времена. Коэффициент равен отношению времени исходной реализации к времени изменённой: значение больше единицы означает ускорение, меньше единицы --- замедление.
|
||||
|
||||
\begin{center}
|
||||
\begingroup
|
||||
@@ -177,41 +147,57 @@ set-строка
|
||||
\setlength{\tabcolsep}{2pt}
|
||||
\begin{tabular}{@{}lrrrrrr@{}}
|
||||
\hline
|
||||
Команда & \shortstack{\texttt{set.c}\\user, с} & \shortstack{\texttt{set.c}\\system, с} & \shortstack{\texttt{set9.c}\\user, с} & \shortstack{\texttt{set9.c}\\system, с} & \shortstack{Ускорение\\по user} & \shortstack{Ускорение\\по user+system} \\
|
||||
Команда & \shortstack{\texttt{set.c}\\user, с} & \shortstack{\texttt{set.c}\\system, с} & \shortstack{\texttt{set9.c}\\user, с} & \shortstack{\texttt{set9.c}\\system, с} & \shortstack{Коэффициент\\по user} & \shortstack{Коэффициент\\по user+system} \\
|
||||
\hline
|
||||
\texttt{-s check} & 0.453 & 0.033 & 0.503 & 0.040 & 0.901× & 0.896× \\
|
||||
\texttt{-s autoremove} & 0.763 & 0.040 & 0.810 & 0.040 & 0.942× & 0.945× \\
|
||||
\texttt{-s install rpm-build} & 1.210 & 0.050 & 1.283 & 0.050 & 0.943× & 0.945× \\
|
||||
\texttt{-s install openuds-server} & 3.747 & 0.080 & 3.897 & 0.083 & 0.962× & 0.961× \\
|
||||
\texttt{-s install password-store} & 1.233 & 0.050 & 1.310 & 0.050 & 0.941× & 0.944× \\
|
||||
\texttt{-s check} & 0.453 & 0.033 & 0.503 & 0.040 & 0.901$\times$ & 0.896$\times$ \\
|
||||
\texttt{-s autoremove} & 0.763 & 0.040 & 0.810 & 0.040 & 0.942$\times$ & 0.945$\times$ \\
|
||||
\texttt{-s install rpm-build} & 1.210 & 0.050 & 1.283 & 0.050 & 0.943$\times$ & 0.945$\times$ \\
|
||||
\texttt{-s install openuds-server} & 3.747 & 0.080 & 3.897 & 0.083 & 0.962$\times$ & 0.961$\times$ \\
|
||||
\texttt{-s install password-store} & 1.233 & 0.050 & 1.310 & 0.050 & 0.941$\times$ & 0.944$\times$ \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\endgroup
|
||||
\end{center}
|
||||
|
||||
Во всех пяти завершённых сценариях \texttt{set9.c} медленнее исходной реализации: суммарное процессорное время увеличилось на 4,0--11,6\%. Таким образом, упрощённый декодер с улучшениями сортировки и кэша не компенсировали стоимость более сложного декодера в данной APT-нагрузке. Однако, данная реализация остаётся значительно быстрее работы оригинального \texttt{set.c} с декодированием "в лоб".
|
||||
|
||||
\section*{5. Прочие исследования}
|
||||
|
||||
В данной главе собраны исследования, которые не вошли непосредственно в реализацию новой версии, однако важны своими идеями и результатами.
|
||||
В данной главе собраны исследования, которые не вошли непосредственно в совместимую реализацию, однако важны своими идеями и результатами. Форматы этого раздела несовместимы с существующими \texttt{set:}-строками и требуют регенерации метаданных.
|
||||
|
||||
\subsection*{5.1. Реализация без промежуточной кодировки хэшей}
|
||||
\subsection*{5.1. Хранение хэшей без Golomb--Rice}
|
||||
|
||||
Первое направление - отказаться от \texttt{delta} и \texttt{Golomb-Rice} и хранить отсортированные усечённые хэши почти напрямую. Заметная часть времени при сравнении тратится на декодирование set-строки. Если сделать строку дешевле в декодировании, можно получить выигрыш на холодном сравнении и проигрыш в длине строки.
|
||||
Первое направление --- отказаться от дельт и Golomb--Rice и хранить отсортированные усечённые хэши почти напрямую в Base64. Это уменьшает стоимость декодирования, но увеличивает строку. В микротесте с 1000 предоставляемыми и 500 требуемыми символами при \texttt{bpp=32} длина строки выросла с 3994 до 5340 символов. Построение строки ускорилось: \texttt{set\_fini} занял 105,09 вместо 147,45 мкс. Холодное сравнение заняло 113,04 вместо 129,67 мкс (0,87$\times$ времени \texttt{set9}), а прогретое --- 1,04 вместо 2,99 мкс (0,35$\times$).
|
||||
|
||||
(!!!) Результаты теста
|
||||
Однако результат зависит от нагрузки: при одном требуемом символе прогретое сравнение прямого формата заняло 1,02$\times$ времени \texttt{set9}.
|
||||
|
||||
Также было рассмотрено исользование \texttt{Roaring Bitmap}, однако его эффективность достигается для плотных множеств, что не актуально при работе с хэш-функциями.
|
||||
Также был рассмотрен Roaring Bitmap. В проверенном прототипе на разреженных усечённых хэшах он дал более длинные строки и более медленное сравнение: строка выросла до 19\,924 символов, холодное сравнение стало в 4,50 раза, а прогретое --- в 76,14 раза медленнее \texttt{set9}. Сжатие zstd сократило строку до 14\,126 символов, но холодное и прогретое сравнения остались в 4,96 и 101,86 раза медленнее.
|
||||
|
||||
\subsection*{5.2. Тестирование коллизий хэш-функций}
|
||||
\subsection*{5.2. Тестирование хэш-функций}
|
||||
|
||||
Отдельно проверялась идея заменить \texttt{Jenkins OAAT} на более современную хэш-функцию. По скорости на длинных строках Jenkins действительно проигрывает (!!!)
|
||||
Отдельно проверялась идея заменить Jenkins OAAT на CityHash32 или XXH32. На фиксированных случайных байтовых строках, медиана трёх запусков, Jenkins уступал по скорости: для длины 16 байт получены 22,330 нс/хэш против 10,402 у CityHash32 и 7,792 у XXH32; для 1024 байт --- 1796,485 против 232,598 и 184,234 нс/хэш соответственно.
|
||||
|
||||
Однако при тестировании коллизий оказалость, что все три функции на вероятностной побитовой карте показывают близкие к "идеальным" результаты. (!!!)
|
||||
Однако скорость отдельной хэш-функции не описывает её качества на данных,
|
||||
характерных для \texttt{set:}-строк. Поэтому для Jenkins OAAT, XXH64 и t1ha2
|
||||
была измерена вероятность единицы в каждом из младших 32 битов хэша на корпусе
|
||||
из 577\,509 уникальных C++ ABI-символов, извлечённых из 259 ELF-файлов пяти
|
||||
пакетов ALT p11. Корпус намеренно содержит близкие по префиксу имена: 99,60\%
|
||||
символов имеют с другим символом общий префикс длиной не менее 12 знаков, а
|
||||
95,05\% --- не менее 24 знаков. Среднее отклонение $P(\text{bit}=1)$ от
|
||||
$0{,}5$ равно 0,0475 процентного пункта для Jenkins OAAT, 0,0431 для XXH64 и
|
||||
0,0615 для t1ha2; максимальное отклонение одного бита --- 0,167, 0,122 и
|
||||
0,188 процентного пункта соответственно. Следовательно, все три функции
|
||||
достаточно равномерно смешивают проверенный корпус; только по этой метрике
|
||||
замена Jenkins OAAT не получает убедительного обоснования.
|
||||
|
||||
Следовательно, в проверенной метрике устойчивого преимущества замены Jenkins не обнаружено. Кроме того, новая хэш-функция изменила бы все значения в wire-format и потребовала бы новой версии формата и пересоздания репозиторных метаданных.
|
||||
|
||||
\section*{6. Итоги}
|
||||
|
||||
(нужны ли..)
|
||||
Работа позволила восстановить назначение и инварианты \texttt{set:}-формата, описать пути исходного \texttt{lib/set.c} и подготовить совместимую реализацию \texttt{set9.c}. Проверка на Sisyphus и \texttt{SELF\_TEST} подтверждают корректную обработку исследованного корпуса и публичного API. Вместе с тем APT-измерения показали отрицательный результат по производительности: более читаемая и структурированная реализация не превзошла специализированный декодер исходного кода.
|
||||
|
||||
Эксперименты с прямым хранением хэшей, Roaring Bitmap и альтернативными хэш-функциями уточнили границы дальнейшей оптимизации.
|
||||
|
||||
\endgroup
|
||||
|
||||
%%% Local Variables:
|
||||
%%% mode: latex
|
||||
|
||||
Reference in New Issue
Block a user