这个问题起源于我几乎在this question下面写的评论,其中Zack正在计算一个大数模数的阶乘(为了这个问题我们将假设为素数)。 Zack使用传统的阶乘计算,在每次乘法时取余数。
我几乎评论说,考虑的另一种选择是Montgomery multiplication,但是考虑更多,我只看到这种技术用于加速相同被乘数的几次乘法(特别是,为了加快计算速度) a n mod p)。
我的问题是:蒙哥马利乘法可以用来加速n的计算! mod p用于大n和p?
答案 0 :(得分:2)
天真,没有;你需要将产品的n个项中的每一个转换为“蒙哥马利空间”,所以你有n个完全缩减mod m,与“通常”算法相同。
但是,因子不仅仅是n项的任意乘积;它更有条理。特别是,如果您已经拥有“Montgomerized”kr mod m
,那么您可以使用非常便宜的缩减来获得(k+1)r mod m
。
所以这是完全可行的,尽管我之前没有看过它。我继续写了一个快速而肮脏的实现(非常未经测试,我根本不相信它):
// returns m^-1 mod 2**64 via clever 2-adic arithmetic (http://arxiv.org/pdf/1209.6626.pdf)
uint64_t inverse(uint64_t m) {
assert(m % 2 == 1);
uint64_t minv = 2 - m;
uint64_t m_1 = m - 1;
for (int i=1; i<6; i+=1) { m_1 *= m_1; minv *= (1 + m_1); }
return minv;
}
uint64_t montgomery_reduce(__uint128_t x, uint64_t minv, uint64_t m) {
return x + (__uint128_t)((uint64_t)x*-minv)*m >> 64;
}
uint64_t montgomery_multiply(uint64_t x, uint64_t y, uint64_t minv, uint64_t m) {
return montgomery_reduce(full_product(x, y), minv, m);
}
uint64_t montgomery_factorial(uint64_t x, uint64_t m) {
assert(x < m && m % 2 == 1);
uint64_t minv = inverse(m); // m^-1 mod 2**64
uint64_t r_mod_m = -m % m; // 2**64 mod m
uint64_t mont_term = r_mod_m;
uint64_t mont_result = r_mod_m;
for (uint64_t k=2; k<=x; k++) {
// Compute the montgomerized product term: kr mod m = (k-1)r + r mod m.
mont_term += r_mod_m;
if (mont_term >= m) mont_term -= m;
// Update the result by multiplying in the new term.
mont_result = montgomery_multiply(mont_result, mont_term, minv, m);
}
// Final reduction
return montgomery_reduce(mont_result, minv, m);
}
并将其与通常的实施进行对比:
__uint128_t full_product(uint64_t x, uint64_t y) {
return (__uint128_t)x*y;
}
uint64_t naive_factorial(uint64_t x, uint64_t m) {
assert(x < m);
uint64_t result = x ? x : 1;
while (x --> 2) result = full_product(result,x) % m;
return result;
}
反对通常的实现与一些内联asm来修复一个小的低效率:
uint64_t x86_asm_factorial(uint64_t x, uint64_t m) {
assert(x < m);
uint64_t result = x ? x : 1;
while (x --> 2) {
__asm__("mov %[result], %%rax; mul %[x]; div %[m]"
: [result] "+d" (result) : [x] "r" (x), [m] "r" (m) : "%rax", "flags");
}
return result;
}
我的Haswell笔记本电脑的结果如下:x:
implementation speedup
---------------------------
naive 1.00x
x86_asm 1.76x
montgomery 5.68x
所以这确实看起来确实很不错。蒙哥马利实现的代码是相当不错的,但也可以通过手写程序集进一步改进。
对于“适度”x和m,这是一种有趣的方法。一旦x变大,x中具有亚线性复杂度的各种方法必然会胜出; factorial具有如此多的结构,以至于这种方法没有利用。