Оптимизация умножения по модулю малого простого числа
Мне нужно выполнить следующую операцию много раз:
- Возьмем два целых числа
a, b
- Вычислить
a * b mod p
, где p = 1000000007
и a, b
имеют тот же порядок величины, что и p
Чувство моей кишки наивное
result = a * b
result %= p
неэффективен. Могу ли я оптимизировать умножение по модулю p
так же, как возведение в степень по модулю p
оптимизировано с помощью pow(a, b, p)
?
Ответы
Ответ 1
Чтобы сделать это вычисление в сборке, но его можно вызвать из Python, я бы
попробуйте встроенную сборку с
Модуль Python, написанный на C.
Оба GCC и
MSVC
компиляторы имеют встроенную сборку, только с различным синтаксисом.
Заметим, что наш модуль p = 1000000007
просто вписывается в 30 бит. Результат
желаемый (a*b)%p
может быть вычислен в регистрах Intel 80x86, учитывая некоторые слабые
ограничения на a,b
не намного больше, чем p
.
Ограничения по размеру a,b
(1) a,b
- это 32-разрядные целые числа без знака
(2) a*b
меньше p << 32
, т.е. p
раз 2 ^ 32
В частности, если a,b
меньше, чем 2*p
, можно избежать переполнения.
Учитывая (1), также достаточно, чтобы либо один из них был меньше p
.
Инструкция Intel 80x86 MUL может умножать два 32-разрядных целых без знака
и сохраните 64-битный результат в пачке регистра аккумуляторов EDX: EAX. Некоторые
детали и причуды MUL обсуждаются в Разделе 10.2.1 этого полезного
summary.
Затем команда DIV может разделить этот 64-битный результат на 32-битную константу
(модуль p
), сохраняя фактор в EAX и остаток в EDX.
См. Раздел 10.2.2 последней ссылки. В результате мы хотим, чтобы остаток.
Именно эта инструкция DIV разделения влечет за собой риск переполнения, должна
64-разрядный продукт в числителе EDX: EAX дает коэффициент больше 32 бит
не выполнив (2) выше.
Я работаю над фрагментом кода в сборке C/inline для "доказательства концепции".
Однако максимальная выгода от скорости будет зависеть от пакетных массивов
данных a,b
для обработки, амортизации накладных вызовов функций и т.д. в
Python (если это целевая платформа).
Ответ 2
Вы отмечаете, что "a, b
имеют тот же порядок величины, что и p". Часто в криптографии это означает, что a,b
являются большими числами вблизи p
, но строго меньше, чем p
.
Если это так, то вы можете использовать простой идентификатор
![a-p \equiv a \pmod{p}]()
чтобы превратить ваш расчет в
result = ((a-p)*(b-p))%p
Затем вы превратили одно большое умножение в два больших вычитания и небольшое умножение. Вам нужно будет просмотреть профиль, который будет быстрее.
Ответ 3
Это не отвечает на вопрос напрямую, но я бы рекомендовал не делать этого в чистом Python, если вы ищете производительность. Некоторые параметры:
- Создайте небольшую библиотеку в C, которая выполняет ваши вычисления, и используйте Python
ctypes
, чтобы поговорить с ней.
- Используйте numpy; вероятно, лучший вариант, если вы хотите не вмешиваться в компиляцию материала самостоятельно. Выполнение операций по одному не будет быстрее, чем собственные операторы Python, но если вы можете поместить несколько из массива numpy, вычисления на них будут намного быстрее, чем эквивалент в Python.
- Используйте cython, чтобы объявить переменные как целые числа; снова, так же, как numpy, вы выиграете от этого больше всего, если будете делать это партиями (потому что тогда вы также можете оптимизировать цикл).
Ответ 4
Хотя это тривиально просто, вы можете попробовать его и сэкономить некоторое время на шаге mod p
, построив список продуктов на основе 1000000007
(размер списка зависит от размера a
и b
). Испытайте по модулю по каждому из них (начиная с самого высокого). Конечно, это помогает, если a & b >= sqrt(p) * 2
.
Ответ 5
Может быть ключ к оптимизации, если вы пояснили, что вы подразумеваете под many раз, например, если вы собирали результаты из высокочастотного цикла, loop может предложить средства для оптимизации вашей процедуры.
Скажем, что безоптимированный цикл был:
p = 1000000007
b = 123456789
a = 0
while a < p:
result = (a * b) % p
dosomething(a, b, result)
a += 1
вы можете оптимизировать * и% из высокочастотного цикла:
p = 1000000007
b = 123456789
a = 0
result = (a * b) % p
while a < p:
dosomething(a, b, result)
a += 1
result += b
if result >= p:
result -= p