Обходной путь Python Global Interpreter Lock (GIL) в многоядерных системах, использующих набор задач в Linux?

Итак, я только что закончил смотреть этот доклад по глобальной блокировке интерпретатора Python (GIL) http://blip.tv/file/2232410.

Суть в том, что GIL является довольно хорошим дизайном для одноядерных систем (Python по существу оставляет обработку / планирование потоков вплоть до операционной системы). Но это может привести к серьезным негативным последствиям в многоядерных системах, и в результате потоки с интенсивным вводом-выводом будут сильно блокироваться потоками с интенсивным использованием ЦП, затратами на переключение контекста, проблемой ctrl-C [*] и так далее.

Так как GIL ограничивает нас в основном выполнением программы Python на одном процессоре, я думаю, почему бы не принять это и просто использовать набор задач в Linux, чтобы установить привязку программы к определенному ядру / процессору в системе (особенно в ситуации с несколько приложений Python, работающих на многоядерной системе)?

В конечном итоге мой вопрос заключается в следующем: кто-нибудь пытался использовать набор задач в Linux с приложениями Python (особенно при запуске нескольких приложений в системе Linux, чтобы можно было использовать несколько ядер с одним или двумя приложениями Python, привязанными к конкретному ядру), и если да, то что были результаты? это стоит делать? Это делает вещи хуже для определенных рабочих нагрузок? Я планирую сделать это и проверить его (в основном, посмотрим, займет ли программа больше или меньше времени), но хотел бы услышать от других о вашем опыте.

Дополнение: Дэвид Бизли (парень, выступающий с докладом в связанном видео) отметил, что некоторые расширения C/C++ вручную снимают блокировку GIL, и если эти расширения оптимизированы для многоядерности (т. Е. Для анализа научных или числовых данных и т. Д.), Тогда вместо того, чтобы использовать преимущества многоядерности для сокращения числа, расширение будет эффективно ограничено тем, что оно ограничено одним ядром (что потенциально может значительно замедлить вашу программу). С другой стороны, если вы не используете такие расширения, как это

Причина, по которой я не использую многопроцессорный модуль, состоит в том, что (в этом случае) часть программы сильно связана с сетевым вводом-выводом (HTTP-запросы), поэтому наличие пула рабочих потоков - это БОЛЬШОЙ способ выжать производительность из коробки, поскольку поток запускает HTTP-запрос, а затем, поскольку он ожидает ввода-вывода, отказывается от GIL, и другой поток может сделать это, так что часть программы может легко запустить более 100 потоков, не сильно повреждая процессор, и позвольте мне на самом деле использовать пропускная способность сети, которая доступна. Что касается Python без стеков / и т.д., я не слишком заинтересован в переписывании программы или замене моего стека Python (доступность также будет проблемой).

[*] Только главный поток может получать сигналы, поэтому если вы отправляете ctrl-C, интерпретатор Python в основном пытается запустить основной поток, чтобы он мог обработать сигнал, но так как он не контролирует напрямую, какой поток запускается (это оставлено на усмотрение операционной системы) оно в основном говорит ОС продолжать переключать потоки, пока в конце концов не попадет в основной поток (что, если вам не повезло, может занять некоторое время).

7 ответов

Решение

Я никогда не слышал, чтобы кто-нибудь использовал набор задач для увеличения производительности с помощью Python. Это не означает, что это не может произойти в вашем случае, но обязательно опубликуйте ваши результаты, чтобы другие могли критиковать ваши методы сравнения и предоставлять валидацию.

Лично я бы отделил ваши потоки ввода / вывода от потоков, связанных с процессором, используя очередь сообщений. Таким образом, ваш интерфейс теперь полностью связан с сетевым вводом / выводом (некоторые с интерфейсом HTTP, некоторые с интерфейсом очереди сообщений) и идеально подходят для вашей ситуации с потоками. Тогда процессоры с интенсивным использованием ЦП могут либо использовать многопроцессорную обработку, либо просто быть отдельными процессами, ожидающими работу, чтобы прибыть в очередь сообщений.

В более долгосрочной перспективе вы можете также подумать о замене внешнего интерфейса потокового ввода-вывода на Twisted или что-то вроде eventlets, потому что, даже если они не помогут производительности, они должны улучшить масштабируемость. Ваш бэкэнд теперь уже масштабируемый, потому что вы можете запускать свою очередь сообщений на любом количестве машин + процессор по мере необходимости.

Другое решение: http://docs.python.org/library/multiprocessing.html

Примечание 1: Это не ограничение языка Python, а реализация CPython.

Примечание 2: Что касается соответствия, у вашей ОС не должно быть проблем с этим.

Интересным решением является эксперимент, о котором Райан Келли сообщил в своем блоге: http://www.rfk.id.au/blog/entry/a-gil-adventure-threading2/

Результаты кажутся очень удовлетворительными.

До тех пор, пока GIL не будет удален из Python, вместо потоков могут использоваться сопрограммы. У меня есть все основания полагать, что эта стратегия была реализована двумя успешными стартапами, использующими гринлеты как минимум в одном случае.

Python GIL для каждого интерпретатора Python. Это означает, что единственное, чтобы избежать проблем с ним при выполнении многопроцессорной обработки, - это просто запустить несколько интерпретаторов (т.е. использовать отдельные процессы вместо потоков для параллелизма), а затем использовать какой-либо другой примитив IPC для связи между процессами (например, сокеты). При этом GIL не является проблемой при использовании потоков с блокировкой вызовов ввода-вывода.

Как упоминалось ранее, основная проблема GIL заключается в том, что вы не можете одновременно выполнять 2 разных потока кода Python. Блокировка потока при блокирующем вызове ввода / вывода блокируется и, следовательно, не исполняется на коде Python. Это означает, что он не блокирует GIL. Если у вас есть две задачи с интенсивным использованием ЦП в отдельных потоках Python, вот где GIL убивает мультиобработку в Python (только реализация CPython, как указывалось ранее). Поскольку GIL останавливает ЦПУ № 1 от выполнения потока Python, в то время как ЦП № 0 занят выполнением другого потока Python.

Я нашел следующее практическое правило достаточным на протяжении многих лет: если рабочие зависят от некоторого общего состояния, я использую один многопроцессорный процесс на ядро ​​(с привязкой к ЦП), а на ядро ​​- исправление пула рабочих потоков (с привязкой к вводу / выводу). ОС позаботится о привязке различных процессов Python к ядрам.

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

Вы можете использовать многопроцессорный модуль, который вместо создания потоков для каждой задачи создает другой процесс компилятора cpython для интерпретации вашего кода. Это позволит вашему приложению использовать преимущества многоядерных систем. Единственная проблема, которую я вижу в этом подходе, состоит в том, что у вас будут значительные накладные расходы, если вы создадите целый новый стек процессов в памяти. ( http://en.wikipedia.org/wiki/Thread_%28computing%29)

Модуль многопроцессорной обработки Python: http://docs.python.org/dev/library/multiprocessing.html

"Причина, по которой я не использую многопроцессорный модуль, заключается в том, что (в этом случае) часть программы сильно связана с сетевым вводом-выводом (HTTP-запросы), поэтому наличие пула рабочих потоков - это БОЛЬШОЙ способ снизить производительность из коробки...."

Об этом, я думаю, вы также можете иметь пул процессов: http://docs.python.org/dev/library/multiprocessing.html

Ат, Лев

Другие вопросы по тегам