WIP
This commit is contained in:
@@ -336,6 +336,155 @@ O\left(\frac pj+r\log j\right).
|
||||
\]
|
||||
При характерном $j\approx p/r$ первое слагаемое имеет порядок $r$. В отличие от фиксированного выбора между шагами 4 и 8, величина перехода адаптируется к наблюдаемому отношению мощностей.
|
||||
|
||||
\section{Результаты экспериментальной проверки}
|
||||
\label{RESULTS}
|
||||
|
||||
Проверка экспериментальной реализации проводилась в два этапа. Сначала проверялась корректность формата и операций над множествами, затем измерялась производительность в сценариях пакетного менеджера.
|
||||
|
||||
\subsection{Дифференциальное тестирование на синтетических множествах}
|
||||
|
||||
Для проверки совместимости был разработан дифференциальный тест, не использующий заранее подготовленные \texttt{set:}-строки. На каждой итерации он самостоятельно формировал исходное множество из случайных уникальных строк. Число символов выбиралось в диапазоне от 1 до 1000, длина каждого имени --- от 1 до 100 знаков, а параметр $bpp$ --- от 10 до 32. В алфавит входили латинские буквы, цифры и знаки, встречающиеся в именах экспортируемых символов: точка, знак \texttt{@} и подчёркивание.
|
||||
|
||||
Из исходного множества строилось второе множество с заранее известным отношением к первому. Тест охватывал четыре класса входов: равные множества, строгое включение, несравнимые множества и некорректные \texttt{set:}-строки. Для проверки строгого включения из первого множества удалялась случайная непустая часть элементов. В случае несравнимости после удаления добавлялись новые символы, отсутствующие в первом множестве. Некорректная строка генерировалась случайным набором символов, в таком случае был шанс получить корректную строку, но для нас остаётся важным одинаковый результат двух программ.
|
||||
|
||||
Один и тот же набор исходных символов независимо передавался построителю исходного \texttt{set.c} и построителю \texttt{set9.c}. Таким образом, каждая реализация сама выполняла хеширование, сортировку и кодирование, после чего её вариант \texttt{rpmsetcmp()} сравнивал полученную пару строк. Тест сопоставлял наблюдаемый результат публичного API: $1$ для строгого надмножества, $0$ для равенства, $-1$ для строгого подмножества, $-2$ для несравнимости и $-3$ или $-4$ для ошибки декодирования соответствующего операнда. Для случая включения операнды дополнительно менялись местами, что позволяло одновременно проверить результаты $1$ и $-1$.
|
||||
|
||||
Генерация и сравнение выполнялись циклически до ручной остановки теста. Такой подход проверял эквивалентность реализаций на широком диапазоне мощностей и точностей.
|
||||
|
||||
\subsection{Производительность в сценариях APT}
|
||||
|
||||
Для измерения времени использовались две локальные сборки \texttt{librpm}: с исходным \texttt{set.c} и с \texttt{set9.c}. На каждой сборке трижды выполнялись одинаковые успешно завершившиеся симуляции APT. В табл.~\Ref{APT_RESULTS} приведены средние значения процессорного времени пользователя и ядра. Время ожидания и ввода-вывода в эти величины не входит.
|
||||
|
||||
Коэффициент вычислялся по суммарному процессорному времени:
|
||||
\[
|
||||
K_{\mathrm{CPU}}=
|
||||
\frac{U_{\mathrm{original}}+S_{\mathrm{original}}}
|
||||
{U_{\mathrm{set9}}+S_{\mathrm{set9}}}.
|
||||
\]
|
||||
Значение $K_{\mathrm{CPU}}>1$ означает ускорение, а $K_{\mathrm{CPU}}<1$ --- замедление экспериментальной реализации.
|
||||
|
||||
\begin{table}[H]
|
||||
\begin{center}
|
||||
\caption{\label{APT_RESULTS}Сравнение исходного \texttt{set.c} и \texttt{set9.c} в симуляциях APT}
|
||||
\scriptsize
|
||||
\begin{tabular}{|l|r|r|r|r|r|}
|
||||
\hline
|
||||
Сценарий & \multicolumn{2}{c|}{Исходный, с} & \multicolumn{2}{c|}{\texttt{set9.c}, с} & $K_{\mathrm{CPU}}$ \\
|
||||
\cline{2-5}
|
||||
& user & system & user & system & \\
|
||||
\hline
|
||||
\texttt{-s check} & 0,453 & 0,033 & 0,503 & 0,040 & 0,896 \\
|
||||
\hline
|
||||
\texttt{-s autoremove} & 0,763 & 0,040 & 0,810 & 0,040 & 0,945 \\
|
||||
\hline
|
||||
\texttt{-s install rpm-build} & 1,210 & 0,050 & 1,283 & 0,050 & 0,945 \\
|
||||
\hline
|
||||
\texttt{-s install openuds-server} & 3,747 & 0,080 & 3,897 & 0,083 & 0,961 \\
|
||||
\hline
|
||||
\texttt{-s install password-store} & 1,233 & 0,050 & 1,310 & 0,050 & 0,944 \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\end{table}
|
||||
|
||||
Во всех пяти измеренных сценариях \texttt{set9.c} не превзошёл исходный вариант: суммарное процессорное время увеличилось примерно на 4,0--11,6\%. Это отрицательный результат, вызванный более простым потоковым декодером, который не превзошёл специализированного табличного декодера исходной реализации даже при условии остальных оптимизаций.
|
||||
|
||||
Для проверки этого объяснения исходный слитый декодер отдельно сравнивался с вариантом, в котором Base62, код Голомба--Райса и восстановление дельт выполнялись последовательными стадиями. В зависимости от сценария слитый путь сокращал суммарное процессорное время в 1,47--2,82 раза. Следовательно, благодаря табличной обработке Base62/Golomb получается измеримый выигрыш. Практическое направление дальнейшей работы может состоять в разработке нового формата хранения и обработки \texttt{set:}-строк.
|
||||
|
||||
\section{Варианты со сменой формата или API}
|
||||
\label{ALTERNATIVE_FORMATS}
|
||||
|
||||
Оптимизации разд.~\Ref{COMPATIBLE_OPTIMIZATIONS} ограничены требованием побайтовой совместимости. Параллельно исследовались представления, снимающие это ограничение. Их результаты нельзя напрямую переносить на существующие RPM-метаданные: новый формат требует повторного формирования зависимостей репозитория.
|
||||
|
||||
\subsection{Roaring Bitmap: отрицательный результат}
|
||||
|
||||
Roaring Bitmap предназначен прежде всего для множеств, содержащих плотные участки целочисленного пространства. Усечённые хеши, напротив, распределены по диапазону приблизительно равномерно. Поэтому контейнеры bitmap не получают длинных серий соседних значений, но сохраняют собственные заголовки и индексы.
|
||||
|
||||
В микротесте использовались 1000 предоставляемых и 500 требуемых символов при $b=32$. Результаты приведены в табл.~\Ref{ROARING_RESULTS}. Коэффициенты времени вычислены относительно \texttt{set9.c}; значение больше единицы означает замедление.
|
||||
|
||||
\begin{table}[H]
|
||||
\begin{center}
|
||||
\caption{\label{ROARING_RESULTS}Сравнение Golomb/Base62 и Roaring Bitmap}
|
||||
\small
|
||||
\begin{tabular}{|l|r|r|r|}
|
||||
\hline
|
||||
Представление & Длина строки & Cold compare & Warm compare \\
|
||||
\hline
|
||||
Golomb/Base62 \texttt{set9} & 3\,994 & 1,00 & 1,00 \\
|
||||
\hline
|
||||
Roaring/hex & 19\,924 & 4,50 & 76,14 \\
|
||||
\hline
|
||||
Roaring+zstd & 14\,126 & 4,96 & 101,86 \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\end{table}
|
||||
|
||||
Без дополнительного сжатия строка оказалась длиннее в 4,99 раза. Холодное сравнение заняло 1321,17 мкс вместо 293,87 мкс, а прогретое --- 399,20 мкс вместо 5,24 мкс. При этом построение bitmap занимало лишь $0,76$ времени \texttt{set\_fini()} варианта \texttt{set9}; ускорение генерации не компенсировало стоимость хранения и сравнения.
|
||||
|
||||
Сжатие zstd уменьшило строку до 14\,126 символов, то есть до 3,54 длины \texttt{set9}, но добавило декомпрессию в горячий путь. В отдельном прогоне холодное и прогретое сравнения были медленнее соответственно в 4,96 и 101,86 раза. Таким образом, Roaring Bitmap для равномерных хешей является подтверждённым отрицательным вариантом: структура данных не соответствует распределению кодируемых значений.
|
||||
|
||||
\subsection{Прямые хеши в Base64}
|
||||
|
||||
Другой вариант, обозначенный как D1, исключает дельты и код Голомба--Райса. $n$ отсортированных уникальных хешей записываются подряд по $b$ бит, после чего битовый массив преобразуется в Base64-строку. Если
|
||||
\[
|
||||
Q=\left\lceil\frac{nb}{8}\right\rceil
|
||||
\]
|
||||
--- число байтов упакованного массива, то доминирующая часть длины payload равна
|
||||
\[
|
||||
L_{\mathrm{payload}}=
|
||||
\left\lceil\frac{4Q}{3}\right\rceil,
|
||||
\]
|
||||
а заголовок формата добавляет постоянное число символов. В отличие от Golomb--Rice, эта оценка почти не зависит от расстояний между соседними хешами: цена быстрого произвольного доступа --- увеличение метаданных.
|
||||
|
||||
Для тех же 1000/500 символов при $b=32$ длина строки выросла с 3994 до 5340 символов, то есть на 33,7\%. Результаты теста показаны в табл.~\Ref{DIRECT_RESULTS}; отношение меньше единицы означает, что D1 затратил меньшую долю времени \texttt{set9.c}.
|
||||
|
||||
\begin{table}[H]
|
||||
\begin{center}
|
||||
\caption{\label{DIRECT_RESULTS}Тест прямого представления D1}
|
||||
\small
|
||||
\begin{tabular}{|l|r|r|r|}
|
||||
\hline
|
||||
Операция & \texttt{set9}, мкс & D1, мкс & D1/\texttt{set9} \\
|
||||
\hline
|
||||
\texttt{set\_fini()} & 147,45 & 105,09 & 0,71 \\
|
||||
\hline
|
||||
\texttt{rpmsetcmp()}, cold & 129,67 & 113,04 & 0,87 \\
|
||||
\hline
|
||||
\texttt{rpmsetcmp()}, warm & 2,99 & 1,04 & 0,35 \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\end{table}
|
||||
|
||||
Для крупного требования прямое представление ускорило все три измеряемые операции, особенно попадание в кэш. Однако при одном требуемом символе холодное сравнение занимало 83,22 мкс ($0,88$ времени \texttt{set9}), а прогретое --- 0,86 мкс ($1,02$), то есть преимущество исчезало. Это подчёркивает зависимость результата от формы нагрузки: выигрыш одного сравнения не доказывает ускорение полного потока зависимостей APT. D1 представляет интерес как компромисс между размером и стоимостью декодирования, но требует отдельной сквозной проверки на преобразованных метаданных репозитория.
|
||||
|
||||
\subsection{Альтернативные хеш-функции}
|
||||
|
||||
Отдельно сравнивались Jenkins OAAT, xxHash32 и CityHash32. Сравнение было необходимо, так как xxHash и CityHash --- хэш функции, разработанные значительно позже последнего обновления \texttt{set.c}. В каждом замере использовались три запуска; в табл.~\Ref{HASH_SPEED} приведены медианы. Для коротких строк указано время одного хеширования, для длинных --- пропускная способность.
|
||||
|
||||
\begin{table}[H]
|
||||
\begin{center}
|
||||
\caption{\label{HASH_SPEED}Скорость 32-битных хеш-функций}
|
||||
\small
|
||||
\begin{tabular}{|l|r|r|}
|
||||
\hline
|
||||
Функция & 32 байта, нс/хеш & 1024 байта, ГиБ/с \\
|
||||
\hline
|
||||
Jenkins OAAT & 50,98 & 0,53 \\
|
||||
\hline
|
||||
xxHash32 & 11,56 & 5,18 \\
|
||||
\hline
|
||||
CityHash32 & 16,70 & 4,10 \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\end{table}
|
||||
|
||||
На строках длиной 32 байта xxHash32 был быстрее Jenkins примерно в 4,4 раза, CityHash32 --- в 3,1 раза. При 1024 байтах различие по пропускной способности достигало соответственно 9,8 и 7,7 раза. Следовательно, Jenkins OAAT не является оптимальным по скорости, особенно на длинных входах.
|
||||
|
||||
Для оценки качества использовались
|
||||
|
||||
\References
|
||||
\begin{enumerate}
|
||||
\item
|
||||
|
||||
Reference in New Issue
Block a user