Как ускорить работу кода python
Перейти к содержимому

Как ускорить работу кода python

  • автор:

Ускорение выполнения кода Python

Всем привет. У меня есть код, выполнение которого надо ускорить Программа в списке ищет пары, сумма которых равна s . Если таких пар несколько, то в ответ выводится та пара, у которой второй элемент в начальном списке имеет наименьший индекс. Например: [2,4,3,6,3,6], 6 В ответе будет [2,4]

def sum_pairs(ints: list, s: int): ints_set = list(set(ints)) results = <> cashe = set() for i in range(len(ints_set) - 1): for j in range(i, len(ints_set)): if tuple([ints_set[i], ints_set[j]]) in cashe: continue if ints_set[i] + ints_set[j] == s: if ints.index(ints_set[i]) < ints.index(ints_set[j]): results[ints_set[i], ints_set[j]] = ints.index(ints_set[j]) cashe.add(tuple([ints_set[i], ints_set[j]])) elif ints.index(ints_set[j]) < ints.index(ints_set[i]): results[ints_set[j], ints_set[i]] = ints.index(ints_set[i]) cashe.add(tuple([ints_set[j], ints_set[i]])) for i in ints_set: if ints.count(i) >1: sum_equal_nums = i * 2 if sum_equal_nums == s: results[i, i] = ints.index(i, ints.index(i) + 1) if len(results) == 0: return None min_value = min(results.values()) for k, v in results.items(): if v == min_value: return list(k) return None 

Если у кого есть варианты, напишите, пожалуйста Заранее спасибо)

Отслеживать

3,149 2 2 золотых знака 11 11 серебряных знаков 36 36 бронзовых знаков

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

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

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

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

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

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

  • Если ваше приложение будет выполнять огромное количество операций поиска на большой коллекции неповторяющихся элементов, то воспользуйтесь словарем.
  • Это высокопроизводительная коллекция данных.
  • Сложность поиска элемента — O(1).
  • Здесь стоит упомянуть, что словари не эффективны для наборов данных с малым количеством элементов.
items = [‘a’, ‘b’. ’100m’] #1000s of itemsfound = False 
for i in items:
if (i == ‘100m’):
found = True
items = #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 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 datafor d in data:
some_func(d)
import multiprocessingdef some_func(d): 
#computations
data = [1,2. 10000] #large datapool = 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 setdef 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

«Если необходимо, чтобы Ваш код работал быстрее,то вероятно, следует просто использовать PyPy»

Гвидо ван Россум (создатель Python).

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

Оптимизация производительности в любом языке программирования не имеет четкого алгоритма действий. Иногда даже незначительные изменения в «нужном месте» могут ускорить работу кода Python в несколько раз. Оптимизация кода — это придание важности мелочам.

Избегайте применение глобальных переменных.

Python очень медленно обрабатывает доступ к глобальным переменным, особенно внутри циклов. Также множественное применение глобальных переменных очень часто приводит к спагетти-коду, что вызывает дополнительные трудности и проблемы.

Использование сторонних модулей и библиотек.

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

Активно используйте встроенные инструменты Python.

Их применение ускоряет код за счет того, что они предварительно оптимизированы, скомпилированы и следовательно выполняются быстрее.

Примером таких инструментов может быть:

  • встроенная функция map() ;
  • встроенная функция str.join() ;
  • встроенный модуль itertools ;
  • и многое другое.

Работа над кодом.

  • пишите код обдуманно и максимально лаконично;
  • по возможности, внедряйте кэширование объектов;
  • не создавать лишние экземпляры объектов (помните, объекты потребляют дополнительную память);

Перевод статьи словацкого разработчика Мартина Хайнца, в которой он описывает девять практических советов о том, как сделать разработку на Python лучше.

Обрабатывайте входные данные.

Чем больше размер программы, тем выше шансы пропустить уязвимость в коде. Один из способов обезопасить себя от возможных ошибок — очистка входных данных перед выполнением программы (input sanitization). В большинстве случаев при таком подходе достаточно поменять регистр символов или использовать регулярные выражения. Но для сложных случаев есть и более эффективный способ:

user_input = "This\nstring has\tsome whitespaces. \r\n" character_map =  ord('\n') : ' ', ord('\t') : ' ', ord('\r') : None > user_input.translate(character_map) 

Это пример заменяет «пробельные» символы \n и \t обычным пробелом и удаляет \r (все перечисленные конструкции обозначают разные виды пробелов). В зависимости от задач, можно генерировать таблицы соответствий разного размера. Задачу облегчает встроенный модуль unicodedata и функция combining() для генерации и отображения. Их можно использовать для удаления всех акцентов из строки.

Используйте итераторы со срезами.

Итератор — это инструмент для поточной обработки данных. Он отвечает за упрощение навигации по элементам: списку, словарю и так далее. Это такой объект-перечислитель, который выдаёт следующий элемент. В основном его используют в цикле for/in .

Но использовать итератор на полную мощность нужно не всегда. И тут незадача: если попытаться использовать срез итератора, то получим ошибку TypeError . Это произойдёт из-за того, что объект итератора не является подписываемым. К счастью, на такой случай есть простое решение:

