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



Книга Как оптимизировать код на 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-кода.


467   0  

Comments

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