Как оптимизировать код на Python


Как я сократил время выполнения приложения на 1/10

Данные советы просты в реализации и могут пригодиться вам в обозримом будущем.

Считается, что первоочередной задачей программиста является написание чистого и эффективного кода. Как только вы создали чистый код, можете переходить к следующим 10 подсказкам. Я подробно объясню их ниже.

Как я измеряю время и сложность кода?

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

python -m cProfile [-o output_file] my_python_file.py

Используйте структуры данных из хеш-таблиц

  • Если ваше приложение будет выполнять огромное количество операций поиска на большой коллекции неповторяющихся элементов, то воспользуйтесь словарем.
  • Это высокопроизводительная коллекция данных.
  • Сложность поиска элемента — O(1).
  • Здесь стоит упомянуть, что словари не эффективны для наборов данных с малым количеством элементов.

Вместо:

items = [‘a’, ‘b’,..,’100m’] #1000s of items found = False for i in items: if (i == ‘100m’): found = True

Пишите:

items = {‘a’:’a’, ‘b’:’b:,..,’100m’:’100m’} #each item is key/value found = False if ‘100m’ in items: found = True

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

Векторизация вместо циклов

Присмотритесь к Python-библиотекам, созданным на С (Numpy, Scipy и Pandas), и оцените преимущества векторизации. Вместо прописывания цикла, который раз за разом обрабатывает по одному элементу массива М, можно выполнять обработку элементов одновременно. Векторизация часто включает в себя оптимизированную стратегию группировки.

import numpy as np array = np.array([[1., 2., 3.], [4., 5., 6.]]) m_array = array*array

Сократите количество строк в коде

Пользуйтесь встроенными функциями Python. Например, map()

Вместо:

newlist = [] def my_fun(a): return a + ‘t’ for w in some_list: newlist.append(my_fun(w))

Пишите:

def my_fun(a): return a + ‘t’ newlist = map(my_fun, some_list)

Каждое обновление строковой переменной создает новый экземпляр

Вместо:

my_var = ‘Malik’ myname_blog = ‘Farhad ‘ + my_var + ‘ FinTechExplained’

Пишите:

my_var = ‘Malik’ myname_blog = ‘Farhad {0} FinTechExplained’.format(my_var)

Пример выше уменьшает объем памяти.

Для сокращения строк пользуйтесь циклами и генераторами for

Вместо:

for x in big_x: for y in x.Y: items.append(x.A + y.A)

Пишите:

items = [x.A+y.A for x in big_x for y in x.Y]

Пользуйтесь многопроцессорной обработкой

Если ваш компьютер выполняет более одного процесса, тогда присмотритесь к многопроцессорной обработке в Python.

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

Вместо:

def some_func(d): #computations data = [1,2,..,10000] #large data for d in data: some_func(d)

Пишите:

import multiprocessing def some_func(d): #computations data = [1,2,..,10000] #large data pool = multiprocessing.Pool(processes=number_of_processors) r = pool.map(some_func, data) pool.close()

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

Пользуйтесь Cython

Cython — это статический компилятор, который будет оптимизировать код за вас.

Загрузите расширения Cythonmagic и пользуйтесь тегом Cython для компиляции кода через Cython.

Воспользуйтесь Pip для установки Cython:

pip install Cython

Для работы с Cython:

%load_ext cythonmagic %%cython def do_work(): … #работа с большим объемом вычислений

Пользуйтесь Excel только при необходимости

Не так давно мне нужно было реализовать одно приложение. И мне бы пришлось потратить много времени на загрузку и сохранение файлов из/в Excel. Вместо этого я пошел другим путем: создал несколько CSV-файлов и сгруппировал их в отдельной папке.

Примечание: все зависит от задачи. Если создание файлов в Excel сильно тормозит работу, то можно ограничиться несколькими CSV-файлами и утилитой на нативном языке, которая объединит эти CSV в один Excel-файл.

Вместо:

df = pd.DataFrame([[‘a’, ‘b’], [‘c’, ‘d’]],index=[‘row 1’, ‘row 2’],columns=[‘col 1’, ‘col 2’]) df.to_excel(“my.xlsx”) df2 = df.copy() with pd.ExcelWriter(‘my.xlsx’) as writer: df.to_excel(writer, sheet_name=’Sheet_name_1') df2.to_excel(writer, sheet_name=’Sheet_name_2')

Пишите:

df = pd.DataFrame([[‘a’, ‘b’], [‘c’, ‘d’]],index=[‘row 1’, ‘row 2’],columns=[‘col 1’, ‘col 2’]) df2 = df.copy() df.to_csv(“my.csv”) df2.to_csv(“my.csv”)

Пользуйтесь Numba

Это — JIT-компилятор (компилятор «на лету»). С помощью декоратора Numba компилирует аннотированный Python- и NumPy-код в LLVM.

Разделите функцию на две части: 1. Функция, которая выполняет вычисления. Ее декорируйте с @autojit.

2. Функция, которая выполняет операции ввода-вывода.

from numba import jit, autojit @autojit def calculation(a): …. def main(): calc_result = calculation(some_object) d = np.array(calc_result) #save to file return d

Пользуйтесь Dask для распараллеливания операций Pandas DataFrame

Dask очень классный! Он помог мне с параллельной обработкой множества функций в DataFrame и NumPy. Я даже попытался масштабировать их в кластере, и все оказалось предельно просто!

import pandas as pd import dask.dataframe as dd from dask.multiprocessing import get data = pd.DataFrame(…) #large data set def my_time_consuming_function(d): …. #долго выполняемая функция ddata = dd.from_pandas(data, npartitions=30) def apply_my_func(df): return df.apply( (lambda row: my_time_consuming_function(*row)), axis=1) def dask_apply(): return ddata.map_partitions(apply_my_func).compute(get=get)

Пользуйтесь пакетом swifter

Swifter использует Dask в фоновом режиме. Он автоматически рассчитывает наиболее эффективный способ для распараллеливания функции в пакете данных.

Это плагин для Pandas.

import swifter import pandas as pd a_large_data_frame = pd.DataFrame(…) #large data set def my_time_consuming_function(data): … result = a_large_data_frame.swifter.apply(some_function)

Пользуйтесь пакетом Pandarallel

Pandarallel может распараллеливать операции на несколько процессов.

Опять же, подходит только для больших наборов данных.

from pandarallel import pandarallel from math import sin pandarallel.initialize() # ALLOWED def my_time_consuming_function(x): …. df.parallel_apply(my_time_consuming_function, axis=1)

Общие советы

  • Первым делом нужно писать чистый и эффективный код. Мы должны проследить, чтобы код внутри цикла не выполнял одни и те же вычисления.
  • Также важно не открывать/закрывать подключения ввода-вывода для каждой записи в коллекции.
  • Подумайте, можно ли кэшировать объекты.
  • Проверьте, что не создаете новые экземпляры объектов там, где они не нужны.
  • И, наконец, убедитесь, что код написан лаконично и не выполняет одни и те же повторяющиеся задачи со сложными вычислениями.

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

Заключение

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


Перевод статьи Farhad Malik: How To Make Python Faster


Поделиться статьей:


Вернуться к статьям

Комментарии

    Ничего не найдено.