import itertools s = itertools.islice(range(50), 10, 20) for val in s: . 

Используя itertools.islice() , можно создать объект islice , который сам по себе является итератором, производящим нужные значения. Важно отметить, что этот объект использует все элементы генератора вплоть до начала среза, что делает itertools.islice() мощным инструментом.

Пропускайте начало итерируемого объекта.

Иногда приходится работать с файлами, которые начинаются с неизвестного количества бесполезных строк, например, с комментариев. И тут модуль itertools снова предлагает простое решение:

string_from_file = """ // Автор: . // Лицензия: . // // Дата: . """ import itertools for line in itertools.dropwhile(lambda line: line.strip().startswith('//'), string_from_file.splitlines()): print(line) 

Этот фрагмент кода создаёт строки, пропуская начальные комментарии. Такой подход может быть полезен, если нужно отбросить элементы (в нашем случае строки) в начале итерируемого объекта.

Используйте kwargs .

Если при разработке программы нужно выполнить несколько похожих действий, то лучшее решение — определить функции для многократного использования в коде. Для этого создается функция с аргументами. Но что делать, если аргументы функции определены, а для ее многократного использования (унификации) нужно передавать разное количество аргументов? Для этого можно использовать ключевые аргументы функции kwargs .

Этот инструмент очень пригодится для создания функции только с именованными аргументами. Это даст возможность (или, скорее, обяжет) использовать такие функции более прозрачно:

def test(*, a, b): pass test("value for a", "value for b") # Traceback (most recent call last): # File "ad.py", line 4, in # test("value for a", "value for b") # TypeError: test() takes 0 positional arguments but 2 were given test(a="value1", b="value2") # Работает. 

Как видно из примера, задачу легко решить, если поместить аргумент * перед ключевыми словами. И, конечно, можно использовать позиционные аргументы, если вставить их до аргумента * .

Используйте объекты, которые поддерживают оператор with .

Открыть файл и заблокировать фрагмент кода можно с помощью оператора with , но можно ли сделать это, пользуясь собственным методом? Да, можно реализовать протокол context manager, используя методы __enter__ и __exit__ :

class Connection: def __init__(self): . def __enter__(self): # инициализация соединения def __exit__(self, type, value, traceback): # закрытие соединения # использование with Connection() as conn: # __enter__() executes . # conn.__exit__() executes 

Это распространённый вариант управления контекстом в Python, но есть и более простой способ:

from contextlib import contextmanager @contextmanager def tag(name): print (f"name>>") yield print (f"name>>") with tag("h1"): print("This is Title.") 

Этот фрагмент кода реализует протокол управления контекстом, используя декоратор менеджера contextmanager . Первая часть функции tag() (до yield ) выполняется при входе в блок with , затем исполняется блок, а после него и остальная часть функции tag() .

Сохраните всё с помощью __slots__ .

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

class Person: __slots__ = ["first_name", "last_name", "phone"] def __init__ (self, first_name, last_name, phone): self.first_name = first_name self.last_name = last_name self.phone = phone 

При определении атрибута __slots__ Python использует небольшой массив фиксированного размера для атрибутов вместо словаря. Это значительно сокращает объём памяти, необходимый для каждого инстанса.

Следует учесть, что в этом случае есть и недостатки: нельзя объявлять какие-либо новые атрибуты помимо используемых в __slots__ , а классы со __slots__ не могут использовать множественное наследование.

Ограничьте использование процессора и памяти.

Если лень оптимизировать память программы или корректировать работу процессора, то можно просто установить лимиты. К счастью, в Python для этого есть специальный модуль resource :

import signal import resource def time_exceeded(signo, frame): print("CPU exceeded. ") raise SystemExit(1) def set_max_runtime(seconds): soft, hard = resource.getrlimit(resource.RLIMIT_CPU) resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard) ) signal.signal(signal.SIGXCPU, time_exceeded) def set_max_memory(size): soft, hard = resource.getrlimit(resource.RLIMIT_AS) resource.setrlimit(resource.RLIMIT_AS, (size, hard)) 

Здесь можно увидеть две опции: установку на максимальное процессорное время и максимальный предел используемой памяти.

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

Что касается памяти, то, как и в случае с процессором, устанавливаем мягкий и жёсткий лимиты. Для этого используется функция resource.setrlimit() с аргументом size и жёсткое ограничение, которое было получено.

Управляйте экспортом элементов.

Такие языки программирования, как Go, имеют механизм экспорта только для элементов (переменных, методов, интерфейсов) начинающихся с заглавной буквы. В Python подобного можно добиться с помощью переменной __all__ :

def foo(): pass def bar(): pass __all__ = ["bar"] 

В данном случае, благодаря __all__ экспортирован будет не весь код, а только функция bar() . Кроме того, можно оставить переменную пустой, то при попытке импорта из этого модуля ничего не попадёт в экспорт, что приведёт к ошибке AttributeError .

Упростите использование операторов сравнения.

Использовать все операторы сравнения для одного класса может быть довольно сложно, учитывая, что их немало: __lt__ , __le__ , __gt__ или __ge__ . Но есть ли более простой способ сделать это? Здесь поможет functools.total_ordering :

from functools import total_ordering @total_ordering class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value  other.value def __eq__ (self, other): return self.value == other.value print(Number(20) > Number(3)) print(Number(1)  Number(5)) print(Number(15) >= Number(15)) print(Number(10)  Number(2)) 

Как это работает? Декоратор @total_ordering автоматически добавляет все остальные методы. В этом случае нужно только определить __lt__ и __eq__ , а все остальные пробелы за нас заполнит декоратор.

В 20 тысяч раз быстрее: 5 приемов для ускорения кода на Python

Несмотря на то, что Python — один из самых популярных языков программирования в мире, он не лишен недостатков. Самый большой из них, о котором, вероятно, известно всем — это скорость. О пяти способах улучшить Python-код в блоге на Dice рассказал разработчик программного обеспечения Дэвид Болтон.

Примечание: автор протестировал способы на Python 3.7 и 3.9, а чтобы вести отсчет времени в наносекундах, использовал функцию perf_counter_ns из пакета time .

Pythonic

Простыми словами, Pythonic — стиль кода. Поэтому, говоря, что какой-либо код — pythonic — имеется в виду, что он написан в соответствии с идиомами Python. Это использование таких функций как map, sum и range, а также понимание списков и генераторов.

Например, нужно сравнить два способа подсчета всех целых чисел в диапазоне от 1-100, которые кратны 3:

from time import perf_counter_ns def main(): # non pythonic start=perf_counter_ns() total=0 for i in range(1,100): if (i %3)== 0: total += i end=perf_counter_ns() print (f"Non-Pythonic Total of divisible by 3= ") print(f"Time took ") # pythonic start=perf_counter_ns() total =sum(range(1, 100, 3)) end=perf_counter_ns() print (f"Pythonic Total of divisible by 3= ") print(f"Time took ") if __name__ == "__main__": main()

Курс GameDev-тестувальник.
Онлайн-курс з нуля про методології тестування та роботу з багами й фічами.

Non-Pythonic Total of divisible by 3= 1683 Time took 13300 Pythonic Total of divisible by 3= 1683 Time took 2900

Это время второго прогона. Первые запуски были 14,500 и 3,000, то есть от 3,5% до 9% дольше. В данном случае Pythonic-код почти в пять раз быстрее обычного.

Создатель Python Гвидо ван Россум

Мемоизация

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

В пакете functool s есть lru_cache , который можно использовать для оформления функции, которую нужно мемоизировать. В приведенном ниже примере:

    fib — это простая немемоизированная функция Фибоначчи;

from time import perf_counter_ns from functools import lru_cache def main(): def fib(n): return n if n < 2 else fib(n-1) + fib(n-2) @lru_cache(maxsize=None) def mfib(n): return n if n < 2 else mfib(n-1) + mfib(n-2) start=perf_counter_ns() print(f"Non-memoized fib()=") end=perf_counter_ns() print(f"Time took ") start=perf_counter_ns() print(f"Memoized fib()=") end=perf_counter_ns() print(f"Time took ") if __name__ == "__main__": main()

Результат говорит сам за себя:

Non-memoized fib()=9227465 Time took 2905175700 Memoized fib()=9227465 Time took 148700

Код выполнился почти в 20 тысяч раз быстрее.

Кстати, разработчик Орен Тош считает , что код можно еще ускорить, используя подкласс Dictionary с методом __missing__ dunder .

Перевод кода на С

Это не всегда легко, так как для этого нужно знать язык C и то, как он взаимодействует с Python. Кроме того, может быть всего несколько случаев, когда кодинг на C поможет. Помогает то, что CPython написан на C.

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

Компиляция Python

Машинный код, который создается при компиляции кода, всегда будет работать быстрее, чем интерпретируемый байт-код. Есть несколько компиляторов Python, включая Numpa , Nuitka , pypi и Cython . Автор советует оптимизировать код Python, прежде чем пытаться компилировать его. Компилятор Numpa — JIT (Just-In-Time), который также обеспечивает ускорение на GPU.

Использование from

Можно постоянно использовать пакет import , но более разумно использовать from , когда можно импортировать только нужную функцию (или функции). Зачем импортировать 20 функций, если нужна только одна? Для таких коротких программ, как ниже, вероятно, разница не будет заметна, но с увеличением размера программы она станет очевидна:

from time import perf_counter_ns def main(): start=perf_counter_ns() n = 10 fact = 1 for i in range(1,n+1): fact = fact * i end=perf_counter_ns() print (f"The factorial of is ") print(f"Time took ") if __name__ == "__main__": main()

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

Заключение

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

Курс Solidity для блокчейн-розробки.

Комплексний курс, який навчить створювати смартконтракти й повноцінні децентралізовані застосунки мовою Solidity, гарантувати безпеку та гнучку функціональність.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *