我找到了一种优化转换的方法,尽管我仍然希望有人可以帮助我进一步改进它们,并希望能找到其他一些聪明的想法。
基本上,这些函数的问题在于它们在打包整数或解包时具有某种二次内存分配行为。(有关此类行为的另一个示例,请参阅 Guido van Rossum 的这篇文章)。
在我意识到这一点后,我决定尝试使用 Divide et Impera 原则,并获得了一些结果。我只是将数组分成两部分,分别转换它们并最终加入结果(稍后我将尝试使用类似于f5
Rossum 帖子中的迭代版本[编辑:它似乎并没有快多少])。
修改后的功能:
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 测试的人有用。
- 正如 JF Sebastian 所指出的,AKS 算法得到了许多改进,因此尝试实现该算法的旧版本总是会导致程序非常缓慢。这并不排除这样一个事实,即如果您已经有一个很好的 AKS 实现,您可以加快它改进多项式的速度。
- 如果您对系数取模感兴趣
n
(阅读:字大小数)并且您不介意外部依赖关系,那么请numpy
使用numpy.convolve
orscipy.fftconvolve
进行乘法运算。它比你能写的任何东西都要快得多。不幸的是,如果n
不是单词大小,您根本无法使用scipy.fftconvolve
,而且numpy.convolve
会变得慢得要命。
- 如果您不必进行模运算(在系数和多项式上),那么使用 ZBDD 可能是一个好主意(正如 harold 所指出的那样),即使我不承诺会取得惊人的结果[即使我认为这是真的很有趣,你应该阅读 Minato 的论文]。
- 如果您不必对系数进行模运算,那么可能使用 RNS 表示,如 Origin 所述,是一个好主意。然后您可以组合多个
numpy
数组以高效运行。
- 如果您想要系数模 a big 的多项式的纯 python 实现
n
,那么我的解决方案似乎是最快的。即使我没有尝试在python中的系数数组之间实现fft乘法(这可能更快)。