Python - параллелизм

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Python - параллелизм

Непрочитанное сообщение Olej » 06 авг 2013, 11:55

Это обсуждение переползло сюда из другой темы, вот отсюда: Python:
Olej писал(а): Вот перевод такой ужасной статьи David Beazley Как устроен GIL в Python:
18 февраля 2010 в 12:29
Как устроен GIL в Python
Это относительно реализации потоков в Python, глобальной блокировки GIL ((Global Interpreter Lock), и выполнение на многопроцессорных (многоядерных) системах.

В которой утверждается и объясняется:
Следующие результаты получены на двухъядерном MacBook:
последовательный запуск — 24,6 с
параллельный запуск — 45,5 с (почти в 2 раза медленнее!)
параллельный запуск после отключения одного из ядер — 38,0 с
Т.е. что выполнение в несколько потоков на многоядерном компьютере может быть медленнее, чем на одном ядре (на одном процессоре)!
Но это настолько важно, что должно обсуждаться в отдельной теме!

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Olej » 06 авг 2013, 13:31

Olej писал(а):
Olej писал(а): Вот перевод такой ужасной статьи David Beazley Как устроен GIL в Python:
18 февраля 2010 в 12:29
Как устроен GIL в Python
Проблема вот в чём (цитирую по статье ... но то же упоминается и в справочнике по Python):

Код: Выделить всё

При создании поток просто выполняет метод run() объекта Thread или любую заданную функцию:
...
На самом деле происходит гораздо большее. Python создает маленькую структуру данных (PyThreadState), в которой указаны: текущий stack frame в коде Python,
текущая глубина рекурсии, идентификатор потока, некоторая информация об исключениях. Структура занимает менее 100 байт. Затем запускается новый поток (pthread),
в котором код на языке C вызывает PyEval_CallObject, который запускает то, что указано в Python callable.

Интерпретатор хранит в глобальной переменной указатель на текущий активный поток.
...
Принцип работы прост. Потоки удерживают GIL, пока выполняются. Однако они освобождают его при блокировании для операций ввода-вывода. Каждый раз,
когда поток вынужден ждать, другие, готовые к выполнению, потоки используют свой шанс запуститься. 
...
При работе с CPU-зависимыми потоками, которые никогда не производят операции ввода-вывода, интерпретатор периодически проводит проверку
(«the periodic check»). По умолчанию это происходит каждые 100 «тиков», но этот параметр можно изменить с помощью sys.setcheckinterval(). 
Интервал проверки — глобальный счетчик, абсолютно независимый от порядка переключения потоков.
...
Ожидающий поток при этом может сделать сотни безуспешных попыток захватить GIL. 
Мы видим, что происходит битва за две взаимоисключающие цели. Python просто хочет запускать не больше одного потока в один момент.
А операционная система («Ооо, много ядер!») щедро переключает потоки, пытаясь извлечь максимальную выгоду из всех ядер.
Цитата излишне обширная, но она полностью перекрывает содержимое статьи...

Аватара пользователя
Виктория
Писатель
Сообщения: 113
Зарегистрирован: 28 дек 2012, 14:05
Откуда: Самара
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Виктория » 06 авг 2013, 13:54

И какой выход? Свой диспетчер потоков (в статье ведь этот путь не отвергается)? Например, для вычислительных задач на многоядерном процессоре

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Olej » 06 авг 2013, 14:05

Виктория писал(а):И какой выход? Свой диспетчер потоков (в статье ведь этот путь не отвергается)? Например, для вычислительных задач на многоядерном процессоре
Прежде всего...
1. нужно проверить детально и понять: что и как происходит?

2. могут быть существенные различия проявления а). в операционных системах и б). версиях Python (где-то высказывались мнения, что начиная с Python 3.2 всё что связано с GIL радикально переписано)

3. ... а потом уже разбираться что и как ... ;-)

P.S. Как вариант: при крайнем желании использовать N процессоров (а N=8 уже далеко не редкость), вы вполне можете "размножить" задачу на несколько параллельных процессов. Сложность некоторая здесь будет только в их несколько изощрённом IPC (межпроцессном взаимодействии) и медленностью таких взаимодействий (но эти взаимодействия нужно просто оптимизировать, чтобы они происходили только когда крайне нужно, а не когда попало ;-) ). Этот путь будет совершенно естественным для Linux или Solaris, но затруднён для Windows ... но и там я проверю как это можно сделать.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Olej » 06 авг 2013, 14:25

Olej писал(а):Цитата излишне обширная, но она полностью перекрывает содержимое статьи...
Я повторю здесь свой тестовый пример (с минимальными изменениями), который показывался в исходной теме, откуда всё началось...
Вот его выполнение на 2-х ядерной машине:

Код: Выделить всё

bash-4.2$ ./mthrs.py
число выполнений 2
число циклов в выполнении 10000000
============ последовательное выполнение ============
время  2.99 секунд
================ параллельные потоки ================
время  3.44 секунд
=============== параллельные процессы ===============
время  1.94 секунд
Если 2 активные (загружающие CPU) порции работы выполнить последовательно друг за другом, то это потребует 2.99 секунд.
А если то же попытаться сделать параллельно в 2-х потоках управляемых интерпретатором Python на 2-х ядерном процессоре, то затраченное время становится 3.44 секунды, что на 15% дольше вместо ожидаемых 50% короче!
А вот если те же 2 порции той же работы заказать параллельно в 2-х отдельных процессах, управляемых операционной системой, то затраченное время становится 1.94 секунд, что на 35% (ну не 50% ;-) ) быстрее, чем последовательное выполнение.

Чем не шаманский результат? :-o
Вложения
mthrs.py
(2.04 КБ) 436 скачиваний

Аватара пользователя
Виктория
Писатель
Сообщения: 113
Зарегистрирован: 28 дек 2012, 14:05
Откуда: Самара
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Виктория » 06 авг 2013, 14:43

Windows вроде больше тяготеет к потокам. Результаты могут быть совсем другие?
Olej писал(а): P.S. Как вариант: при крайнем желании использовать N процессоров (а N=8 уже далеко не редкость), вы вполне можете "размножить" задачу на несколько параллельных процессов....
Где бы ещё найти восьмиядерный проц? ))

Т.е. в споре "потоки vs процессы" даже для параллельного выполнения чисто вычислительных задач побеждают процессы? (например, одновременный поиск по разным направлениям, дифференцирование по нескольким координатам и т.п. ) Хотя, даже для 8 ядер организация параллельных вычислений идет не от данных, а от особенностей требуемых операций, функций. Но это я Вас отвлекла, извиняюсь.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Olej » 06 авг 2013, 15:19

Виктория писал(а):Windows вроде больше тяготеет к потокам. Результаты могут быть совсем другие?
В Windows принципиально нет fork() - клонирования процесса, на котором стоит всё серверное ПО UNIX, поэтому там это одна из основных возможностей...
Но моделирование fork() есть в других интерфейсных пакетах под Windows, например Boost (а Boost имеет интерфейс к Python)... не исключено, что и в пакетах Python (а их великое множество!) есть нечто, моделирующее поведение fork().

Результаты могут быть, конечно, очень отличающиеся.
У автора статьи, от которой я оттолкнулся, результаты тоже характером сильно отличаются, у него MacOS, в отличие от моего Linux...
Виктория писал(а): Т.е. в споре "потоки vs процессы" даже для параллельного выполнения чисто вычислительных задач побеждают процессы? (например, одновременный поиск по разным направлениям, дифференцирование по нескольким координатам и т.п. ) Хотя, даже для 8 ядер организация параллельных вычислений идет не от данных, а от особенностей требуемых операций, функций. Но это я Вас отвлекла, извиняюсь.
Здесь принципиально то, что процесс "программы на Python" - это не исполнимый машинный код, как C/C++, PASCAL или Modula-2, а байт-код программы в обрамлении исполняющей системы Python (интерпретатора). И переключением потоков ведает именно эта исполняющая система Python, а не операционная система.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Olej » 06 авг 2013, 15:29

Виктория писал(а): Где бы ещё найти восьмиядерный проц? ))
У меня нет под рукой 8-ядерного проца ;-) ... максимум, что нашлось сразу - это вот это:

Код: Выделить всё

olej@atom:~/2013-WORK$ cat /proc/cpuinfo | grep proc
processor       : 0
processor       : 1
processor       : 2
processor       : 3
olej@atom:~/2013-WORK$ cat /proc/cpuinfo | grep 'model name'
model name      : Intel(R) Atom(TM) CPU  330   @ 1.60GHz
model name      : Intel(R) Atom(TM) CPU  330   @ 1.60GHz
model name      : Intel(R) Atom(TM) CPU  330   @ 1.60GHz
model name      : Intel(R) Atom(TM) CPU  330   @ 1.60GHz
Это "минималистичный" Intel Atom, и у него, конечно, не 4 ядра, а 2 ядра, но с hyperthreading ... и, в принципе, он на них распределяет выполнение потоков (отчасти, где может)...
И стоят там не совсем и свежие (это Ubuntu 10.04):

Код: Выделить всё

