Python 优化整系数列表与其长整数表示之间的转换
我正在尝试优化我的多项式实现。特别是,我正在处理系数模为n的多项式(可能是2^64)和模为形式为x^r-1的多项式(Python 优化整系数列表与其长整数表示之间的转换,python,optimization,polynomial-math,Python,Optimization,Polynomial Math,我正在尝试优化我的多项式实现。特别是,我正在处理系数模为n的多项式(可能是2^64)和模为形式为x^r-1的多项式(r是1: coefs.pop() 返回系数 请注意,我没有选择n,它是用户的输入,我的程序想要证明它的素性(使用AKS测试),所以我不能分解它 (*)我尝试了几种方法: 使用numpy数组而不是列表,并使用numpy.convolve进行乘法。对于n2^64来说速度非常慢。[我还想避免使用外部库] 使用scipy.fftconvolve。对于n>2^64,根本不起作用 从一开始
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
def_coefs_to_long(coefs,窗口):
''给定一系列系数*coefs*和*window*大小返回一个
这些系数的长整数表示。
'''
res=0
加法器=0
对于coefs中的k:
res+=k=window
#确保返回的列表从不为空,并且没有额外的0。
如果不是coefs:
coefs.append(0)
elif非coefs[-1]和len(coefs)>1:
coefs.pop()
返回系数
请注意,我没有选择n
,它是用户的输入,我的程序想要证明它的素性(使用AKS测试),所以我不能分解它
(*)我尝试了几种方法:
numpy
数组而不是列表,并使用numpy.convolve
进行乘法。对于n<2^64
来说速度很快,但是对于n>2^64
来说速度非常慢。[我还想避免使用外部库]scipy.fftconvolve
。对于n>2^64
,根本不起作用mod x^r-1
操作(这就破坏了使用这种表示法的原因)除非你这样做是为了学习,为什么要重新发明轮子?另一种方法是将python包装器写入其他多项式库或程序,如果这样的包装器还不存在的话 试试PARI/GP。速度惊人。我最近写了一个定制的C代码,花了我两天的时间编写,结果只比两行PARI/GP脚本快3倍。我敢打赌,调用PARI的python代码最终会比单独用python实现的任何东西都要快。甚至还有一个从python调用PARI的模块: 您可以尝试使用来表示多项式的系数。您还可以像现在一样将系数拆分为更小的整数,但不需要将它们转换回大整数来执行乘法或其他操作。这不需要太多的重新编程工作 剩余数系统的基本原理是使用模运算对数字进行唯一表示。围绕RNS的整个理论允许您对小系数进行运算 编辑: 一个简单的例子:
假设用模11和13表示RNS中的大系数。你的系数将全部由2个小整数组成(我找到了一种优化转换的方法,尽管我仍然希望有人能帮助我进一步改进它们,并希望找到其他聪明的想法 基本上,这些函数的错误在于它们在打包整数或解包整数时具有某种二次内存分配行为。 (参见Guido van Rossum的帖子了解这种行为的另一个例子) 在我意识到这一点后,我决定尝试一下Divide et Impera原理,并获得了一些结果。我只需将数组分成两部分,分别进行转换并最终合并结果(稍后我将尝试使用类似于Rossum的帖子[edit:它似乎没有快多少])中的
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)
我试图在第一个函数中避免更多的内存重新分配,在开始和结束索引之间传递,并避免切片,但结果表明,对于小的输入,这会大大降低函数的速度,对于实际的输入,这只会稍微慢一点。
也许我可以试着把它们混在一起,尽管我认为我不会得到更好的结果
我在上一节课编辑了我的问题,因此一些人给了我一些不同于我最近要求的建议。我认为澄清一些不同来源在评论和答案中指出的结果是很重要的,以便它们对其他希望实现快速多项式和/或AK的人有用这是测试
- 正如J.F.Sebastian指出的那样,AKS算法得到了许多改进,因此尝试实现旧版本的算法总是会导致非常慢的程序。这并不排除这样一个事实,即如果你已经有了AKS的良好实现,你可以加快改进多项式的速度
- 如果您对系数模很小的
(读:字号)你不介意外部依赖,然后选择n
并使用numpy
或numpy.convolve
进行乘法运算。它将比你能写的任何东西都快。不幸的是,如果scipy.fftconvolve
不是字号,你根本不能使用n
,也不能使用scipy.fftconvolve
变得非常慢numpy.convolve>e
- 如果您不必(对系数和多项式)进行模运算,那么使用ZBDDs可能是一个好主意(正如har所指出的)
>>> 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
>>> 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