Как я сократил время выполнения приложения на 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-кода.
Читайте также:
- Давайте синхронизировать потоки в Python
- 10 внешних Python-пакетов, которые вам точно понравятся
- Полезные хитрости на Python от А до Я
Перевод статьи Farhad Malik: How To Make Python Faster