olej@atom:~/2013-WORK$ u_n_a_m_e -a
Linux atom 2.6.32-45-generic #100-Ubuntu SMP Wed Nov 14 10:41:11 UTC 2012 i686 GNU/Linux
olej@atom:~/2013-WORK$ python -V
Python 2.6.5
Это даже интереснее ... разные версии:

Код: Выделить всё

olej@atom:~/2013-WORK/python/parallel/pytspeed/mthrs$ ./mthrs.py
число выполнений 2
число циклов в выполнении 10000000
============ последовательное выполнение ============
время  6.36 секунд
================ параллельные потоки ================
время  9.80 секунд
=============== параллельные процессы ===============
время  3.45 секунд
А теперь во все 4 потока, но так, чтобы общий объём "работы" сохранился (уменьшая число циклов "работы") - нагляднее для сравнений:

Код: Выделить всё

olej@atom:~/2013-WORK/python/parallel/pytspeed/mthrs$ ./mthrs.py -t4 -n5000000
число выполнений 4
число циклов в выполнении 5000000
============ последовательное выполнение ============
время  6.55 секунд
================ параллельные потоки ================
время 10.21 секунд
=============== параллельные процессы ===============
время  2.71 секунд
Всё становится ещё хуже...
Но насколько же всё становится лучше для выполнения в 4 независимых процесса!

И для проверки ("контрольный выстрел" ;-) ) - тот же объём вычислений, но только в 1 последовательный поток или процесс:

Код: Выделить всё

olej@atom:~/2013-WORK/python/parallel/pytspeed/mthrs$ ./mthrs.py -t1 -n20000000
число выполнений 1
число циклов в выполнении 20000000
============ последовательное выполнение ============
время  6.38 секунд
================ параллельные потоки ================
время  6.49 секунд
=============== параллельные процессы ===============
время  7.07 секунд

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Olej » 06 авг 2013, 15:57

Виктория писал(а):Т.е. в споре "потоки vs процессы" даже для параллельного выполнения чисто вычислительных задач побеждают процессы? (например, одновременный поиск по разным направлениям, дифференцирование по нескольким координатам и т.п. )
Не всё так однозначно...

1. мы говорим здесь только о параллельности в Python, т.е. то, как исполнением потоков управляет исполняющая система Python...

2. и, получается, что не "даже для параллельного выполнения чисто вычислительных задач", а "исключительно для параллельного выполнения чисто вычислительных задач" ... т.е. для операций требующих блокирования (ввод-вывод, сетевые операции и т.п.) Python должен организовать "параллельное сосуществование" достаточно эффективно.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Python - параллелизм

Непрочитанное сообщение Olej » 06 авг 2013, 17:33

Виктория писал(а):Windows вроде больше тяготеет к потокам. Результаты могут быть совсем другие?
Вот точно та же программа, но в Windows, её выполнение:

Код: Выделить всё

C:\Python-test\pytspeed\mthrs>mthrsw.py
число выполнений 2
число циклов в выполнении 10000000
============ последовательное выполнение ============
время 2.39 секунд
================ параллельные потоки ================
время 2.33 секунд
=============== параллельные процессы ===============
error: create child process
Я специально максимально не трогал код, в том числе и кодировки русскоязычные - файл перенесен по FTP.
Теперь выполнение в 2-х потоках на 2-х процессорах/ядрах равно (в пределах точности временных измерений) последовательному выполнению того же в 1-м потоке.
Но такое изменение поведения, я думаю (практически уверен!) связано не с Linux/Windows, а с тем, что эти прогоны (Windows) я делаю в Python 3, где и говорили, что что-то с GIL переделали:

Код: Выделить всё

c:\Python-test\pytspeed>python -V
Python 3.3.2
P.S. Хот я и написал "не трогал код" - это не совсем так: "трогать" пришлось, но только операторы print, которые в 3.Х теперь стали вызовами функции, вот эти изменённые места:

Код: Выделить всё

#print "число выполнений %i" % thrnum
#print "число циклов в выполнении %i" % repnum
print( "число выполнений {0:d}".format( thrnum ) )
print( "число циклов в выполнении {0:d}".format( repnum ) )
...
#print "============ последовательное выполнение ============"
print( "============ последовательное выполнение ============" )
...
#print "время %5.2f секунд" % clc
print( "время {0:.2f} секунд".format( clc ) )
...
и т.д.

P.P.S. Как и должно ожидать, fork() в Windows нет и быть не может...
Но меня удивляет то, что это выразилось не в синтаксической ошибке при обращении к модулю os, а то, что там этот вызов есть, а закончился он ошибкой исполнения!
Вложения
mthrsw.py
(2.12 КБ) 458 скачиваний

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 4 гостя