многопоточный код с выпущенным GIL и сложными потоками медленнее в Python

1

Я работаю над 8-ядерным процессором с 8G RAM и Linux Redhat 7, и я использую IDE Pycharm.

Я попытался использовать модуль потоковой передачи python, чтобы воспользоваться преимуществами многоядерной обработки, но у меня получился гораздо более медленный код. Я выпустил GIL через Numba и убедился, что мои потоки выполняют достаточно сложные вычисления, поэтому проблема не в том, что обсуждается в примере. Как сделать numba @jit использовать все ядра процессора (распараллелить 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 во время запуска многопоточного кода

Система Motinor во время запуска последовательного кода

Кто-нибудь знает, что может быть причиной этой неэффективности в потоковом коде?

Теги:
performance
multithreading
multiprocessing
numba

1 ответ

2

Вы должны знать, что вызов функции в Python довольно дорогостоящий (например, для вызова функции на C), тем более вызов функции numba-jitted: необходимо проверить, что параметры правильные (то есть, что они действительно массивы float-numpy, которые вы передаете, - мы увидим, что это может быть фактором 5 медленнее, чем обычный Python -call).

Позвольте проверить накладные расходы вашей 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 -call по сравнению с временем, проведенным в самой функции. Вышеприведенный анализ является довольно поверхностным, поэтому я не гарантирую, что других существенных проблем нет, но для того, чтобы иметь большие куски работы для force это был бы шаг в правильном направлении.

  • 0
    Большое спасибо за ответ. Да, я думаю, что принудительный вызов занимает большую часть времени, но я в любом случае не знаю, как сделать многопоточность без вызова функции! На самом деле я следовал руководству, как заставить numba @jit использовать все ядра процессора (распараллелить numba @jit). Они также вызывают функцию для многопоточности, но их вызов не занимает много времени, поэтому мне интересно, почему мой вызов функции занимает так много времени
  • 0
    stackoverflow.com/questions/45610292/...
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню