我正在尝试优化我的多项式实现。特别是我正在处理系数为n
(可能是>2^64
)的多项式,并以x^r - 1
形式模拟多项式(r
为< 2^64
) 。目前我将系数表示为整数列表(*),并且我以最直接的方式实现了所有基本操作。
我希望取幂和乘法尽可能快,为了获得这个,我已经尝试了不同的方法。我目前的方法是将系数列表转换为大整数乘以整数并将系数解包。
问题是打包和拆包需要花费很多时间。
那么,有没有办法改进我的“打包/解压缩”功能?
def _coefs_to_long(coefs, window):
'''Given a sequence of coefficients *coefs* and the *window* size return a
long-integer representation of these coefficients.
'''
res = 0
adder = 0
for k in coefs:
res += k << adder
adder += window
return res
#for k in reversed(coefs): res = (res << window) + k is slower
def _long_to_coefs(long_repr, window, n):
'''Given a long-integer representing coefficients of size *window*, return
the list of coefficients modulo *n*.
'''
mask = 2**window - 1
coefs = [0] * (long_repr.bit_length() // window + 1)
for i in xrange(len(coefs)):
coefs[i] = (long_repr & mask) % n
long_repr >>= window
# assure that the returned list is never empty, and hasn't got an extra 0.
if not coefs:
coefs.append(0)
elif not coefs[-1] and len(coefs) > 1:
coefs.pop()
return coefs
请注意,我不选择n
,它是来自用户的输入,我的程序想要证明它的素数(使用AKS测试),所以我不能将其分解。
(*)我尝试了几种方法:
numpy
数组而不是列表,并使用numpy.convolve
进行乘法运算。 n < 2^64
速度很快但n > 2^64
速度非常慢[我也想避免使用外部库] scipy.fftconvolve
。 n > 2^64
<。li>根本不起作用。mod x^r -1
操作而不将整数转换为系数列表(这使得使用此表示的原因失效)。答案 0 :(得分:2)
您可以尝试使用residual number systems来表示多项式的系数。您也可以像现在一样将系数拆分为更小的整数,但是您不需要将它们转换回一个巨大的整数来进行乘法或其他操作。这不需要太多的重新编程工作。
残差数系统的基本原理是使用模运算的数字的唯一表示。围绕RNS的整个理论允许您对小系数进行操作。
修改强> 一个简单的例子:
假设您在具有模数11和13的RNS中表示您的大系数。您的系数将全部由2个小整数(&lt; 11和<13)组成,可以组合成原始(大)整数。
假设您的多项式最初为33x²+ 18x + 44。在RNS中,系数分别为(33 mod 11,33 mod 13),(18 mod 11,18 mod 13)和(44 mod 11,44 mod 13)=&gt;(0,7),(7,5) )和(0,5)。
将多项式与常数相乘可以通过将每个小系数乘以该常数并对其进行模数来完成。
假设你乘以3,你的系数将变为(0,21 mod 13)=(0,8),(21 mod 11,15 mod 13)=(10,2)和(0 mod 11,15 mod 13)=(0,2)。没有必要将系数转换回它们的大整数。
为了检查我们的乘法是否有效,我们可以将新系数转换回它们的大型表示。这需要将每组系数“求解”为模块化系统。对于第一个系数(0,8),我们需要求解x mod 11 = 0和x mod 13 = 8.这不应该太难实现。在此示例中,您可以看到x = 99是一个有效的解决方案(模13 * 11)
然后我们得到99x²+ 54x + 132,正确的乘法多项式。 与其他多项式相乘是相似的(但需要您以成对方式将系数相互相乘)。添加也是如此。
对于您的用例,您可以根据所需的系数数量或其大小来选择n。
答案 1 :(得分:2)
如何将任意精度整数多项式直接实现为numpy数组列表?
让我解释一下:说你的多项式是Σ p A p X p 。如果大整数A p 可以表示为A p =Σ k A p,k 2 64 k 然后k th numpy数组将在位置p包含64位int A p,k 。
您可以根据问题的结构选择密集或稀疏数组。
实现添加和标量操作只是向量化相同操作的bignum实现的问题。
乘法可以如下处理:AB =Σ p,k,p',k' A p,k B p',k'< / sub> 2 64(k + k') X p + p'。因此,使用密集数组的简单实现可能导致对numpy.convole
或scipy.fftconvolve
的log 64 (n) 2 调用。
模运算应易于实现,因为它是左手项的线性函数,右手项具有小系数。
编辑这里有更多解释
不是将多项式表示为任意精度数列表(它们本身表示为64位“数字”的列表),而是将表示转置为:
如果只有少数几个系数非常大,那么数组中的数组大部分都是0,所以使用稀疏数组可能是值得的。
调用 p,k p th 系数的k th 数字。
注意大整数表示的类比:大整数表示为
x =Σ k x k 2 64 k
你的多项式A的表示方式与
相同A =Σ k A k 2 64 k A k =Σ k A p,k X p
要实现添加,您只需假装您的数组列表是一个简单数字列表,并像往常一样为大整数实现添加(注意用if then
替换numpy.where
条件。)
要实现乘法,你会发现你需要进行log 64 (n) 2 多项式乘法。
对系数实现模运算,又是一个在大整数上转换模运算的简单例子。
要使用小系数的多项式取模数,请使用此运算的线性度:
一个mod(X r - 1)=(Σ k A k 2 64 k )mod (X r - 1)
=Σ k 2 64 k (A k mod(X r - 1))
答案 2 :(得分:1)
我找到了一种优化转化的方法,即使我仍然希望有人可以帮助我进一步改进它们,并希望找到其他一些聪明的想法。
基本上这些函数的错误在于它们在打包整数时或解包时都有某种二次内存分配行为。 (有关此类行为的另一个例子,请参阅Guido van Rossum的this帖子。)
在我意识到这一点之后,我决定尝试Divide et Impera原则,并且我已经获得了一些结果。我简单地将数组分成两部分,分别转换它们并最终加入结果(稍后我会尝试使用类似于Rossum帖子中的f5
的迭代版本[编辑:它似乎并不多更快])。
修改后的功能:
def _coefs_to_long(coefs, window):
"""Given a sequence of coefficients *coefs* and the *window* size return a
long-integer representation of these coefficients.
"""
length = len(coefs)
if length < 100:
res = 0
adder = 0
for k in coefs:
res += k << adder
adder += window
return res
else:
half_index = length // 2
big_window = window * half_index
low = _coefs_to_long(coefs[:half_index], window)
high = _coefs_to_long(coefs[half_index:], window)
return low + (high << big_window)
def _long_to_coefs(long_repr, window, n):
"""Given a long-integer representing coefficients of size *window*, return
the list of coefficients modulo *n*.
"""
win_length = long_repr.bit_length() // window
if win_length < 256:
mask = 2**window - 1
coefs = [0] * (long_repr.bit_length() // window + 1)
for i in xrange(len(coefs)):
coefs[i] = (long_repr & mask) % n
long_repr >>= window
# assure that the returned list is never empty, and hasn't got an extra 0.
if not coefs:
coefs.append(0)
elif not coefs[-1] and len(coefs) > 1:
coefs.pop()
return coefs
else:
half_len = win_length // 2
low = long_repr & (((2**window) ** half_len) - 1)
high = long_repr >> (window * half_len)
return _long_to_coefs(low, window, n) + _long_to_coefs(high, window, n)
结果:
>>> import timeit
>>> def coefs_to_long2(coefs, window):
... if len(coefs) < 100:
... return coefs_to_long(coefs, window)
... else:
... half_index = len(coefs) // 2
... big_window = window * half_index
... least = coefs_to_long2(coefs[:half_index], window)
... up = coefs_to_long2(coefs[half_index:], window)
... return least + (up << big_window)
...
>>> coefs = [1, 2, 3, 1024, 256] * 567
>>> # original function
>>> timeit.timeit('coefs_to_long(coefs, 11)', 'from __main__ import coefs_to_long, coefs',
... number=1000)/1000
0.003283214092254639
>>> timeit.timeit('coefs_to_long2(coefs, 11)', 'from __main__ import coefs_to_long2, coefs',
... number=1000)/1000
0.0007998988628387451
>>> 0.003283214092254639 / _
4.104536516782767
>>> coefs = [2**64, 2**31, 10, 107] * 567
>>> timeit.timeit('coefs_to_long(coefs, 66)', 'from __main__ import coefs_to_long, coefs',... number=1000)/1000
0.009775240898132325
>>>
>>> timeit.timeit('coefs_to_long2(coefs, 66)', 'from __main__ import coefs_to_long2, coefs',
... number=1000)/1000
0.0012255229949951173
>>>
>>> 0.009775240898132325 / _
7.97638309362875
正如您所看到的,这个版本提供了相当快的转换速度,从4
到8
倍速(输入越大,速度越快)。
使用第二个函数获得了类似的结果:
>>> import timeit
>>> def long_to_coefs2(long_repr, window, n):
... win_length = long_repr.bit_length() // window
... if win_length < 256:
... return long_to_coefs(long_repr, window, n)
... else:
... half_len = win_length // 2
... least = long_repr & (((2**window) ** half_len) - 1)
... up = long_repr >> (window * half_len)
... return long_to_coefs2(least, window, n) + long_to_coefs2(up, window, n)
...
>>> long_repr = coefs_to_long([1,2,3,1024,512, 0, 3] * 456, 13)
>>> # original function
>>> timeit.timeit('long_to_coefs(long_repr, 13, 1025)', 'from __main__ import long_to_coefs, long_repr', number=1000)/1000
0.005114212036132813
>>> timeit.timeit('long_to_coefs2(long_repr, 13, 1025)', 'from __main__ import long_to_coefs2, long_repr', number=1000)/1000
0.001701267957687378
>>> 0.005114212036132813 / _
3.006117885794327
>>> long_repr = coefs_to_long([1,2**33,3**17,1024,512, 0, 3] * 456, 40)
>>> timeit.timeit('long_to_coefs(long_repr, 13, 1025)', 'from __main__ import long_to_coefs, long_repr', number=1000)/1000
0.04037192392349243
>>> timeit.timeit('long_to_coefs2(long_repr, 13, 1025)', 'from __main__ import long_to_coefs2, long_repr', number=1000)/1000
0.005722791910171509
>>> 0.04037192392349243 / _
7.0545853417694
我试图在传递开始和结束索引的第一个函数中避免更多的内存重新分配并避免切片,但事实证明,对于小输入,这会大大减慢函数的速度,而且对于实例输入。 也许我可以尝试混合它们,即使我认为我不会获得更好的结果。
我在上一期编辑了我的问题,因此有些人给了我一些建议,其目的与我最近要求的不同。我认为重要的是要澄清评论和答案中不同来源指出的结果,以便它们对于希望实施快速多项式和/或AKS测试的其他人有用。
n
(读取:字号大小)并且您不介意外部依赖关系,那么请转到numpy
并使用numpy.convolve
或scipy.fftconvolve
用于乘法。它会比你能写的任何东西都快得多。不幸的是,如果n
不是字大小,则根本无法使用scipy.fftconvolve
,numpy.convolve
也会变得很慢。numpy
数组以有效运行。n
的多项式的纯python实现,那么我的解决方案似乎是最快的。即使我没有尝试在python中实现系数数组之间的fft乘法(可能更快)。