Найти - Пользователи
Полная версия: Эффективность использования multi-core (multiprocessing)
Начало » Python для новичков » Эффективность использования multi-core (multiprocessing)
1
amdlintuxos
Доброго времени суток всем форумчанам.

Пытаюсь освоить выгоду от исползования multi-core CPU + python.
Сразу скажу, про GIL читал вскользь.

Сначала опробывал синтаксис исползуя простенький примерчик, где в отделных процессах запускалась функция совершающая много несвязанных арифметических действий.

from multiprocessing import Process, Pool
import os, time

def f(arg):
mas = range(1, 10000)
p = 1
for i in mas:
p*=i*i*i*i

print 'parent process:', os.getppid(), 'process id:', os.getpid()
print "calculation", arg, "DONE"

if __name__ == '__main__':
t = time.time()
case = 2

if case == 0:
print 'POOL, 4x parallel calculation'
pool = Pool(processes = 4) # start 4 worker processes
pool.map(f, [1,2,3,4])

if case == 1:
print '4x parallel calculation'
p1 = Process(target=f, args=(1,))
p1.start()

p2 = Process(target=f, args=(2,))
p2.start()

p3 = Process(target=f, args=(3,))
p3.start()

p4 = Process(target=f, args=(4,))
p4.start()

p1.join()
p2.join()
p3.join()
p4.join()

if case == 2:
print 'serial calculation'
f(1)
f(2)
f(3)
f(4)

print "Time taken:", time.time() - t
Замерялось время он от момента создания процессов до момента когда все процессы завершали счёт.
Пример показал позитивный эффект даже на слабеньком процессоре Athom с технологией Hyper Threading.
case 1:
4x parallel calculation
parent process: 24640 process id: 24643
calculation 3 DONE
parent process: 24640 process id: 24642
calculation 2 DONE
parent process: 24640 process id: 24641
calculation 1 DONE
parent process: 24640 process id: 24644
calculation 4 DONE
Time taken: 17.3200678825
case 2:
serial calculation
parent process: 24649 process id: 24651
calculation 1 DONE
parent process: 24649 process id: 24651
calculation 2 DONE
parent process: 24649 process id: 24651
calculation 3 DONE
parent process: 24649 process id: 24651
calculation 4 DONE
Time taken: 27.8628590107
Далее я немного усложнил задачу, и вместо использования простых чисел и арифметических операций, я создал фиктивный обьект, на основании этого обьекта создавались два листа содержащих экземпляры этих обьектов. Далее эти листы передавались в качестве аргументов функциям которые запускались в параллеле. Далее, внутри функций над этими обьектами совершались манипуляции. В данном примере я получил отставание по сравнению с последовательным выполнениемэтих двух функций(паралельное исполнение этих же самых функций оказалось медленнее). Тут я стал думать о GIL, и как я его понял. Я создал еще один test case, похожий на первый, с паралельным исползованием функций, но аргументы я им передавал разные (т.е. каждый поток получал уникалные листы обьектов), дабы исключить влияние GIL (если я конечно правильно понял GIL, что то мне подсказует что неверено истолковал я). В итоге результат тот же самый.

from multiprocessing import Process, Queue
import os, time
from random import randint

def f1(list1, list2):
for l1 in list1:
for l2 in list2:
if l1.xy == l2.xy:
list1.remove(l1)
list2.remove(l2)
break
#return list1, list2


def f2(list1, list2):
for l1 in list1:
l1.render()

for l2 in list2:
l2.render()

class ob():
def __init__(self, (x,y)):
self.xy = (x,y)

def render(self):
(x, y) = self.xy ; x*x*x*x*x*x*x # some load


if __name__ == '__main__':
case = 0

npc_list = []
bullets_list = []

npc_list2 = []
bullets_list2 = []

i = 0
while i < 1000:
npc = ob((randint(1,100), randint(1,100)))
npc_list.append(npc)

bullet = ob((randint(1,100), randint(1,100)))
bullets_list.append(bullet)

npc = ob((randint(1,100), randint(1,100)))
npc_list2.append(npc)

