какие участки кода в python могут выполняться без gil

Python threading или GIL нам почти не помеха

Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место.
Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.).
Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» 🙂
Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

С чего все начиналось

Возьмем простой код (почти как в статье-переводе):

Запустим на python 2.6.5 (ubuntu 10.04 x64, i5 750):

И на python 2.7.2 (win7 x64, i5 750):

Сразу отбросим, что win-версия явно медленнее. В обоих случаях видно значительное замедление параллельного варианта.

Если очень хочется, то можно

GIL в любом случае не позволит многопоточному варианту выполняться быстрее, чем линейный. Однако, если реализация некоего функционала упрощается при введении поточности в код, то стоит хотя бы попытаться по возможности сократить это отставание.
При работе многопоточного приложения ОС может произвольно «перебрасывать» разные потоки между ядрами. И когда два (и более) потока одного python-процесса одновременно пытаются захватывать GIL, начинаются тормоза. Переброс выполняется и для однопоточной программы, но там он не сказывается на скорости.

Соответственно, чтобы потоки захватывали GIL поочередно, можно ограничить python-процесс одним ядром. А поможет нам в этом CPU Affinity Mask, позволяющая в формате битовых флагов указывать на каких ядрах/процессорах разрешено выполняться программе.

На разных ОС данная операция выполняется разными средствами, но сейчас рассмотрим Ubuntu Linux и WinXP+. Также изучалась FreeBSD 8.2 на Intel Xeon, но это останется за пределами статьи.

А сколько у нас вариантов?

Прежде чем выбирать ядра, нужно определиться сколько их у нас в распоряжении. Тут стоит плясать от возможностей платформы: multiprocessing.cpu_count() в python 2.6+, os.sysconf(‘SC_NPROCESSORS_ONLN’) по POSIX и т.д. Пример определения можно посмотреть тут.

Linux Ubuntu

Чтобы достучаться до libc воспользуемся модулем ctypes. Для загрузки нужной библиотеки воспользуемся ctypes.CDLL:

pid_t — это int, cpu_set_t — структура из одного поля размером в 1024 бита (т.е. возможно работать с 1024 ядрами/процессорами).
Воспользуемся cpusetsize, чтобы работать не сразу со всеми ядрами и считать, что cpu_set_t — это unsigned long. В общем случае следует воспользоваться ctypes.Arrays, но это выходит за рамки темы статьи.
Также стоит заметить, что mask передается как указатель, т.е. ctypes.POINTER( ).
После проведения соответствия типов C и ctypes получаем:

После указания argtypes за типами передаваемых значений следит ctypes. Чтобы модуль не ругался, а делал свою работу, корректно укажем значения при вызове:

Как видно, ctypes сам неявно разобрался с указателем. Также стоит заметить, что вызов с pid=0 выполняется над текущим процессом.

Windows XP+

В документации к нужным нам функциям указано:

Теперь мы знаем, когда это будет работать и какую библиотеку нужно грузить.

Делаем по аналогии с Linux версией. Берем заголовки:

В качестве HANDLE нас вполне устроит ctypes.c_uint, а вот с типами out параметров нужно быть аккуратными:
DWORD_PTR — это все тот же ctypes.c_uint, а PDWORD_PTR — это уже ctypes.POINTER(ctypes.c_uint).
Итого получаем:

И кажется, что вот сделаем там и все заработает:

Но увы. Функции принимают не pid, а HANDLE процесса. Его еще нужно получить. Для этого воспользуемся функцией OpenProcess ну и «парной» к ней CloseHandle:

Если не вдаваться в подробности, то мы просто получаем HANDLE нужного нам процесса с доступом на чтение параметров, а при ro=False и на их изменение. Об этом написано в документации по SetProcessAffinityMask и GetProcessAffinityMask:

Так что никакого метода Монте-Карло 🙂

Переписываем наши get_affinity и set_affinity c учетом изменений:

WindowsXP+ для ленивых

Чтобы немного сократить объем кода для Win-реализации можно поставить модуль pywin32. Он избавит нас от необходимости задавать константы и разбираться с библиотеками и параметрами вызова. Наш код выше мог бы выглядеть как-то так:

Кратко, понятно, но это сторонний модуль.

И что в итоге?

Если собрать это все воедино и добавить к нашим первоначальным тестам еще один:

то результаты будут следующими:

Цифры во второй колонке — теже тесты, но с cnt в 10 раз большим.
Мы получили два потока выполнения практически без потери в скорости работы по сравнению с однопоточным вариантом.

Affinity задается битовой маской на обоих ОС. На 4х ядерной машине get_affinity выдает значение 15 (1+2+4+8).

Пример и весь код для статьи выложил на github.
Принимаю любые предложения и претензии.
Также интересуют результаты на процессоре с поддержкой HT и на других версиях Linux.

Всех с первым апреля! Этот код действительно работает 🙂

Источник

Действительно ли Python GIL уже мертв?

Всем привет! Уже в следующий понедельник начинаются занятия в новой группе курса «Разработчик Python», а это значит, что у нас есть время для публикации еще одного интересного материала, чем мы сейчас и займемся. Приятного прочтения.

какие участки кода в python могут выполняться без gil. image loader. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-image loader. картинка какие участки кода в python могут выполняться без gil. картинка image loader. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

В далеком 2003 году Intel выпустил новый процессор Pentium 4 “HT”. Этот процессор разгонялся до 3ГГц и поддерживал технологию гиперпоточности.

какие участки кода в python могут выполняться без gil. 9dz1esccmgms80liftaeolcqiui. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-9dz1esccmgms80liftaeolcqiui. картинка какие участки кода в python могут выполняться без gil. картинка 9dz1esccmgms80liftaeolcqiui. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

В последующие годы Intel и AMD боролись за достижение наибольшей производительности настольных компьютеров, увеличивая скорость шины, размер кэша L2 и уменьшая размер матрицы для минимизации задержки. В 2004 году на смену модели HT с частотой 3ГГц пришла 580 модель “Prescott” с разгоном до 4ГГц.

Казалось, чтобы идти вперед нужно было просто повышать тактовую частоту, однако новые процессоры страдали от высокого энергопотребления и тепловыделения.

Процессор вашего настольного ПК сегодня выдает 4ГГц? Маловероятно, поскольку путь к повышению производительности в конечном итоге лежал через повышение скорости шины и увеличение количества ядер. В 2006 году Intel Core 2 заменил Pentium 4 и имел гораздо более низкую тактовую частоту.

Помимо выпуска многоядерных процессоров для широкой пользовательской аудитории в 2006 году произошло кое-что еще. Python 2.5 наконец увидел свет! Он поставлялся уже с бета версией ключевого слова with, которое вы все знаете и любите.

У Python 2.5 имелось одно серьезное ограничение, когда речь заходила об использовании Intel Core 2 или AMD Athlon X2.
Это был GIL.

Что такое GIL?

GIL (Global Interpreter Lock – глобальная блокировка интерпретатора) – это булевое значение в интерпретаторе Python, защищенное мьютексом. Блокировка используется в основном цикле вычисления байткода CPython, чтобы установить, какой поток в данный момент времени выполняет инструкции.

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

GIL упрощает многопоточное программирование на Python.

какие участки кода в python могут выполняться без gil. image loader. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-image loader. картинка какие участки кода в python могут выполняться без gil. картинка image loader. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

GIL также говорит нам о том, что в то время, как CPython может быть многопоточным, только один поток в любой момент времени может выполняться. Это означает, что ваш четырехъядерный процессор делает примерно это (за исключением синего экрана, надеюсь).

Текущая версия GIL была написана в 2009 году для поддержки асинхронных функций и осталась нетронутой даже после множества попыток убрать ее в принципе или изменить требования к ней.

Любое предложение убрать GIL было обосновано тем, что глобальная блокировка интерпретатора не должна ухудшать производительность однопоточного кода. Тот, кто пробовал включать гиперпоточность в 2003 году, поймет, о чем я говорю.

Отказ от GIL в CPython

Если вы хотите действительно распараллелить код на CPython, вам придется использовать несколько процессов.

В CPython 2.6 модуль multiprocessing был добавлен в стандартную библиотеку. Мультипроцессная обработка (multiprocessing) маскировала собой порождение процессов в CPython (каждый процесс со своей собственной GIL).

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

Мультипроцессная обработка также поддерживает использование переменных через очередь или канал. У нее есть объект блокировки, который используется для блокировки объектов в главном процессе и записи из других процессов.

У мультипроцессной обработки есть один главный недостаток. Она несет значительную вычислительную нагрузку, которая отражается как на времени обработки, так и на использовании памяти. Время запуска CPython даже без no-site составляет 100-200 мс (загляните на https://hackernoon.com/which-is-the-fastest-version-of-python-2ae7c61a6b2b, чтобы узнать больше).

В итоге у вас может быть параллельный код на CPython, но вам все еще нужно тщательно распланировать работу длительных процессов, которые совместно используют несколько объектов.

Другой альтернативой может являться использование стороннего пакета, такого как Twisted.

Итак, напомню, что многопоточность в CPython – это просто, но в действительности это не является распараллеливанием, а вот мультипроцессная обработка параллельна, но влечет за собой значительные накладные расходы.

Что если есть путь лучше?
Ключ к обходу GIL кроется в имени, глобальная блокировка интерпретатора является частью глобального состояния интерпретатора. Процессы CPython могут иметь несколько интерпретаторов и, следовательно, несколько блокировок, однако эта функция используется редко, поскольку доступ к ней есть только через C-API.

Одной из особенностей CPython 3.8, является PEP554, реализация субинтерпретаторов и API с новым модулем interpreters в стандартной библиотеке.

Это позволяет создавать несколько интерпретаторов из Python в рамках одного процесса. Еще одно нововведение Python 3.8 заключается в том, что все интерпретаторы будут иметь свои собственные GIL.

какие участки кода в python могут выполняться без gil. image loader. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-image loader. картинка какие участки кода в python могут выполняться без gil. картинка image loader. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

Поскольку состояние интерпретатора содержит область аллоцированную в памяти, коллекцию всех указателей на объекты Python (локальные и глобальные), субинтерпретаторы в PEP554 не могут получить доступ к глобальным переменным других интерпретаторов.

Лучше всего было бы иметь общее пространство в памяти, которое можно изменять и контролировать определенным процессом. Таким образом, объекты могут быть отправлены главным интерпретатором и получены другим интерпретатором. Это будет пространство управляемой памяти для поиска указателей PyObject, к которому может получить доступ каждый интерпретатор, при этом основной процесс будет управлять блокировками.

какие участки кода в python могут выполняться без gil. image loader. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-image loader. картинка какие участки кода в python могут выполняться без gil. картинка image loader. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

API для этого все еще разрабатывается, но оно, вероятно, будет выглядеть примерно так:

Это выглядит неэффективно

Модуль marshal работает действительно быстро, однако не так быстро, как совместное использование объектов непосредственно из памяти.

В PEP574 представлен новый протокол pickle (v5), который поддерживает возможность обработки буферов памяти отдельно от остальной части потока pickle. Что касается больших объектов данных, то сериализация их всех на одном дыхании и десериализация из субинтерпретатора добавит большое количество накладных расходов.

Новый API может быть реализован (чисто гипотетически) следующим образом —

Это выглядит шаблонно

Как только этот PEP объединится с другими, я думаю, мы увидим несколько новых API в PyPi.

Сколько накладных расходов имеет субинтерпретатор?

Короткий ответ: Больше, чем поток, меньше, чем процесс.
Длинный ответ: Интерпретатор имеет свое собственное состояние, потому ему нужно будет клонировать и инициализовать следующее, несмотря на то, что PEP554 упрощает создание субинтерпретаторов:

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

Как насчет asyncio?

Существующая реализация цикла событий asyncio в стандартной библиотеке создает кадры стека для оценки, а также совместно использует состояние в главном интерпретаторе (и, следовательно, совместно использует GIL).

После объединения PEP554, вероятно уже в Python 3.9, может быть использована альтернативная реализация цикла событий (хотя этого еще никто и не сделал), которая параллельно запускает асинхронные методы в субинтерпретаторах.

Звучит круто, заверните и мне!

Ну, не совсем.
Поскольку CPython так долго работал на одном интерпретаторе, многие части базы кода используют “Runtime State” вместо “Interpreter State”, поэтому если бы PEP554 был введен уже сейчас, проблем все равно было бы много.

Например, состояние сборщика мусора (в версиях 3.7

Источник

Действительно ли Python GIL уже мертв?

Всем привет! Уже в следующий понедельник начинаются занятия в новой группе курса «Разработчик Python», а это значит, что у нас есть время для публикации еще одного интересного материала, чем мы сейчас и займемся. Приятного прочтения.

какие участки кода в python могут выполняться без gil. . какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-. картинка какие участки кода в python могут выполняться без gil. картинка . Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

В далеком 2003 году Intel выпустил новый процессор Pentium 4 “HT”. Этот процессор разгонялся до 3ГГц и поддерживал технологию гиперпоточности.

какие участки кода в python могут выполняться без gil. 9dz1esccmgms80liftaeolcqiui. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-9dz1esccmgms80liftaeolcqiui. картинка какие участки кода в python могут выполняться без gil. картинка 9dz1esccmgms80liftaeolcqiui. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

В последующие годы Intel и AMD боролись за достижение наибольшей производительности настольных компьютеров, увеличивая скорость шины, размер кэша L2 и уменьшая размер матрицы для минимизации задержки. В 2004 году на смену модели HT с частотой 3ГГц пришла 580 модель “Prescott” с разгоном до 4ГГц.

Казалось, чтобы идти вперед нужно было просто повышать тактовую частоту, однако новые процессоры страдали от высокого энергопотребления и тепловыделения.

Процессор вашего настольного ПК сегодня выдает 4ГГц? Маловероятно, поскольку путь к повышению производительности в конечном итоге лежал через повышение скорости шины и увеличение количества ядер. В 2006 году Intel Core 2 заменил Pentium 4 и имел гораздо более низкую тактовую частоту.

Помимо выпуска многоядерных процессоров для широкой пользовательской аудитории в 2006 году произошло кое-что еще. Python 2.5 наконец увидел свет! Он поставлялся уже с бета версией ключевого слова with, которое вы все знаете и любите.

У Python 2.5 имелось одно серьезное ограничение, когда речь заходила об использовании Intel Core 2 или AMD Athlon X2.
Это был GIL.

Что такое GIL?

GIL (Global Interpreter Lock – глобальная блокировка интерпретатора) – это булевое значение в интерпретаторе Python, защищенное мьютексом. Блокировка используется в основном цикле вычисления байткода CPython, чтобы установить, какой поток в данный момент времени выполняет инструкции.

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

GIL упрощает многопоточное программирование на Python.

какие участки кода в python могут выполняться без gil. . какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-. картинка какие участки кода в python могут выполняться без gil. картинка . Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

GIL также говорит нам о том, что в то время, как CPython может быть многопоточным, только один поток в любой момент времени может выполняться. Это означает, что ваш четырехъядерный процессор делает примерно это (за исключением синего экрана, надеюсь).

Текущая версия GIL была написана в 2009 году для поддержки асинхронных функций и осталась нетронутой даже после множества попыток убрать ее в принципе или изменить требования к ней.

Любое предложение убрать GIL было обосновано тем, что глобальная блокировка интерпретатора не должна ухудшать производительность однопоточного кода. Тот, кто пробовал включать гиперпоточность в 2003 году, поймет, о чем я говорю.

Отказ от GIL в CPython

Если вы хотите действительно распараллелить код на CPython, вам придется использовать несколько процессов.

В CPython 2.6 модуль multiprocessing был добавлен в стандартную библиотеку. Мультипроцессная обработка (multiprocessing) маскировала собой порождение процессов в CPython (каждый процесс со своей собственной GIL).

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

Мультипроцессная обработка также поддерживает использование переменных через очередь или канал. У нее есть объект блокировки, который используется для блокировки объектов в главном процессе и записи из других процессов.

У мультипроцессной обработки есть один главный недостаток. Она несет значительную вычислительную нагрузку, которая отражается как на времени обработки, так и на использовании памяти. Время запуска CPython даже без no-site составляет 100-200 мс (загляните на https://hackernoon.com/which-is-the-fastest-version-of-python-2ae7c61a6b2b, чтобы узнать больше).

В итоге у вас может быть параллельный код на CPython, но вам все еще нужно тщательно распланировать работу длительных процессов, которые совместно используют несколько объектов.

Другой альтернативой может являться использование стороннего пакета, такого как Twisted.

Итак, напомню, что многопоточность в CPython – это просто, но в действительности это не является распараллеливанием, а вот мультипроцессная обработка параллельна, но влечет за собой значительные накладные расходы.

Что если есть путь лучше?
Ключ к обходу GIL кроется в имени, глобальная блокировка интерпретатора является частью глобального состояния интерпретатора. Процессы CPython могут иметь несколько интерпретаторов и, следовательно, несколько блокировок, однако эта функция используется редко, поскольку доступ к ней есть только через C-API.

Одной из особенностей CPython 3.8, является PEP554, реализация субинтерпретаторов и API с новым модулем interpreters в стандартной библиотеке.

Это позволяет создавать несколько интерпретаторов из Python в рамках одного процесса. Еще одно нововведение Python 3.8 заключается в том, что все интерпретаторы будут иметь свои собственные GIL.

какие участки кода в python могут выполняться без gil. bqncm29jhm ytakgrlkasbfe 6y. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-bqncm29jhm ytakgrlkasbfe 6y. картинка какие участки кода в python могут выполняться без gil. картинка bqncm29jhm ytakgrlkasbfe 6y. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

Поскольку состояние интерпретатора содержит область аллоцированную в памяти, коллекцию всех указателей на объекты Python (локальные и глобальные), субинтерпретаторы в PEP554 не могут получить доступ к глобальным переменным других интерпретаторов.

Лучше всего было бы иметь общее пространство в памяти, которое можно изменять и контролировать определенным процессом. Таким образом, объекты могут быть отправлены главным интерпретатором и получены другим интерпретатором. Это будет пространство управляемой памяти для поиска указателей PyObject, к которому может получить доступ каждый интерпретатор, при этом основной процесс будет управлять блокировками.

какие участки кода в python могут выполняться без gil. bewwd8ju. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-bewwd8ju. картинка какие участки кода в python могут выполняться без gil. картинка bewwd8ju. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

API для этого все еще разрабатывается, но оно, вероятно, будет выглядеть примерно так:

Это выглядит неэффективно

Модуль marshal работает действительно быстро, однако не так быстро, как совместное использование объектов непосредственно из памяти.

В PEP574 представлен новый протокол pickle (v5), который поддерживает возможность обработки буферов памяти отдельно от остальной части потока pickle. Что касается больших объектов данных, то сериализация их всех на одном дыхании и десериализация из субинтерпретатора добавит большое количество накладных расходов.

Новый API может быть реализован (чисто гипотетически) следующим образом —

Это выглядит шаблонно

Как только этот PEP объединится с другими, я думаю, мы увидим несколько новых API в PyPi.

Сколько накладных расходов имеет субинтерпретатор?

Короткий ответ: Больше, чем поток, меньше, чем процесс.
Длинный ответ: Интерпретатор имеет свое собственное состояние, потому ему нужно будет клонировать и инициализовать следующее, несмотря на то, что PEP554 упрощает создание субинтерпретаторов:

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

Как насчет asyncio?

Существующая реализация цикла событий asyncio в стандартной библиотеке создает кадры стека для оценки, а также совместно использует состояние в главном интерпретаторе (и, следовательно, совместно использует GIL).

После объединения PEP554, вероятно уже в Python 3.9, может быть использована альтернативная реализация цикла событий (хотя этого еще никто и не сделал), которая параллельно запускает асинхронные методы в субинтерпретаторах.

Звучит круто, заверните и мне!

Ну, не совсем.
Поскольку CPython так долго работал на одном интерпретаторе, многие части базы кода используют “Runtime State” вместо “Interpreter State”, поэтому если бы PEP554 был введен уже сейчас, проблем все равно было бы много.

Например, состояние сборщика мусора (в версиях 3.7 Поделиться ссылкой:

Источник

Зачем нужен Python Global Interpreter Lock и как он работает

какие участки кода в python могут выполняться без gil. Untitled 6 1. какие участки кода в python могут выполняться без gil фото. какие участки кода в python могут выполняться без gil-Untitled 6 1. картинка какие участки кода в python могут выполняться без gil. картинка Untitled 6 1. Наверное всем, кто хоть раз интересовался Python, известно про GIL — его одновременно и сильное и слабое место. Не мешая однопоточным скриптам работать, он ставит изрядные палки в колеса при многопоточной работе на CPU-bound задачах (когда потоки выполняются, а не висят попеременно в ожидании I/O и т.п.). Подробности хорошо описаны в переводе двухгодичной давности. Побороть GIL в официальной сборке Python для настоящего распараллеливания потоков мы не можем, но можно пойти другим путем — запретить системе перебрасывать потоки Python между ядрами. В общем пост из серии, «если не нужно, но очень хочется» :) Если вы знаете про processor/cpu affinity, пользовались ctypes и pywin32, то ничего нового не будет.

Python Global Interpreter Lock (GIL) — это своеобразная блокировка, позволяющая только одному потоку управлять интерпретатором Python. Это означает, что в любой момент времени будет выполняться только один конкретный поток.

Работа GIL может казаться несущественной для разработчиков, создающих однопоточные программы. Но во многопоточных программах отсутствие GIL может негативно сказываться на производительности процессоро-зависымых программ.

Поскольку GIL позволяет работать только одному потоку даже в многопоточном приложении, он заработал репутацию «печально известной» функции.

В этой статье будет рассказано о том, как GIL влияет на производительность приложений, и о том, как это самое влияние можно смягчить.

Что за проблему в Python решает GIL?

Python подсчитывает количество ссылок для корректного управления памятью. Это означает, что созданные в Python объекты имеют переменную подсчёта ссылок, в которой хранится количество всех ссылок на этот объект. Как только эта переменная становится равной нулю, память, выделенная под этот объект, освобождается.

Вот небольшой пример кода, демонстрирующий работу переменных подсчёта ссылок:

Проблема, которую решает GIL, связана с тем, что в многопоточном приложении сразу несколько потоков могут увеличивать или уменьшать значения этого счётчика ссылок. Это может привести к тому, что память очистится неправильно и удалится тот объект, на который ещё существует ссылка.

Счётчик ссылок можно защитить, добавив блокираторы на все структуры данных, которые распространяются по нескольким потокам. В таком случае счётчик будет изменяться исключительно последовательно.

Но добавление блокировки к нескольким объектам может привести к появлению другой проблемы — взаимоблокировки (англ. deadlocks), которая получается только если блокировка есть более чем на одном объекте. К тому же эта проблема тоже снижала бы производительность из-за многократной установки блокираторов.

GIL — эта одиночный блокиратор самого интерпретатора Python. Он добавляет правило: любое выполнение байткода в Python требует блокировки интерпретатора. В таком случае можно исключить взаимоблокировку, т. к. GIL будет единственной блокировкой в приложении. К тому же его влияние на производительность процессора совсем не критично. Однако стоит помнить, что GIL уверенно делает любую программу однопоточной.

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

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

Почему для решения проблемы был выбран именно GIL?

Итак, почему же это не очень «хорошее» решение используется в Python? Насколько для разработчиков это решение критично?

По словам Larry Hastings, архитектурное решение GIL — это одна из тех вещей, которые сделали Python популярным.

Python существует с тех времён, когда в операционных системах не существовало понятия о потоках. Этот язык разрабатывался в расчёте на лёгкое использование и ускорение процесса разработки. Всё больше и больше разработчиков переходило на Python.

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

GIL можно было легко реализовать и интегрировать в Python. Он увеличивал производительность однопоточных приложений, поскольку управление велось только одним блокиратором.

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

Как можно понять, GIL — фактическое решение проблемы, с которой столкнулись разработчики CPython в начале жизни Python.

Влияние GIL на многопоточные приложения

Если смотреть на типичную программу (не обязательно написанную на Python) — есть разница, ограничена ли эта программа производительностью процессора или же I/O.

Операции, ограниченные производительностью процессора (англ. CPU-bound) — это все вычислительные операции: перемножение матриц, поиск, обработка изображений и т. д.

Операции, ограниченные производительностью I/O (англ. I/O-bound) — это те операции, которые часто находятся в ожидании чего-либо от источников ввода/вывода (пользователь, файл, БД, сеть). Такие программы и операции иногда могут ждать долгое время, пока не получат от источника то, что им нужно. Это связано с тем, что источник может проводить собственные (внутренние) операции, прежде чем он будет готов выдать результат. Например, пользователь может думать над тем, что именно ввести в поисковую строку или же какой запрос отправить в БД.

Ниже приведена простая CPU-bound программа, которая попросту ведёт обратный отсчёт:

Запустив это на 4х-ядерном компьютере получим такой результат:

Ниже приведена та же программа, с небольшим изменением. Теперь обратный отсчёт ведётся в двух параллельных потоках:

Как видно из результатов, оба варианта затратили примерно одинаковое время. В многопоточной версии GIL предотвратил параллельное выполнение потоков.

GIL не сильно влияет на производительность I/O-операций в многопоточных программах, т. к. в процессе ожидания от I/O блокировка распространяется по потокам.

Однако программа, потоки которой будут работать исключительно с процессором (например обработка изображения по частям), из-за блокировки не только станет однопоточной, но и на её выполнение будет затрачиваться больше времени, чем если бы она изначально была строго однопоточной.

Такое увеличение времени — это результат появления и реализации блокировки.

Почему GIL всё ещё используют?

Разработчики языка получили уйму жалоб касательно GIL. Но такой популярный язык как Python не может провести такое радикальное изменение, как удаление GIL, ведь это, естественно, повлечёт за собой кучу проблем несовместимости.

В прошлом разработчиками были предприняты попытки удаления GIL. Но все эти попытки разрушались существующими расширениями на C, которые плотно зависели от существующих GIL-решений. Естественно, есть и другие варианты, схожие с GIL. Однако они либо снижают производительность однопоточных и многопоточных I/O-приложений, либо попросту сложны в реализации. Вам бы не хотелось, чтобы в новых версиях ваша программа работала медленней, чем сейчас, ведь так?

Создатель Python, Guido van Rossum, в сентябре 2007 года высказался по поводу этого в статье «It isn’t Easy to remove the GIL»:

«Я был бы рад патчам в Py3k только в том случае, если бы производительность однопоточных приложений или многопоточных I/O-приложений не уменьшалась.»

С тех пор ни одна из предпринятых попыток не удовлетворяла это условие.

Почему GIL не был удалён в Python 3?

Python 3 на самом деле имел возможность переделки некоторых функций с нуля, хотя из-за этого многие расширения на С попросту сломались бы и их пришлось бы переделывать. Именно из-за этого первые версии Python 3 так слабо расходились по сообществу.

Но почему бы параллельно с обновлением Python 3 не удалить GIL?

Его удаление сделает однопоточность в Python 3 медленней по сравнению с Python 2 и просто представьте, во что это выльется. Нельзя не заметить преимущества однопоточности в GIL. Именно поэтому он всё ещё не удалён.

Но в Python 3 действительно появились улучшения для существующего GIL. До этого момента в статье рассказывалось о влиянии GIL на многопоточные программы, которые затрагивают только процессор или только I/O. А что насчёт тех программ, у которых часть потоков идут на процессор, а часть на I/O?

В таких программах I/O-потоки «страдают» из-за того, что у них нет доступа к GIL от процессорных потоков. Это связано со встроенным в Python механизмом, который принуждал потоки освобождать GIL после определённого интервала непрерывного использования. В случае, если никто другой не используют GIL, эти потоки могли продолжать работу.

Но тут есть одна проблема. Почти всегда GIL занимается процессорными потоками и остальные потоки не успевают занять место. Этот факт был изучен David Beazley, визуализацию этого можно увидеть здесь.

Проблема была решена в Python 3.2 в 2009 разработчиком Antoine Pitrou. Он добавил механизм подсчёта потоков, которые нуждаются в GIL. И если есть другие потоки, нуждающиеся в GIL, текущий поток не занимал бы их место.

Как справиться GIL?

Если GIL у вас вызывает проблемы, вот несколько решений, которые вы можете попробовать:

После запуска получаем такой результат:

Можно заметить приличное повышение производительности по сравнению с многопоточной версией. Однако показатель времени не снизился до половины. Всё из-за того, что управление процессами само по себе сказывается на производительности. Несколько процессов более сложны, чем несколько потоков, поэтому с ними нужно работать аккуратно.

Альтернативные интерпретаторы Python. У Python есть много разных реализаций интерпретаторов. CPython, Jyton, IronPython и PyPy, написанные на C, Java, C# и Python соответственно. GIL существует только на оригинальном интерпретаторе — на CPython.

Вы просто можете использовать преимущества однопоточности, в то время, пока одни из самых ярких умов прямо сейчас работают над устранением GIL из CPython. Вот одна из попыток.

Зачастую, GIL рассматривается как нечто-то сложное и непонятное. Но имейте ввиду, что как python-разработчик, вы столкнётесь с GIL только если будете писать расширения на C или многопоточные процессорные программы.

На этом этапе вы должны понимать все аспекты, необходимые при работе с GIL. Если же вам интересна низкоуровневая структура GIL — посмотрите Understanding the Python GIL от David Beazley.

Хинт для программистов: если зарегистрируетесь на соревнования Huawei Cup, то бесплатно получите доступ к онлайн-школе для участников. Можно прокачаться по разным навыкам и выиграть призы в самом соревновании.

Перейти к регистрации

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *