具有已发布GIL和复杂线程的多线程代码在Python中速度较慢

时间:2018-06-19 23:54:23

标签: python multithreading performance multiprocessing numba

我正在使用具有8G RAM和Linux Redhat 7的8核处理器机器,并且正在使用Pycharm IDE。

我试图使用python线程模块来利用多核处理的优势,但最终却得到了慢得多的代码。我通过Numba发布了GIL,并确保我的线程正在执行足够复杂的计算,因此问题不在How to make numba @jit use all cpu cores (parallelize numba @jit)

中讨论过

这是多线程代码:

l=200

@nb.jit('void(f8[:],f8,i4,f8[:])',nopython=True,nogil=True)
def force(r,ri,i,F):

   sum=0
   for j in range(12):
       if (j != i):
           fij=-4 * (12*1**12/(r[j]-ri)**13-6*1**6/(r[j]-ri)**7)
           sum=sum+fij

   F[i+12]=sum

def ODEfunction(r, t):

   f = np.zeros(2 * 12)

   lbound=-4* (12*1**12/(-0.5*l-r[0])**13-6*1**6/(-0.5*l-r[0])**7)
   rbound=-4* (12*1**12/(0.5*l-r[12-1])**13-6*1**6/(0.5*l-r[12-1])**7)

   f[0:12]=r[12:2*12]

   thlist=[threading.Thread(target=force, args=(r,r[i],i,f)) for i in range(12)]

   for thread in thlist:
       thread.start()
   for thread in thlist:
       thread.join()

   f[12]=f[12]+lbound
   f[2*12-1]=f[2*12-1]+rbound
   return f

这是顺序版本:

l=200

@nb.autojit()
def ODEfunction(r, t):

   f = np.zeros(2 * 12)
   lbound=-4* (12*1**12/(-0.5*l-r[0])**13-6*1**6/(-0.5*l-r[0])**7)
   rbound=-4* (12*1**12/(0.5*l-r[12-1])**13-6*1**6/(0.5*l-r[12-1])**7)

   f[0:12]=r[12:2*12]

   for i in range(12):
       fi = 0.0
       for j in range(12):
           if (j!=i):
               fij = -4 * (12*1**12/(r[j]-r[i])**13-6*1**6/(r[j]-r[i])**7)
               fi = fi + fij
       f[i+12]=fi
   f[12]=f[12]+lbound
   f[2*12-1]=f[2*12-1]+rbound
   return f

我还考虑了在多线程和顺序代码运行期间附加系统监视器的图像:

System Motinor during the run of the multi threaded code

System Motinor during the run of the sequential code

有人知道导致线程代码效率低下的原因是什么吗?

1 个答案:

答案 0 :(得分:2)

您必须知道,在Python中调用一个函数的成本相当高(例如,与在C中调用一个函数相比),因此调用numba-jitted函数的代价更高:必须检查参数是否正确(即它们确实是您要传递的float-numpy数组-我们将看到它比普通的Python调用慢5倍。)

与函数中发生的工作相比,让我们检查一下jitted函数的开销:

import numba as nb
import numpy as np

@nb.jit('void(f8[:],f8,i4,f8[:])',nopython=True,nogil=True)
def force(r,ri,i,F):

   sum=0
   for j in range(12):
       if (j != i):
           fij=-4 * (12*1**12/(r[j]-ri)**13-6*1**6/(r[j]-ri)**7)
           sum=sum+fij

   F[i+12]=sum

@nb.jit('void(f8[:],f8,i4,f8[:])',nopython=True,nogil=True)
def nothing(r,ri,i,F):
    pass

def no_jit(r,ri,i,F):
    pass

F=np.zeros(24)
r=np.zeros(12)

现在:

>>>%timeit force(r,1.0,0,F)
706 ns ± 8.96 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>>%timeit nothing(r,1.0,0,F)
645 ns ± 5.36 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>>%timeit no_jit(r,1.0,0,F) #to measure overhead of numba
120 ns ± 6.56 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

因此,基本上有90%的开销,因为该函数是“内联的”,所以您在单线程模式下没有这些开销。这并不奇怪:您的for循环只有12次迭代-只是工作量不够,例如,在您已链接内部循环的示例中,有10^10个迭代!

此外,在线程之间分派工作也有一些开销,我的胆量说,这甚至比jitted-call的开销还多-但要确保应该对程序进行概要分析。即使使用8核,这些赔率也很难克服!

与功能本身所花费的时间相比,目前最大的路障可能是force调用的大开销。上面的分析是很肤浅的,所以我不保证没有其他重要的问题-但是为force做更多的工作将是朝着正确方向迈出的一步。