加速大整数的“基本转换”

时间:2010-11-25 04:45:43

标签: c algorithm optimization math

我正在使用基本转换算法从大整数生成置换(分成32位字)。

我使用相对标准的算法:

/* N = count,K is permutation index (0..N!-1) A[N] contains 0..N-1 */
i = 0;
while (N > 1) {
   swap A[i] and A[i+(k%N)]
   k = k / N
   N = N - 1
   i = i + 1
}

不幸的是,每次迭代的除法和模数加起来,尤其是移动到大整数 - 但是,似乎我可以使用乘法!

/* As before, N is count, K is index, A[N] contains 0..N-1 */
/* Split is arbitrarily 128 (bits), for my current choice of N */
/* "Adjust" is precalculated: (1 << Split)/(N!) */
a = k*Adjust; /* a can be treated as a fixed point fraction */
i = 0;
while (N > 1) {
   a = a*N;  
   index = a >> Split;         
   a = a & ((1 << Split) - 1);  /* actually, just zeroing a register */       
   swap A[i] and A[i+index]
   N = N - 1
   i = i + 1
}

这更好,但做大整数乘法仍然很迟钝。

问题1:
有没有办法更快地做到这一点?

EG。既然我知道N *(N-1)小于2 ^ 32,我可以从一个单词中提取这些数字,并合并到“剩余时间”吗? 或者,有没有办法修改一个arithetic解码器,一次拉出一个指标?

问题2:
为了好奇 - 如果我使用乘法将数字转换为基数10而不进行调整,则结果乘以(10 ^位数/ 2 ^移位)。是否有一种棘手的方法来删除使用十进制数字的这个因素?即使有调整因子,这似乎会更快 - 为什么标准库不会使用这个vs divide和mod?

2 个答案:

答案 0 :(得分:2)

看到你在谈论像2 ^ 128 /(N!)这样的数字,似乎在你的问题中N将会相当小(根据我的计算,N <35)。 我建议以原始算法为出发点;首先切换循环的方向:

i = 2;
while (i < N) {
    swap A[N - 1 - i] and A[N - i + k % i]
       k = k / i
       i = i + 1
}

现在更改循环以在每次迭代中执行多个排列。我想,无论数字i,分割的速度都是相同的,只要i 将范围2 ... N-1拆分为子范围,以使每个子范围内的数字乘积小于2 ^ 32:

2, 3, 4, ..., 12: product is 479001600
13, 14, ..., 19:  product is 253955520
20, 21, ..., 26:  product is 3315312000
27, 28, ..., 32:  product is 652458240
33, 34, 35:       product is 39270

然后,将长数k除以产品而不是除以i。每次迭代将产生余数(小于2 ^ 32)和更小的数k。当你有余数时,你可以使用原始算法在内循环中使用它;现在会更快,因为它不涉及长时间划分 这是一些代码:

static const int rangeCount = 5;
static const int rangeLimit[rangeCount] = {13, 20, 27, 33, 36};
static uint32_t rangeProduct[rangeCount] = {
    479001600,
    253955520,
    3315312000,
    652458240,
    39270
};

for (int rangeIndex = 0; rangeIndex < rangeCount; ++rangeIndex)
{
    // The following two lines involve long division;
    // math libraries probably calculate both quotient and remainder
    // in one function call
    uint32_t rangeRemainder = k % rangeProduct[rangeIndex];
    k /= rangeProduct[rangeIndex];

    // A range starts where the previous range ended
    int rangeStart = (rangeIndex == 0) ? 2 : rangeLimit[rangeIndex - 1];

    // Iterate over range
    for (int i = rangeStart; i < rangeLimit[rangeIndex] && i < n; ++i)
    {
        // The following two lines involve a 32-bit division;
        // it produces both quotient and remainder in one Pentium instruction
        int remainder = rangeRemainder % i;
        rangeRemainder /= i;
        std::swap(permutation[n - 1 - i], permutation[n - i + remainder]);
    }
}

当然,这段代码可以扩展到128位以上 另一个优化可能涉及从范围的乘积中提取2的幂;这可能会通过延长范围来增加一点点加速。不确定这是否值得(可能是N的大值,如N = 1000)。

答案 1 :(得分:-1)

不了解算法,但你使用的算法看起来很简单,所以我真的不知道如何优化算法。

您可以使用其他方法:

  • 使用ASM(汇编程序) - 根据我的经验,经过很长一段时间试图弄清楚如何在ASM中编写某个算法,它最终比编译器生成的版本慢:)可能是因为编译器也知道如何布局代码,以便CPU缓存更高效,和/或什么指令实际上更快,什么情况(这是在GCC / Linux上)。
  • 使用多处理:
    • 使您的算法成为多线程,并确保使用与可用cpu核心数相同的线程数运行(大多数cpu现在都有多核/多线程)
    • 使您能够在网络上的多台计算机上运行算法,并设计一种将这些数字发送到网络中的计算机的方法,这样您就可以使用它们的CPU电源。