SkillFactory 07 мая 2019

Ближе к земле: Python и низкоуровненые операции

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

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

Связи – прежде всего

Как известно, Python — это высокоуровневый язык, который не взаимодействует с компьютерным «железом» напрямую. Ваш код компилируется в так называемый байт-код, с которым работает интерпретатор Python, передающий инструкции процессору. Многие специалисты называют эту утилиту виртуальной машиной (ВМ), хотя между этими понятиями есть разница.

Именно интерпретатор Python распоряжается имеющимися ресурсами, позволяя программам записывать новые данные и удаляя устаревшие. Чтобы понять, какая информация зря занимает место, ВМ использует структуру PyObject. Она учитывает существующие между объектами связи, исходя из того, что если на какой-то из них нет ссылок, то и смысла в его существовании нет.

Когда переменная делает ссылку на объект, передаёт его в качестве аргумента или добавляет в список, PyObject добавляет единицу на соответствующем счётчике. Если счётчик оказывается на нуле, занятая объектом память освобождается. Это может запустить цепочку удалений, если в устаревшем объекте были ссылки на другие. С его удалением соответствующие счётчики уменьшатся на единицу, в каких-то случаях — до нуля. Цикл повторится.

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

Ты не пройдешь

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

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

Принцип GIL обеспечивает потокобезопасность при подсчёте ссылок, не позволяя нескольким объектам менять свои значения одновременно. Для этого в каждый момент интерпретатор открывает доступ к объектам только одному потоку. В некоторых ситуациях (например, в операциях ввода/вывода) блокировка приостанавливается, чем пользуются продвинутые Python-программисты. Но это исключения, а в большинстве случаев GIL твёрдо контролирует соблюдение очереди.

В результате Python фактически закрыт для многопоточных сценариев, зато однопоточные выполняются значительно быстрее, чем с другими техниками потокобезопасности (например, при использовании системы отдельных запретов). Именно этот довод играет ключевую роль для автора Python Гвидо ван Россума, который не раз говорил оппонентам, что GIL останется в языке до тех пор, пока программисты не смогут иначе гарантировать нынешнюю скорость однопоточных операций.

Сухой остаток

Альтернатива механизму подсчёта ссылок и, как следствие, глобальной блокировке — это «мусоросборочные» механизмы (garbage collection) вроде тех, что используются в Java или языках .NET. Как можно догадаться по названию, эти компоненты сами находят в памяти устаревшие данные и удаляют их. Это более ресурсоёмкая задача, поэтому, например, Android-приложения на языке Kotlin (использует сборщик мусора) могут работать медленнее своих iOS-аналогов на SWIFT (считает ссылки).

Зато такие программы получают все преимущества многопоточных операций, а близость Java к «физическому» компьютеру ускоряет вычисления. С другой стороны, зачем это обычному пользователю, который сидит на компьютере с четырьмя ядрами… В общем, вы сами видите, что этот спор может продолжаться вечно.

Самое главное, что даже с этими запретами Python остаётся самым эффективным языком с точки зрения скорости разработки. Возможность абстрагироваться от компьютерной архитектуры и писать код, с которым легко разберётся сторонний специалист, позволяет программистам быстрее создавать свои продукты. Это экономит деньги заказчика ПО, значит, при прочих равных рынок будет склоняться в пользу Python. А там и производительность подтянется.

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