Фон
Недавно я взял старый код (~ 1998 г.) и переписал его, чтобы улучшить производительность. Раньше в базовых структурах данных c для состояния я хранил элементы в нескольких массивах, а теперь я использую необработанные биты (для случаев, когда требуется менее 64 бит). То есть, раньше у меня был массив из b
элементов, а теперь у меня b
битов, установленных в одном 64-битном целом числе, которые указывают, является ли это значение частью моего состояния.
Использование встроенных функций, таких как _pext_u64
и _pdep_u64
Мне удалось выполнить все операции в 5-10 раз быстрее. Я работаю над последней операцией, которая связана с вычислением идеальной функции ha sh.
Точные детали функции ha sh не слишком важны, но все сводится к вычислению биномиальные коэффициенты (n choose k
- n!/((n-k)!k!)
для различных n
и k
. В моем текущем коде для этого используется большая таблица поиска, которую, вероятно, трудно значительно ускорить самостоятельно (за исключением возможных промахов кеша в таблицу, которую я не измерял).
Но я подумал, что с помощью инструкций SIMD я смогу напрямую вычислить их для нескольких состояний параллельно и, таким образом, увидеть общий прирост производительности.
Некоторые ограничения:
- В каждом 64-битном состоянии всегда установлено ровно
b
бит (представляющих небольшие числа). - Значение
k
в биномиальных коэффициентах относится к b
и изменяется равномерно в процессе вычислений. Эти значения небольшие (в большинстве случаев <= 5). </li> - Окончательное значение ha sh будет <15 миллионов ( легко помещается в 32 бита). </li>
Итак, я могу довольно легко выписать математику для выполнения этого параллельно и для сохранения всех операций в виде целочисленного множественного / деления без остатков, сохраняя при этом 32 бита. Общий поток:
- Извлечь биты в значения, подходящие для инструкций SIMD.
- Выполнить вычисление
n choose k
таким образом, чтобы избежать переполнения. - Извлечь выводит окончательное значение ha sh из каждой записи
Но я не писал код SIMD раньше, поэтому я все еще в курсе всех доступных функций и их недостатков / эффективности .
Пример:
Раньше у меня были бы данные в массиве, если бы всегда было 5 элементов:
[3 7 19 31 38]
Теперь я использую для этого одно 64-битное значение:
0x880080088
Это делает многие другие операции очень эффективными. Для идеального ha sh мне нужно эффективно вычислить что-то вроде этого (используя c
для выбора):
(50c5)-(38c5) + (37c4)-(31c4) + (30c3)-(19c3) + ...
Но на практике у меня есть куча из них, чтобы вычислить, только с немного разными значениями:
(50c5)-(Xc5) + ((X-1)c4)-(Yc4) + ((Y-1)c3)-(Zc3) + ...
Все X / Y / Z ... будут разными, но форма расчета идентична для каждого.
Вопросы:
Разумна ли моя интуиция по поводу повышения эффективности за счет преобразования в операции SIMD? ( Некоторые источники предлагают «нет» , но это проблема вычисления одного коэффициента, а не выполнения нескольких параллельно.)
Есть ли что-то более эффективное, чем повторение _tzcnt_u64
вызывает извлечение битов в структуры данных для операций SIMD? (Например, я мог бы временно разбить свое 64-битное представление состояния на 32-битные фрагменты, если бы это помогло, но тогда мне не гарантировалось бы, что в каждом элементе будет установлено одинаковое количество битов.)
Каковы лучшие встроенные функции для вычисления нескольких последовательных операций умножения / деления для биномиальных коэффициентов, когда я знаю, что переполнения не будет. (Когда я просматриваю ссылки Intel, у меня возникают проблемы с быстрой интерпретацией наименования при просмотре всех вариантов - неясно, доступно ли то, что я хочу.)
Если прямое вычисление коэффициентов вряд ли будет эффективным, можно ли использовать инструкции SIMD для параллельного поиска в моей предыдущей таблице поиска коэффициентов?
несколько вопросов вместе, но, учитывая конкретный c контекст, я подумал, что было бы лучше объединить их в один.)