Книга Тиаго Антао «Сверхбыстрый Python» (original) (raw)

Недавно дочитал книгу Тиаго Антао, которая в русскоязычном варианте называется «Сверхбыстрый Python», а в оригинале более скромно — «Fast Python». Ее подзаголовок — «Эффективные техники для работы с большими наборами данных». Сразу скажу, что книга мне очень понравилась и я ее всячески рекомендую тем, кто занимается обработкой данных или большими вычислениями с использованием Python.

Как известно, Python обладает репутацией медленного языка, и к сожалению, заслуженно. Его главное преимущество — это удобство работы. Но иногда требуется скорость, которую чистый Python обеспечить не может, а переписывать всю программу на C / C++ / Rust не хочется. К счастью, для таких случаев в Python есть обходные пути, позволяющие вроде бы писать на языке Python (или его подобии), но при этом значительно повысить скорость расчета или обработки данных. Тут надо оговориться, что книга посвящена приемам и библиотекам, работающим с реализацией CPython, не затрагивая другие реализации Python, такие как PyPy, IronPython, JPython и др.

По сути эта книга является сборником приемов, позволяющих подойти с разных сторон к решению задач оптимизации работы скриптов на Python. В основном внимание сосредоточено не на сложные математические вычисления, а на обработку больших данных. Каждая глава посвящена своей теме: асинхронное и многопроцессное программирование, использованию таких инструментов как Cython (не путайте с CPython) и Numba для компиляции скрипта в более низкоуровневый код, использованию видеокарт и распределенных вычислений. И везде на очень простых примерах, как правило не превышающих страницу, автор показывает основные идеи, используя разные библиотеки, и сравнивая между собой разные решения одной и то же задачи по скорости выполнения.

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

Далее я коротко пробегусь по главам книги и расскажу о чем они.

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

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

Если у вас обычная персоналка или виртуальный сервер у хостера, то такой оптимизации достичь не получится, поскольку за тот же кэш будут бороться не только ваше приложение, но и приложения других клиентов хостера, а в случае персоналки — другие запущенные приложения. Если же вы работаете в кластере, то необходимо учитывать скорость передачи данных по сети между узлами, но тут все зависит от архитектуры сети. Опять же, в одной из будущих глав автор приведет пример, когда время доступа при передаче данных по сети будет меньше, чем время доступа к жесткому диску. В последнее время появилось понятие бессерверных (serverless) вычислений (ИМХО, такое название — это обманка маркетологов), которые требуют особого подхода к оптимизации (далее про бессерверные вычисления автор ничего не говорит). Ну и, наконец, если у вас на сервере (серверах) есть GPU, то почему бы не воспользоваться ими?

Вторая глава посвящена тому, чем нам может помочь сам Python и его стандартные библиотеки при попытке ускорить работу. Первое, что стоит делать, если вас не устраивает производительность — это профилировать код. Узкие места могут оказаться в самом неожиданном месте, и интуиция тут часто обманывает. В этой главе автор на примере простого приложения, которое работает с большим количеством данных в формате CSV, показывает, как пользоваться стандартным для Python профайлером cProfile, а также сторонних инструментов для наглядного представления результатов профилирования. В этой главе упоминаются такие инструменты как SnakeViz (представление результатов профилирования в виде графиков), line_profiler (позволяет находить узкие места вплоть до строчки программы), коротко говорится про использования магической команды %timeit в Jupyter Notebook.

В этой же главе есть интересный раздел, посвященный тому, как непросто определить реальный объем занимаемой памяти объектами, рассказывается про ограничения функции sys.getsizeof() и как их можно обойти. Здесь же говорится о важности выбора правильных контейнеров для хранения данных.

Третья глава, пожалуй, самая сложная. Она посвящена вопросам конкурентности, параллелизму и асинхронности. Подразумевается, что читатель уже имеет об этом какое-то представление и желательно уже иметь опыт работы с асинхронными функциями в Python с помощью asyncio. Здесь автор ссылается на другую замечательную книжку, о которой я когда-то писал — Мэттью Фаулер «Asyncio и конкурентное программирование на Python».

В этой главе на примере разработки простого фреймворка для выполнений операций Map-Reduce рассматриваются различные виды конкурентности. Коротко описываются термины конкурентность, параллелизм, асинхронность, почему их нельзя путать, и какую роль во всем этом играет GIL (Global Interpreter Lock). Из особенностей этой главы я бы отметил использование стандартного модуля marshal для сериализации / десериализации функций и передачи их между процессами, говорится об ограничениях такого подхода (нельзя сериализовать лямбды, только полноценные функции). В примерах к этой главе произошла какая-то путаница с кодом. Формат данных, которые ожидает сервер не соответствует формату, который посылает клиент. Но если открыть исходники к книге, выложенные автором на github, то там все нормально. На основную идею этой главы и то, что исходники должны продемонстрировать, этот косяк не влияет, но запутывает при чтении кода. В примерах этой главы показано, как использовать асинхронность вместе с многопоточностью и многопроцессностью.

Четвертая глава посвящена библиотеке NumPy. Автор сразу предупреждает, что это не учебник по NumPy. Цель этой главы не описать функции этой библиотеки, а посмотреть на нее с точки зрения повышения производительности вычислений. Но в то же время эта глава будет полезна в том числе и тем, кто только начинает пользоваться этой библиотекой, чтобы сразу привыкать использовать векторизованные функции, работающие сразу с массивом целиком, вместо того, чтобы рассчитывать каждый элемент массива отдельно в цикле. В самом начале главы речь пойдет о так называемых представлениях (views), которые позволяют не создавать лишние массивы, а работать с данными «по месту», приводятся примеры, когда NumPy удается создать такие представления, а когда он бессилен и ему приходится создавать новый массив с копированием в него. Здесь же автор рассказывает, как NumPy хранит массивы в памяти и как эти знания можно использовать для ускорения вычислений. Также здесь рассказывается о том, что библиотека NumPy использует более низкоуровневую библиотеку BLAS и LAPACK, для которых есть разные реализации, и не все они одинаково полезны быстры. Авто рекомендует всегда проверять, какую реализацию использует NumPy (главное, чтобы не NetLib), и в случае необходимости устанавливать более шуструю реализацию (например, OpenBLAS или Intel MKL). Завершается глава небольшим разделом про многопоточность в NumPy (здесь опять все зависит от используемой низкоуровневой библиотеки).

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

Пятая глава рассказывает про такой инструмент как Cython (не путайте с CPython), который позволяет транслировать код, сильно напоминающий язык Python, в программу на языке C и компилировать ее. Альтернатива Cython — это Numba, и автор пишет, что в реальной работе он предпочитает именно Numba, но большой кусок книги он посвящает Cython, потому что на этой библиотеке можно показать многие приемы оптимизации, а при использовании Numba с точки зрения пользователя происходит слишком много закулисной магии. Но при этом Numba пользоваться заметно проще, и ей автор отводит место в приложении в самом конце книги.

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

В этой же главе показано, как можно профилировать код, который использует Cython. А в завершении главы показано, как использовать Cython вместе с NumPy, чтобы получить наибольший выигрыш в скорости, а потом еще коротко говорится о том, как в такой связке использовать параллелизм, потому что Cython умеет работать с OpenMP.

Шестая глава достаточно небольшая и посвящена сразу нескольким темам, которые в той или иной мере были охвачены ранее. В основном эта глава охватывает три темы: сжатие данных с целью максимально использовать кэш процессора (о чем коротко было упомянуто в первой главе), использование библиотеки NumExpr, которая позволяет в некоторых случаях заметно ускорить вычисления, а также разработка клиент-серверных приложений для тех случаев, когда у нас имеется надежная и быстрая сеть. Например, рассказывается о библиотеке Blosc, предназначенной для сжатия и обработки двоичных данных.

Седьмая глава посвящена вопросам оптимизации работы с табличными данными с помощью Pandas. Сначала автор показывает, что можно предпринять для ускорения чтения данных средствами самого Pandas, потом призывает на помощь уже знакомые по предыдущим главам Cython и NumExpr. После этого в игру вступает тяжелая артиллерия в виде библиотеки Apache Arrow, которая, как и Pandas, предназначена для работы с табличными данными. Для демонстрации работы с Apache Arrow сначала для анализа данных используется только эта библиотека, а затем показывается, как она может работать в дружбе с Pandas.

В восьмой главе рассказывается о библиотеках, которые можно использовать для хранения больших данных. Но начинается она с описания библиотеки fsspec, которая позволяет абстрагироваться от способа хранения файлов и работать, например, в файлах внутри архивов, через SFTP, SMB, Amazon S3 и даже с репозиториями на github-е как с обычной файловой системой.

После этого автор рассказывает о двух форматах, предназначенных для хранения больших данных. Сначала речь идет о формате Apache Parquet, который изначально создавался для системы Hadoop, а затем про формат Zarr, который, как я понял, является более современным аналогом формата HDF5.

Девятая глава посвящена программированию с использованием GPU с помощью библиотеки CuPy. В этой главе будет использоваться технология CUDA от Nvidia. В начале главы упоминаются и другие технологии — Vulcan и OpenCL, но они остались за бортом этой книги. Также в этой главе используется компилятор Numba, в котором также есть поддержка CUDA. Эту главу нельзя назвать подробным учебником по CUDA, но по крайней мере она позволяет показать, как эту технологию можно задействовать при программировании на Python.

И, наконец, последняя, десятая глава посвящена анализу данных с помощью библиотеки Dask. Эта библиотека позволяет выстаивать конвейер для обработки данных (и даже визуализировать его в виде графа), а затем запускать его за выполнение либо на одном компьютере в многопоточном или многопроцессном режимах, либо использовать распределенные вычисления на кластере компьютеров или с использованием облачных провайдеров. Автор пишет, что по сути Dask напоминает библиотеку Apache Spark, изначально написанную для Java (не путайте с одноименным микрофреймворком Spark). При описании Dask много внимания уделяется вопросу секционирования данных и выявлению узких мест, мешающих эффективной параллельной работе. Судя по всему, Dask достойна отдельной книги, поэтому эту главу можно рассматривать только как способ ознакомления с ней, чтобы узнать, что есть такая интересная штуковина.

Как я уже сказал в начале, книга мне понравилась, ее точно не стоит читать новичкам в Python, а вот если вы уже чувствуете себя уверенным разработчиком, начинаете подозревать, что скорость интерпретатора вас уже не устраивает, и вам нужно выжать из него больше, то книгу «Сверхбыстрый Python» очень советую. Читается книга достаточно легко, примеры не очень сложные. Они написаны таким образом, чтобы сосредоточиться именно на технике оптимизации, а не на каких-то отвлекающих внимание вычислениях или обработке. К сожалению, в коде иногда попадаются досадные опечатки, но они не на понимание сути книги, и в ошибочных местах можно сразу увидеть, что так быть не должно.

Как итог, в своем субъективном рейтинге я этой книжке поставил 5 из 5.

PS. Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.

Загрузка...