diff --git a/articles/for-alt/article.tex b/articles/for-alt/article.tex new file mode 100644 index 0000000..5bb5419 --- /dev/null +++ b/articles/for-alt/article.tex @@ -0,0 +1,219 @@ + +% Подключаемый фрагмент по образцу abstract.tex. +% Авторские данные, аннотация и ключевые слова в исходном тексте не заданы. +\title{Исследование и улучшение механизма работы \texttt{set}-строк в ALT RPM} +\maketitle + +\begingroup +\setlength{\emergencystretch}{3em} + +Ориентир на 700 слов, 9к символов +(!!!)/(???) - дополнить/уточнить +всё ещё куда-то надо включить "хэш клёвый, но медленный, вот тесты" +Проверить, чтобы всё, что нужно в \texttt{это} попало + +\section*{1. Зачем нужны set-строки} + +Обычная зависимость от версии библиотеки не гарантирует, что в ней остались все нужные программе символы: символ можно удалить, не сменив SONAME, а одинаковые SONAME могут скрывать разные наборы экспортов. Поэтому ALT RPM использует версии зависимостей вида \texttt{set:}. Для \texttt{Provides} такая строка описывает символы, предоставляемые библиотекой, а для \texttt{Requires} - символы, которые конкретный потребитель требует от неё. В такой конфигурации сравниваются не номера версий, а включение множеств: все хэши символов из \texttt{Requires} должны присутствовать в \texttt{Provides}. +Гарантия вероятностная, поскольку вместо полноценных имён символов хранятся усечённые хэши, но ответ "символ отсутствует", когда он есть мы не получим + +\section*{2. Структура 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-строку. +\end{enumerate} + +Примечание: при формировании set-строки для \texttt{req} символов, \texttt{bpp} вычисляется по количеству \texttt{prov} символов в актуальной (???) версии библиотеки для избежания сильного усечения хэшей при небольшом количестве требуемых символов. + +Итоговая строка выглядит следующим образом: + +\begin{verbatim} +set: +\end{verbatim} + +\begin{verbatim} +bpp_char = bpp - 7 + 'a' +Mshift_char = Mshift - 7 + 'a' +\end{verbatim} + +Краткая схема: + +\begin{verbatim} +массив строк + | (Jenkins OAAT) + v +массив усечённых хэшей + | (qsort) + v +отсортированный массив хэшей + | (вычисление разницы между элементами) + v +массив delta + | (Rice-Golomb преобразование) + v +битовый массив + | (base62 преобразование) + v +set-строка +\end{verbatim} + +\subsection*{Механизм сравнения set-строк} + +Для проверки включения множества символов \texttt{req} в множество \texttt{prov} необходимо выполнить обратное декодирование следующим образом: + +\begin{verbatim} +set-строка + | (обратное base62 преобразование) + v +битовый массив + | (обратное Rice-Golomb преобразование) + v +массив delta + | (вычисление изначальных значений) + v +массив усечённых хэшей (отсортированный) +\end{verbatim} + +Значения хэшей в массивах приводятся к минимальному \texttt{bpp} из двух set-строк (сохраняется отсортированность и дистинктивность(???)). + +После получения отсортированных массивов хэшей из set-строк, включение символов (а точнее их усечённых хэш-значений) одного множества во второе проверить не составляет труда. + +\section*{3. Практическая реализация} + +Несмотря на описанную структуру set-строки, на практике в текущем \texttt{lib/set.c} применяется множество оптимизаций и улучшений, направленных на ускорение работы \texttt{rpmsetcmp(const char *set1, const char *set2)} (функции, выдающей результат включения множеств). Рассмотрим основные из них. + +\subsection*{3.1. Слитый декодер} + +Вместо описанной выше последовательности декодирования set-строк применяется функция, объединяющая этапы \texttt{base62} и \texttt{golomb}. + +\texttt{decode\_base62\_golomb()} - оптимизированная версия стадий \texttt{decode\_base62} и \texttt{decode\_golomb}. Функция считывает сразу по два байта, с помощью пре-compiled таблицы преобразует их в битовую последовательность, набирая блоки до 24 бит, после декодирует по \texttt{Rice-Golomb}. + +Благодаря такому подходу удаётся ускорить работу алгоритма на сравнении строк в \textasciitilde{}2 раза. (!!!) + +\subsection*{3.2. Кэширование \texttt{Provides} set-строк} + +При передачи первого параметра (\texttt{const char *set1}) в функцию сравнения set-строк (\texttt{rpmsetcmp()}), set-строка кэшируется. +Используется простой LRU кэш (массив размером \texttt{256}), который сохраняет fingerprint оригинальной set-строки, саму set-строку и декодированный массив хэшей. + +При cache\_hit элемент смещается на первую позицию, а при первом попадании попадает на позицию \texttt{min(243, len(cache))}. + +Очевидным недостатком такого подхода является: + +\begin{enumerate} +\item Малый размер кэша + при увеличении размера кэша до 512 элементов, производительность увеличилась на X\% (!!!) + +\item Затраты на \texttt{realloc} при cache\_hit + из-за хранения элементов кэша как массив (а не списком, например), после каждого попадания кэша приходится смещать до 255 записей. +\end{enumerate} + +\subsection*{3.3. Быстрое сравнение включения множеств символов} + +После получения отсортированных и усечённых до одинакового \texttt{bpp} масивов хэшей, необходимо проверить включение множеств. Отметим, что множество \texttt{req} символов будет, как правило, разреженным относительно множества \texttt{prov} символов. + +\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]}. + +Макрос \texttt{IFLT8} с первоначальным прыжком на 8 элементов выбирается при \texttt{len(hash\_arr1) >= 16 * len(hash\_arr2)}. В остальных случаях используется макрос \texttt{IFLT4}. + +Из недостатков данного способа выделяется фиксированная длина прыжка, которую имеет смысл увеличивать пропорционально \texttt{len(hash\_arr1) / len(hash\_arr2)}. + +\section*{4. Дальнейшие оптимизации} + +(???) Говорить ли про python-реализацию вовсе + +Текущая реализация \texttt{lib/set.c} трудночитаемая и труднопонимаемая, а также не содержит некоторых оптимизаций, которые могли бы сильнее ускорить работу библиотеки. + +В связи с этим, было принято решение о реимплементации кода с сохранением совместимости к текущему формату set-строк. + +\subsection*{4.1. Слитый энкодер} + +В прошлом разделе говорилось о ускорении работы функции \texttt{rpmsetcmp()}, однако для части кода отвечающей за создание set-строк как таковых оптимизаций не существует. +Поэтому энкодер в новой версии пропускает стадию создания битового масива и напрямую декодирует base62 строки в массив delta. + +\subsection*{4.2. Общая память под строки} + +Для улучшения encode составляющей также была изменена работа с памятью под символы. В новой версии вместо множества указателей на строки, под каждый из которых требуется свой \texttt{malloc}, введён единый указатель, в котором хранятся все символы последовательно, а индекс начала каждого из символа хранится отдельно. + +Это позволяет снизить часть расходов на \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} - замедление. + +\begin{center} +\begingroup +\scriptsize +\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} \\ +\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× \\ +\hline +\end{tabular} +\endgroup +\end{center} + +\section*{5. Прочие исследования} + +В данной главе собраны исследования, которые не вошли непосредственно в реализацию новой версии, однако важны своими идеями и результатами. + +\subsection*{5.1. Реализация без промежуточной кодировки хэшей} + +Первое направление - отказаться от \texttt{delta} и \texttt{Golomb-Rice} и хранить отсортированные усечённые хэши почти напрямую. Заметная часть времени при сравнении тратится на декодирование set-строки. Если сделать строку дешевле в декодировании, можно получить выигрыш на холодном сравнении и проигрыш в длине строки. + +(!!!) Результаты теста + +Также было рассмотрено исользование \texttt{Roaring Bitmap}, однако его эффективность достигается для плотных множеств, что не актуально при работе с хэш-функциями. + +\subsection*{5.2. Тестирование коллизий хэш-функций} + +Отдельно проверялась идея заменить \texttt{Jenkins OAAT} на более современную хэш-функцию. По скорости на длинных строках Jenkins действительно проигрывает (!!!) + +Однако при тестировании коллизий оказалость, что все три функции на вероятностной побитовой карте показывают близкие к "идеальным" результаты. (!!!) + +\section*{6. Итоги} + +(нужны ли..) + +\endgroup + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "../main" +%%% End: