蒙哥马利乘法可以用来加速(大数)的计算! %(一些素数)

时间:2014-07-20 12:24:56

标签: modulo factorial modular-arithmetic montgomery-multiplication

这个问题起源于我几乎在this question下面写的评论,其中Zack正在计算一个大数模数的阶乘(为了这个问题我们将假设为素数)。 Zack使用传统的阶乘计算,在每次乘法时取余数。

我几乎评论说,考虑的另一种选择是Montgomery multiplication,但是考虑更多,我只看到这种技术用于加速相同被乘数的几次乘法(特别是,为了加快计算速度) a n mod p)。

我的问题是:蒙哥马利乘法可以用来加速n的计算! mod p用于大n和p?

1 个答案:

答案 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具有如此多的结构,以至于这种方法没有利用。