new chapter
This commit is contained in:
@@ -189,7 +189,7 @@ B=\sum_{i=1}^{n}\ell_i
|
|||||||
\section{Построение \texttt{set:}-строк в \texttt{set.c}}
|
\section{Построение \texttt{set:}-строк в \texttt{set.c}}
|
||||||
\label{CONSTRUCTION}
|
\label{CONSTRUCTION}
|
||||||
|
|
||||||
Благодаря сценариям автозависимостей \texttt{rpm-build} утилита \texttt{mkset} получает символы из ELF-файлов и далее импользует \texttt{lib/set.c}, вызывая интерфейс построения множества:
|
Благодаря сценариям автозависимостей \texttt{rpm-build} утилита \texttt{mkset} получает символы из ELF-файлов и далее использует \texttt{lib/set.c}, вызывая интерфейс построения множества:
|
||||||
\[
|
\[
|
||||||
\texttt{set\_new()}\ \longrightarrow\
|
\texttt{set\_new()}\ \longrightarrow\
|
||||||
\texttt{set\_add()}\ \longrightarrow\
|
\texttt{set\_add()}\ \longrightarrow\
|
||||||
@@ -221,6 +221,73 @@ M_{\mathrm{build}}=O(L+n+B).
|
|||||||
\]
|
\]
|
||||||
Главным асимптотическим слагаемым для больших наборов остаётся \texttt{qsort()}, тогда как большое число отдельных копирований строк и наличие промежуточного битового массива влияют на постоянные затраты. Это определяет два независимых направления дальнейшего улучшения: замена сортировки для крупных множеств и потоковое кодирование без промежуточных представлений. При этом любые изменения должны сохранять байтовую совместимость формата.
|
Главным асимптотическим слагаемым для больших наборов остаётся \texttt{qsort()}, тогда как большое число отдельных копирований строк и наличие промежуточного битового массива влияют на постоянные затраты. Это определяет два независимых направления дальнейшего улучшения: замена сортировки для крупных множеств и потоковое кодирование без промежуточных представлений. При этом любые изменения должны сохранять байтовую совместимость формата.
|
||||||
|
|
||||||
|
\section{Сравнение \texttt{set:}-строк}
|
||||||
|
\label{COMPARISON}
|
||||||
|
|
||||||
|
Функция \texttt{rpmsetcmp()} получает две строки и определяет отношение включения между закодированными множествами. В штатном пути RPM первым операндом передаётся \texttt{Provides}, а вторым --- \texttt{Requires}. Возвращаемые значения различают четыре результата: 1, если первое множество строго содержит второе; 0 при равенстве; $-1$, если первое множество строго содержится во втором; $-2$, если множества несравнимы по включению. Ошибки декодирования первого и второго операндов отображаются соответственно в коды $-3$ и $-4$.
|
||||||
|
|
||||||
|
\subsection{Обратное декодирование и нормализация точности}
|
||||||
|
|
||||||
|
После удаления необязательного префикса \texttt{set:} проверяются параметры $b$ и $m$, а затем выполняется обратная цепочка преобразований
|
||||||
|
\[
|
||||||
|
\begin{aligned}
|
||||||
|
\text{Base62-строка}
|
||||||
|
&\longrightarrow \text{битовый поток}
|
||||||
|
\longrightarrow \text{дельты Голомба--Райса},\\
|
||||||
|
&\longrightarrow \text{возрастающий массив хешей}.
|
||||||
|
\end{aligned}
|
||||||
|
\]
|
||||||
|
Логически это преобразование обратно рассмотренному в разд.~\Ref{FORMAT}. Однако исходный \texttt{lib/set.c} не создаёт отдельный битовый массив: функция \texttt{decode\_base62\_golomb()} объединяет первые две стадии. Она считывает обычные символы попарно, преобразует их предварительно вычисленной таблицей и обрабатывает блоки до 24 бит. Частное и остаток кода Голомба--Райса восстанавливаются непосредственно из этих блоков. После этого единственный линейный проход суммирует дельты и получает исходные усечённые хеши.
|
||||||
|
|
||||||
|
Две строки могут иметь разные значения \texttt{bpp}. Сравнение выполняется при общей точности
|
||||||
|
\[
|
||||||
|
b_* = \min(b_P,b_R).
|
||||||
|
\]
|
||||||
|
Для этого более точный массив последовательно проецируется на один бит вниз. На одном шаге применяется отображение
|
||||||
|
\[
|
||||||
|
\pi_t(x)=x\bmod 2^t.
|
||||||
|
\]
|
||||||
|
После удаления старшего бита исходный возрастающий массив распадается на две возрастающие части. Обе части сливаются с сохранением возрастания элементов, а появившиеся совпадения удаляются. Поэтому после понижения точности сохраняются одновременно сортировка и представление множества без повторов. При разности точностей $d=|b_P-b_R|$ слияние повторяется $d$ раз.
|
||||||
|
|
||||||
|
В результате сравниваются два возрастающих массива
|
||||||
|
\[
|
||||||
|
\widehat P=H_{b_*}(P),\qquad
|
||||||
|
\widehat R=H_{b_*}(R).
|
||||||
|
\]
|
||||||
|
|
||||||
|
Понижение \texttt{bpp} необходимо, так как сравнение множеств хэшей отличающейся точности невозможно.
|
||||||
|
|
||||||
|
\subsection{Кэш декодированных множеств}
|
||||||
|
|
||||||
|
Декодирование длинной строки существенно дороже проверки нескольких уже готовых целых значений. Кроме того, один поставщик обычно сопоставляется с требованиями нескольких пакетов. Поэтому исходная реализация кэширует декодированный первый операнд, то есть в обычном вызове множество \texttt{Provides}.
|
||||||
|
|
||||||
|
Кэш состоит из двух массивов по 256 элементов: коротких отпечатков и указателей на записи. Отпечаток формируется из трёх байтов строки и служит только предварительным фильтром. Корректность попадания подтверждается полным сравнением исходной строки. Запись одним выделением памяти хранит декодированный массив, его длину и копию строки.
|
||||||
|
|
||||||
|
Поиск выполняется линейно. При попадании запись перемещается в начало двумя вызовами \texttt{memmove()}. Если кэш заполнен, последняя запись освобождается, а новый элемент вставляется на позицию 243 (\texttt{PIVOT\_SIZE}), после чего хвост обоих массивов также сдвигается. Такая схема приближает LRU-политику, но не является строгим LRU: новая, ещё не подтвердившая полезность запись помещается около конца кэша, тогда как повторно использованная попадает в начало.
|
||||||
|
|
||||||
|
Второй операнд исходная реализация каждый раз декодирует во временный массив на стеке. Кроме того, кэш хранит результат при исходном \texttt{bpp}; если для конкретного сравнения его требуется понизить, проекция вычисляется заново. Следовательно, кэш особенно полезен для потока сравнений одного \texttt{Provides} с разными \texttt{Requires} одинаковой точности, но не устраняет стоимость декодирования требований и нормализации.
|
||||||
|
|
||||||
|
\subsection{Проверка включения возрастающих массивов}
|
||||||
|
|
||||||
|
Наивная проверка $\widehat R\subseteq\widehat P$ последовательно продвигает указатель по $\widehat P$ до очередного требуемого значения. Исходная реализация одновременно вычисляет оба отношения включения с помощью флагов \texttt{ge} и \texttt{le}. Это позволяет одним проходом получить все четыре значения публичного API, а не только ответ на типичный для RPM вопрос $\widehat R\subseteq\widehat P$.
|
||||||
|
|
||||||
|
Для ускорения поиска используются макросы \texttt{IFLT4} и \texttt{IFLT8}. Первый перемещает указатель по массиву первого операнда блоками по четыре элемента, после превышения искомого значения возвращается на два элемента и уточняет позицию единичными шагами. Второй выполняет ту же схему с начальным шагом восемь и уточнениями 4, 2 и 1. В конец массива добавляются восемь значений-сентинелов $\mathtt{UINT\_MAX}$, благодаря чему пробный прыжок за последний настоящий элемент остаётся допустимым обращением к памяти. Макрос \texttt{IFLT8} выбирается при $p\geq16r$, в остальных случаях используется \texttt{IFLT4}.
|
||||||
|
|
||||||
|
Такой выбор учитывает разреженность требований, установленную в разд.~\Ref{PROBLEM}, но лишь двумя фиксированными режимами. При медианном $p/r=28{,}1$ шаг восемь уже применим, тогда как при $p/r=257$ он всё ещё остаётся равным восьми и не отражает фактическое среднее расстояние между требуемыми значениями.
|
||||||
|
|
||||||
|
Пусть $s_P$ и $s_R$ --- длины payload двух строк, а $d=|b_P-b_R|$. Холодное сравнение имеет оценку
|
||||||
|
\[
|
||||||
|
T_{\mathrm{cold}}=
|
||||||
|
O(s_P+s_R)+O\bigl(d\max(p,r)\bigr)+O(p+r).
|
||||||
|
\]
|
||||||
|
Поскольку формат ограничивает $10\leq b\leq32$, величина $d$ ограничена константой, и итоговая асимптотика линейна по размеру входных строк и декодированных множеств. При попадании первого операнда в кэш исчезает его декодирование, но остаются линейный поиск по кэшу, проверка строки, декодирование второго операнда, возможная нормализация и проход по массивам:
|
||||||
|
\[
|
||||||
|
T_{\mathrm{hit}}=
|
||||||
|
O(C+s_P+s_R)+O\bigl(d\max(p,r)\bigr)+O(p+r),
|
||||||
|
\qquad C=256.
|
||||||
|
\]
|
||||||
|
Здесь член $s_P$ отражает окончательную проверку ключа; на практике она выполняется только для записей с совпавшим коротким отпечатком. Дополнительная память одного холодного вызова составляет $O(p+r)$, не считая сохраняемого в процессе кэша первого операнда.
|
||||||
|
|
||||||
\References
|
\References
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\item
|
\item
|
||||||
|
|||||||
Reference in New Issue
Block a user