Рубріки: Решения

В 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= {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, который можно использовать для оформления функции, которую нужно мемоизировать. В приведенном ниже примере: 

  • fib — это простая немемоизированная функция Фибоначчи;
  • fib(35) делает много сложений;
  • mfib — это мемоизированная версия.
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

Машинный код, который создается при компиляции кода, всегда будет работать быстрее, чем интерпретируемый байт-код. Есть несколько компиляторов 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-подход.

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023