Примечания по оптимизации
Оба алгоритма сортировки по хэш-сетам и основам сортировки можно оптимизировать, отметив три факта:
- Нечетные и четные значения могут обрабатываться отдельно
- Вычисление целочисленного квадратного корня - очень быстрая операция (обычно состоит из 3-5 делений и нескольких сложений)
- Локальность кэша важна для обоих этих алгоритмов
Приведенные ниже оптимизированные алгоритмы обычно работают в 5 раз быстрее и используют менее половины ОЗУ в неоптимизированном случае. В некоторых случаях, когда размер данных подобен размеру кэша L2 / L3, они могут работать в 100 раз быстрее или более.
Оптимизированный алгоритм на основе радикальной сортировки
Структура данных состоит из пяти списков целых чисел: IN, Aodd, Bodd, Aeven, Beven
Списки A и B используют половину целочисленного размера IN. (например, если IN = 64 бита, A & B = 32 бита)
- Сканирование списка IN, чтобы найти самые большие нечетные и четные числа MAXodd и MAXeven
- Пусть LIMITodd = этаж (sqrt (MAXodd))
- Let LIMITeven = floor (sqrt (MAXeven))
- Для каждого номера в списке IN: a. Вычислить квадратный корень, если положительный. Если точно, добавьте квадратный корень в список Aodd / Aeven. б. Если число> = 0 и <= LIMITodd / LIMITeven, добавьте его в список Bodd / Beven </li>
- Список сортировки по радиксу Aodd и Bodd с использованием всего лишь log2 (LIMITodd) битов
- Линейное сканирование Аодда и Бодда на совпадение
- Список сортировки Radix Aeven и Beven, использующий только log2 (LIMITeven) биты
- Линейное сканирование Эйвена и Бевена на совпадение
Если при линейном сканировании совпадение найдено, немедленно верните это совпадение.
Причина, по которой это намного быстрее, чем простой алгоритм сортировки по основанию, заключается в том, что:
- Как правило, сортируемые массивы имеют менее 1/4 числа значений и требуют только половину числа бит на целое число, так что в общей сложности менее 1/8 ОЗУ используется в данном виде, что хорошо для кеш.
- Радикальная сортировка выполняется на гораздо меньшем количестве битов, что приводит к меньшему количеству проходов, поэтому, даже если она превышает ваш кэш L1 или L2, вы читаете ОЗУ меньше и вы читаете гораздо меньше ОЗУ
- Линейное сканирование обычно выполняется намного быстрее, поскольку список A содержит только точные квадратные корни, а список B содержит только небольшие значения
Оптимизированный алгоритм на основе хэширования
Структура данных - это список целых чисел IN, плюс два хэш-набора A и B
Наборы A и B используют половину целочисленного размера IN
- Сканирование списка IN, чтобы найти самые большие нечетные и четные числа MAXodd и MAXeven
- Пусть LIMITodd = floor (sqrt (MAXodd))
- Let LIMITeven = floor (sqrt (MAXeven))
- Для каждого нечетного числа в списке IN: a. Вычислить квадратный корень, если положительный. Если точно, проверьте, существует ли квадратный корень в B, и верните, если true, в противном случае добавьте его в A. b. Если число> = 0 и <= LIMITodd / LIMITeven, проверьте, существует ли оно в A, и верните, если true, в противном случае добавьте его в B. </li>
- Очистите A и B и повторите шаг 4 для четных чисел
Причина, по которой это быстрее, чем простой алгоритм хэширования, заключается в том, что:
- Хэш-набор обычно составляет 1/8 объема ОЗУ, что приводит к гораздо лучшей производительности кеша
- Только точные квадраты и маленькие числа имеют записи хэш-набора, поэтому гораздо меньше времени тратится на хэширование и добавление / удаление значений
Здесь доступна дополнительная небольшая оптимизация: A и B могут быть одним хэш-набором, в котором битовый флаг хранит каждую запись, чтобы указать, находится ли целое число в A или B (его не может быть в обоих, потому что тогда алгоритм был бы прекращен).