Уведомления

Группа в Telegram: @pythonsu

#1 Дек. 12, 2010 15:46:33

amdlintuxos
От:
Зарегистрирован: 2010-12-12
Сообщения: 11
Репутация: +  0  -
Профиль   Отправить e-mail  

Эффективность использования multi-core (multiprocessing)

Доброго времени суток всем форумчанам.

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

Зараннее благодарю.



Отредактировано (Дек. 12, 2010 15:52:29)

Офлайн

#2 Дек. 12, 2010 17:30:07

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

Эффективность использования multi-core (multiprocessing)

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



Офлайн

#3 Дек. 12, 2010 19:36:59

Александр Кошелев
От: Москва
Зарегистрирован: 2007-02-03
Сообщения: 1724
Репутация: +  2  -
Профиль   Отправить e-mail  

Эффективность использования multi-core (multiprocessing)

amdlintuxos
Тут я стал думать о GIL, и как я его понял.
При использовании процессов GIL не играет никакой роли. Это вообще из другой оперы.



Офлайн

#4 Дек. 13, 2010 20:03:47

amdlintuxos
От:
Зарегистрирован: 2010-12-12
Сообщения: 11
Репутация: +  0  -
Профиль   Отправить e-mail  

Эффективность использования multi-core (multiprocessing)

Александр Кошелев
При использовании процессов 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 : сильно ускоряет паралелные вычисления, например в коде первого поста, второй пример.



Отредактировано (Дек. 13, 2010 20:15:05)

Офлайн

#5 Дек. 13, 2010 21:55:04

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

Эффективность использования multi-core (multiprocessing)

amdlintuxos
Как видите различия в коде 1 и коде 2 незначительны.
почему же время исполнения последователных вычеслений кода 1 (2.5сек), а кода 2 (1.6сек)?
У вас там баг в подсчете времени в коде 1. Внесите t = time.time() вовнутрь цикла.



Офлайн

#6 Дек. 13, 2010 22:06:46

amdlintuxos
От:
Зарегистрирован: 2010-12-12
Сообщения: 11
Репутация: +  0  -
Профиль   Отправить e-mail  

Эффективность использования multi-core (multiprocessing)

Ed
У вас там баг в подсчете времени в коде 1. Внесите t = time.time() вовнутрь цикла.
позорный мой недочёт. спасибо большое за помощь. теперь всё встало на свои места.



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version