并行化第n个阶乘python程序的更好方法?

问题描述

我有这个python代码来连续计算n个“ 1”的第n个阶乘。我已经能够很好地对其进行优化,包括使用多处理模块将其调整为在所有内核上运行。但是我注意到第7个进程(因为从上到下,这是值的下限)明显快于其余线程。线程0-6平均耗时32秒,n = 11,而线程7仅耗时12秒。我希望数字本身越大,差异就越大,但我不希望有如此明显的差异。

我的代码中是否存在除了导致这堵大墙的计算之外的其他遗漏?我已经验证了输出,并且每个段的长度几乎相同(线程7经过几十次计算而稍长一些,但是从总体上看,这没什么,线程7还是运行最短的)

是否有更好的方法来并行化此方法以提高效率?使线程的增量不相同会有所帮助吗?

编辑:添加python版本信息

Win32上的Python 3.8.5(tags / v3.8.5:580fbb0,2020年7月20日,15:57:54)[MSC v.1924 64位(AMD64)]

(我做了25次n = 11的测试,所有测试都与此类似)

Duration of each Process

import multiprocessing
import argparse
from datetime import datetime
from math import log10

parser = argparse.ArgumentParser(
    formatter_class=argparse.Helpformatter,description="Calcs n factorial",usage=""
)

parser.add_argument("-n","--number",type=int,default=2)

args = parser.parse_args()

def getlog(send_end,i,threads,num,n,inc):
    begin = datetime.Now()
    start = num-inc*i
    end = num-inc*(i+1) if i < threads-1 else 0
    output = sum(map(log10,range(start,end,-n)))
    send_end.send(output)
    final = datetime.Now()
    duration = final-begin
    print("{},{},{}".format(i,duration,start,end))

def main():
    n = args.number
    num = int('1'*n)
    threads = multiprocessing.cpu_count() if num/multiprocessing.cpu_count() > multiprocessing.cpu_count() else 1
    inc = int(num/threads)
    inc -= inc%n
    jobs = []
    pipe_list = []
    for i in range(threads):
        recv_end,send_end = multiprocessing.Pipe(False)
        p = multiprocessing.Process(target=getlog,args=(send_end,inc))
        jobs.append(p)
        pipe_list.append(recv_end)
        p.start()
    for proc in jobs:
        proc.join()
    e = sum([output.recv() for output in pipe_list])

    print('%.2fe%d' % (10**(e % 1),e // 1))
    
if __name__ == '__main__':
    start = datetime.Now()
    main()
    end = datetime.Now()
    print(end-start)

解决方法

@echo off pushd "D:\Jaye\Programming\Web\Project\scss" for /f "delims=" %%i in ('dir /b /s /a-d *.scss ^| findstr /V /IRC:"^_"') do echo Non of these starts with "_": "%%~i" popd 如果需要使用超出C range范围之外的值,请使用较慢的实现-请参见source

您在Windows上,其中C long是32位(即使在64位Python版本上)。进程7是唯一将范围元素放在C long范围内的程序。

,

以不同的幅度迭代一百万个数字的时间:

from timeit import repeat
from collections import deque

for e in range(26,36):
    n = 2**e
    t = min(repeat(lambda: deque(range(n,n+10**6),0),number=1))
    print(e,t)

在64位Windows上的32位Python上为我提供的输出,请注意,从2 30 增至2 31 时的巨大增长:

26 0.020830399999999916
27 0.020713199999999987
28 0.02067260000000004
29 0.021565000000000056
30 0.021966000000000152
31 0.16404839999999998
32 0.16630840000000013
33 0.16394810000000026
34 0.16302989999999973
35 0.1655395999999998

在范围上映射log10仍显示大约相同(绝对)的增加:

26 0.14502039999999994
27 0.1435571
28 0.14378349999999962
29 0.14398270000000002
30 0.14687919999999988
31 0.29700239999999933
32 0.29499730000000035
33 0.2949491999999996
34 0.2964432000000006
35 0.2918921999999995

代码:

from timeit import repeat
from collections import deque
from math import log10

for e in range(26,36):
    n = 2**e
    t = min(repeat(lambda: deque(map(log10,range(n,n+10**6)),t)

您在线程7中的数字都是快幅度,而其他线程中的大多数/所有数字都是慢幅度。

您可以更改范围,以使它们都经过所有幅度。更简单的示例:使用范围range(0,10)range(10,20)代替范围range(0,20,2)range(1,2)

顺便说一句,当从2 30 变为2 31 时,我看到64位Windows上的64位Python也有类似的增长。但是在Linux上的64位Python上,从2 30 到2 31 并没有增加,但从2 62 62>时也有类似的显着增加。 sup>到2 63

更新

以上删除线段落不正确。正如enter image description here所示,并不是“数字”很慢(我曾想过),而是有两个完全独立的range实现。而且只有您的线程7使用快速线程(少量线程)。因此,我上面提出的使所有线程/范围遍历所有幅度的建议实际上会适得其反。它不会使速度较慢的速度更快,而只会使速度较慢的速度和其他速度一样慢。闷闷不乐。

因此,另一种建议是:不要像给每个线程一个范围那样,而是给每个线程一个long范围的一部分和一个非long范围的一部分。那应该使所有线程都同样快,并减少整体时间。但是效果会很小,对于较大的n甚至会更小,我怀疑是否值得这样做。