为啥我使用多处理/多线程的函数在 for 循环中使用时如此缓慢,但在循环之外却没有?

Posted

技术标签:

【中文标题】为啥我使用多处理/多线程的函数在 for 循环中使用时如此缓慢,但在循环之外却没有?【英文标题】:Why is my function that uses multiprocessing/multi threading so slow when used in a for loop, but not outside of it?为什么我使用多处理/多线程的函数在 for 循环中使用时如此缓慢,但在循环之外却没有? 【发布时间】:2021-07-09 16:51:14 【问题描述】:

我正在使用多处理,它在单个大数字上非常快,但是当我尝试在 for 循环中使用它时,小数字,只测试 1009 个小数字需要 5 秒。但是对于 for 循环之外的单个数字来说速度非常快。当使用多处理的函数处于 for 循环中时,有什么方法可以加速多处理?我尝试了多线程并且遇到了同样的问题,所以我在这里寻找一些专家建议。我在下面为我的多处理版本包含了我的代码和时序,但如果需要,我也可以包含我的多线程版本:

import gmpy2 
import time
 
sinn = 2110229697309202254897383305762150945330987087513434511395506048950594976569434432057019507105035289374307720719984431280856161609820548842778454256113246763860786119268583367543952735347969627478873317341364209555365064365565504232770227619462128918701942169785585423104678142850200975026619010035331023744330713985615650556129731348659986462960062760308034462660525448390420668021248422741300646552941285862310410598374242189448623917196191138254637812716211329113836605859918549332304189053950819346551095911511755911832183789503704294770046935064469435830299623205136625543859303686699678929069468518950480476841246805908501510754550017255944080874819287974625925494008373883250410775902993163965873632474224574883242826458163446781002284368017611606202344050570737818087202137703099075773680753707346415849787963446390136517016131227807076254668461445862154978026041507116570585784569893773262639243954090283224759975513502582494002154146757110676408972377044584495342170277522887809749465855954126593100747444378301829661568735873345178089061677917127496915956539418931430313218084338374827152407795095072639044306222222695685778907958272820576498682506540189586657786292950574081739269257159839589987847266550007783514316481286222515710538845836151864127815058116482680058626451349913138908040817800742009650450811565324184631847563730941344941348929727603343965091116543702880556850922077216848669966268219928808236163268726995495688157209747596437162960244538054993785127947211290438554095851924381172697827312534174244295581184309147813790451951453564726742200569263225639113681905176376701339808868274637448606821696026703034737428319530072483125495383057919894902076566679023694181381398377144302767983385253577700652358431959604517728821603076762965129019244904679015099154368058005173028200266632883632953133017055122970338782493475762548347258351148037427739052271661340801912188203749647918379812483260399614599813650518046331670764766419886619324840045611486524123102046413946014624119568013100078163986683199814025915420877588778260860713148420321896163326473203441644820182490479899368048072263481024886708136521847014624735722333931331098969321911443978386868675912141648200500219168920887757573018380579532261231821382787339600631297820996466930957801607217549420247654458172818940238337170577825003408756362106088558651381993611741503374243481167926898332728164900189941804942580426055589622673679047058619682175301326905577843405270203660160407401675700528981573327582844330828861745574031416926871562443652858767649050943181353635950301154441954046214987718582670685455252774874198771086552440702483933126644594300464549471422237478151976561680719370424626162642534252062987911763456822609569209140676822858933588602318066530038691463577379331113471591913447226829868760176810195567325921301390329055242213842898142597360121925124635965685365925901913816717677946911762631634793638450106377437599347740569467683272089859392249351406815344105961234868327316964137925419770514177021722214309784062017826024217906664090209434553785436385765927274067126192143337589109608949427467825999057058702263715338956534536892852849984934736685814891286495169007648767081688963426768409476169071460997622740467533572971356017575900999100928776382541052696124463195981888715845688808970103527288822088031150716134784735332326775370417950625124642515148342694377095213470544739900830244879573205335578256682901821773047071352497997708791157012233232529777513203024818391621220967964874173106990772425289900446640237659116713251437567138729645677868024033209183367071421651937808005637679844370347367922676824239404492688418047080583797577102267329067247758368597488680401670673861120323439239792549053895366970423259196919428554146265587250617656401028722578111927104663315250291888502226235291264834968061065817079511872899991276288365723969841290984981957389126603952133124328219936785870274843554107325931034103072894378438818494802517594594270034007832922248742746517915210656205746338575621725899098414488628833412591266637224507533934158213117522503993423240638893845121918647788013

 
def ffs(x): 
    """Returns the index, counting from 0, of the 
    least significant set bit in `x`. 
    """ 
    return (x&-x).bit_length()-1 
    
    
 
def MillerRabin(arglist):  
  N = arglist[0] 
  primetest = arglist[1] 
  iterx = arglist[2] 
  powx = arglist[3] 
  withstats = arglist[4] 
  primetest = gmpy2.powmod(primetest, powx, N)  
  if withstats == True: 
     print("first: ", primetest)  
  if primetest == 1 or primetest == N - 1:  
    return True  
  else:  
    for x in range(0, iterx):  
       primetest = gmpy2.powmod(primetest, 2, N)  
       if withstats == True: 
          print("else: ", primetest)  
       if primetest == N - 1: return True  

       if primetest == 1: return False  
  return False  
    
   
def sfactorint_isprime(N, withstats=False): 
 
    N = gmpy2.mpz(N) 
    from multiprocessing import Pool 
 
    if N <= 1: return False 
    if N == 2: 
      return True 
    if N % 2 == 0: 
      return False 
    if N < 2: 
        return False 
         
    # Add Trial Factoring here to speed up smaller factored number testing 
 
     
    iterx = ffs(N-1) 
    """ This k test is an algorithmic test builder instead of using 
        random numbers. The offset of k, from -2 to +2 produces pow tests 
        that fail or pass instead of having to use random numbers and more 
        iterations. All you need are those 5 numbers from k to get a  
        primality answer.  
    """ 
    k = pow(N, -1, 1<<N.bit_length()) - 1 
    t = N >> iterx 
    tests = [k-2, k-1, k, k+1, k+2] 
     
    for primetest in range(len(tests)): 
      if tests[primetest] >= N: 
         tests[primetest] %= N 
   
    arglist = [] 
    for primetest in range(len(tests)): 
      if tests[primetest] >= 2: 
        arglist.append([N, tests[primetest], iterx, t, withstats]) 
      
    with Pool(5) as p: 
       s=p.map(MillerRabin, arglist)     
     
    if s.count(True) == len(arglist): return True 
    else: return False 
     
    return s 
    
start = time.time() 
xx = sfactorint_isprime(sinn) 
end = time.time() 
print (end-start)                                                                                                                                      

0.551032543182373


start = time.time() 
for x in range(1, 1009, 2): 
  xx = sfactorint_isprime(x) 
end = time.time() 
print (end-start)  
                                                                                                                                    
5.828385353088379

【问题讨论】:

你为什么使用gmpy2? Python 已经内置了大整数支持。请注意,由于全局解释器锁 (GIL),Python 多线程不会改进代码。 @JérômeRichard 我使用了 gmpy2,因为它的 pow 比 python 的 pow 快 10-15 倍。如果你不能使用 gmpy2,那些语句可以只更改为 pow,gmpy.mpz 语句将其删除,以便于测试。 【参考方案1】:

这条线非常很贵:@​​987654323@。

确实,它将 Python 进程分叉到其他几个进程中(这里有 5 个工人)。这个系统端调用(forkCreateProcess)代价高昂,因为操作系统要管理每个新创建进程的内存、这些进程在可用内核上的调度、(共享)硬件资源等。更不用说进程也在with 范围的末尾加入和销毁(这也很昂贵)。因为这条线是在大约 500 次迭代的循环中执行的,它会创建大约 2500 个进程。这应该比实际计算成本高得多。您应该尝试创建流程,然后只共享一次所有工作。如果这是不可能的,您可以使用进程间通信来做到这一点。在这里,在第二种情况下(for x in range(1, 1009, 2)并行化外循环应该会好得多。请注意,Python 并不是并行计算的最佳工具(这样做很麻烦,而且最终效率不高)。

在主流 Windows 平台上,还会发生一个额外的非常意外的行为:默认包含的杀毒软件会分析每个新创建的进程以检查它们是否是恶意的。这种深度分析非常昂贵,在这种特定情况下完全没有用。更不用说创建新进程是already expensive on Windows。

【讨论】:

感谢您的回答!我想知道在这种情况下迁移到 asyncio 是否会有所帮助,你知道它是否有相同的限制,它会打开新进程吗? AFAIK,您可以创建一个进程池,然后使用 asyncio 管理您的任务。这应该比第二种情况的当前代码快得多,但是 asyncio 的通信开销远非免费。因此,您仍然应该非常小心地使用它。此外,请注意 Windows 上似乎不支持基于多处理的 asyncio。如果你在一个类 Unix 平台上,你可以看看***.com/questions/38193596。 好的,我改成了 asyncio,它要快得多,但仍然比小数字的非并行版本慢得多(大数字似乎需要很长时间才能完成,所以没有t 真的是 I/O 命中)。我现在正在将我的代码转换为 c,但我想知道我是否会遇到同样的问题。 是的,可悲的是,正如预期的那样。 C 为优化和高效并行化提供了更多机会。您可以查看 OpenMP 以并行化您的代码。请注意,使用多个线程和进程总是会引入开销,但基于本地线程的通信(在共享内存中)通常比进程间 Python 通信快得多(至少几个数量级)。

以上是关于为啥我使用多处理/多线程的函数在 for 循环中使用时如此缓慢,但在循环之外却没有?的主要内容,如果未能解决你的问题,请参考以下文章

Python 多处理 - 为啥每个进程有这么多线程?

在 Python3 中使用 for 循环进行多线程/多处理

C#多线程实现循环。界面会假死怎么办?

for循环内的多处理

在函数内多处理 Python 中的 for 循环

Python多线程循环