bullet = ob((randint(1,100), randint(1,100)))
bullets_list2.append(bullet)

i += 1

while True:
t = time.time()

if case == 0:
print "PARALLEL calc. using SAME objects"
p1 = Process(target=f1, args=(npc_list, bullets_list))
p1.start()

p2 = Process(target=f2, args=(npc_list, bullets_list))
p2.start()

p1.join()
p2.join()

if case == 1:
print "PARALLEL calc. using DIFFERENT objects"
p1 = Process(target=f1, args=(npc_list, bullets_list))
p1.start()

p2 = Process(target=f2, args=(npc_list2, bullets_list2))
p2.start()

p1.join()
p2.join()

if case == 2:
print "SERIAL calc."
f1(npc_list, bullets_list)
f2(npc_list, bullets_list)

print "Time taken:", time.time() - t
case 0:
PARALLEL calc. using SAME objects
Time taken: 1.75695300102
Time taken: 1.71754407883
Time taken: 1.72614192963
case 1:
PARALLEL calc. using DIFFERENT objects
Time taken: 1.70828604698
Time taken: 1.69986701012
Time taken: 1.74617981911
case 2:
SERIAL calc.
Time taken: 1.66356611252
Time taken: 0.824527978897
Time taken: 0.73535490036
Time taken: 0.86488699913
Time taken: 0.710259914398
Мне именно хотелось бы получить выгоду от исползования многопроцессорности в коде 2, потомучто это ближе к тем задачам которые мне нужны
Как бы мне лучше поступить?
Кстати создание процессов внутри loop мой недочёт, позже я подумаю как это дело вынести за пределы loop ( в моём случае это game-loop), просто сейчас хочу нащупать основной эффект, а допилка и дооптимизация планируется после.

Зараннее благодарю.
Ed
По-моему у вас там что-то с данными. Они по виду меняются в тесте.
Я попробовал изолировать данные и у меня результаты вполне вменяемые.
Вот слегка модифицированый ваш код: http://pastebin.com/R4mjTzNg
Вот результат:
$ python proc1.py  
PARALLEL calc. using DIFFERENT objects
f1: 1000 1000
f1: 1000 1000
f1: 1000 1000
f2: 1000 1000
f2: 1000 1000
f2: 1000 1000
f2: 1000 1000
f1: 1000 1000
Time taken: 1.4449698925
SERIAL calc.
f1: 1000 1000
f1: 1000 1000
f1: 1000 1000
f1: 1000 1000
f2: 1000 1000
f2: 1000 1000
f2: 1000 1000
f2: 1000 1000
Time taken: 2.7552011013
Александр Кошелев
amdlintuxos
Тут я стал думать о GIL, и как я его понял.
При использовании процессов GIL не играет никакой роли. Это вообще из другой оперы.
amdlintuxos
Александр Кошелев
При использовании процессов GIL не играет никакой роли. Это вообще из другой оперы.
Я читал много постов и очень много жалоб было на GIL, я уже засомневался что питон(стандартный) позволит исползовать эффективно MultiCore CPU, я ошибся и подвергся паранои. спасибо за разяснение, значит не всё так плохо.

Ed
По-моему у вас там что-то с данными. Они по виду меняются в тесте
верно, но их изменение не так значительно чтоб сказыватся на тесте, в любом случае я исключил это влияние.
я посмотрел ваш код, он работает, спасибо. я так же поковырялся со своим кодом и на удивление обнаружил что очень сильное влияние на тест оказывает конструкция
for case in :
очень хотелось бы понять, почему так?

код 1:
from multiprocessing import Process, Queue
import os, time
from random import randint

def f1(list1, list2):
for l1 in list1:
for l2 in list2:
(x,y) = l2.xy # some load
x*x*x*x*x*x*x # some load

def f2(list1, list2):
for l1 in list1:
l1.render()
for l2 in list2:
l2.render()

class ob():
def __init__(self, (x,y)):
self.xy = (x,y)

def render(self):
(x, y) = self.xy # some load
x*x*x*x*x*x*x # some load


if __name__ == '__main__':
npc_list = []
bullets_list = []

i = 0
while i < 1000:
npc = ob((randint(1,100), randint(1,100)))
npc_list.append(npc)

bullet = ob((randint(1,100), randint(1,100)))
bullets_list.append(bullet)

i += 1


while True:
t = time.time()

for case in [0,1]:
if case == 0:
print "PARALLEL calc."
p1 = Process(target=f1, args=(npc_list, bullets_list))
p1.start()

p2 = Process(target=f2, args=(npc_list, bullets_list))
p2.start()

p1.join()
p2.join()


if case == 1:
print "SERIAL calc."
f1(npc_list, bullets_list)
f2(npc_list, bullets_list)

print "Time taken:", time.time() - t
результат работы кода 1
PARALLEL calc.
Time taken: 0.963114023209
SERIAL calc.
Time taken: 2.58085608482
PARALLEL calc.
Time taken: 0.948005914688
SERIAL calc.
Time taken: 2.55663514137
PARALLEL calc.
Time taken: 0.969657897949
SERIAL calc.
Time taken: 2.60941004753
PARALLEL calc.
Time taken: 0.962624788284
SERIAL calc.
Time taken: 2.59019184113
PARALLEL calc.
Time taken: 0.946449995041
SERIAL calc.
Time taken: 2.56901907921
PARALLEL calc.
Time taken: 0.953253984451
код 2:
from multiprocessing import Process, Queue
import os, time
from random import randint

def f1(list1, list2):
for l1 in list1:
for l2 in list2:
(x,y) = l2.xy # some load
x*x*x*x*x*x*x # some load

def f2(list1, list2):
for l1 in list1:
l1.render()
for l2 in list2:
l2.render()

class ob():
def __init__(self, (x,y)):
self.xy = (x,y)

def render(self):
(x, y) = self.xy # some load
x*x*x*x*x*x*x # some load


if __name__ == '__main__':
npc_list = []
bullets_list = []

i = 0
while i < 1000:
npc = ob((randint(1,100), randint(1,100)))
npc_list.append(npc)

bullet = ob((randint(1,100), randint(1,100)))
bullets_list.append(bullet)

i += 1

case = 1
while True:
t = time.time()

if case == 0:
print "PARALLEL calc."
p1 = Process(target=f1, args=(npc_list, bullets_list))
p1.start()

p2 = Process(target=f2, args=(npc_list, bullets_list))
p2.start()

p1.join()
p2.join()


if case == 1:
print "SERIAL calc."
f1(npc_list, bullets_list)
f2(npc_list, bullets_list)

print "Time taken:", time.time() - t
Результат работы кода 2
SERIAL calc.
Time taken: 1.61172699928
SERIAL calc.
Time taken: 1.60983991623
SERIAL calc.
Time taken: 1.60948204994
SERIAL calc.
Time taken: 1.61442399025
SERIAL calc.
Как видите различия в коде 1 и коде 2 незначительны.
почему же время исполнения последователных вычеслений кода 1 (2.5сек), а кода 2 (1.6сек)?
просто хотелось бы понять этот момент чтоб в будущем исключить ошибки приводящие к замедлению скорости.
А вот паралелные вычисления в данном случае по времени совпадают(код 1, код 2).
Однако в других случаях(тело функции 1 изменяло содержимое листов) я выхватывал что время исполнения последователных функций кода 2 и кода 1 идентичны, а вот конструкция for case in : сильно ускоряет паралелные вычисления, например в коде первого поста, второй пример.
Ed
amdlintuxos
Как видите различия в коде 1 и коде 2 незначительны.
почему же время исполнения последователных вычеслений кода 1 (2.5сек), а кода 2 (1.6сек)?
У вас там баг в подсчете времени в коде 1. Внесите t = time.time() вовнутрь цикла.
amdlintuxos
Ed
У вас там баг в подсчете времени в коде 1. Внесите t = time.time() вовнутрь цикла.
позорный мой недочёт. спасибо большое за помощь. теперь всё встало на свои места.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB