В 20 тысяч раз быстрее: 5 приемов для ускорения кода на Python
Несмотря на то, что Python — один из самых популярных языков программирования в мире, он не лишен недостатков. Самый большой из них, о котором, вероятно, известно всем — это скорость. О пяти способах улучшить Python-код в блоге на Dice рассказал разработчик программного обеспечения Дэвид Болтон.
Примечание: автор протестировал способы на Python 3.7 и 3.9, а чтобы вести отсчет времени в наносекундах, использовал функцию perf_counter_ns из пакета time.
Простыми словами, 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= {total}")
print(f"Time took {end-start}")
# pythonic
start=perf_counter_ns()
total =sum(range(1, 100, 3))
end=perf_counter_ns()
print (f"Pythonic Total of divisible by 3= {total}")
print(f"Time took {end-start}")
if __name__ == "__main__":
main() Это дает нам:
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-код почти в пять раз быстрее обычного.
Метод ускорения медленных функций. Это способ оптимизации, при котором сохраняется результат выполнения функции, и этот результат используется при следующем вызове. Если повторные вызовы функций выполняются с одинаковыми параметрами, можно сохранить предыдущие значения вместо повторения ненужных вычислений.
В пакете functools есть lru_cache, который можно использовать для оформления функции, которую нужно мемоизировать. В приведенном ниже примере:
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()={fib(35)}")
end=perf_counter_ns()
print(f"Time took {end-start}")
start=perf_counter_ns()
print(f"Memoized fib()={mfib(35)}")
end=perf_counter_ns()
print(f"Time took {end-start}")
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, включая 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 {n} is {fact}")
print(f"Time took {end-start}")
if __name__ == "__main__":
main() При первом запуске было получено 4200 наносекунд, а при последующих — около 3900. Не забывайте, что можно помещать импорты внутрь функций, чтобы они вызывались только тогда, когда это необходимо.
Если нужно выделить какой-то один способ, автор склоняется к использованию мемоизации, которая дает максимальные показатели по скорости, но чтобы стать лучшим программистом на Python, желательно освоить Pythonic-подход.
На фоне роста спроса на ликвидность в бычьем рынке 2025 года, криптозаймы снова выходят на…
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…