Python - язык программирования, известный своей простотой и читабельностью, но когда доходит до скорости - он проигрывает всем. Что очень критично, когда работаешь в машинном обучении и имеешь дело с огромным количеством данных. Сегодня рассмотрим парочку примеров, как можно ускорить ваш код на ровном месте
Использование генераторов вместо списков 🏄
Начнем с оптимизации по памяти. Представим, что перед нами стоит задача пройтись по всем данным и видоизменить их. Причем использовать векторизованные вычисления запрещено (Numpy, Pandas и др.). В этом случае первое что приходит в голову: создать список и все туда накидать. Давайте проверим сколько это займет памяти:
Для этого напишем простенькую функцию для отображения сколько занимает объект оперативки и тут же сгенерируем данных:
Скрытый текст
from sys import getsizeof
from random import random
def memory_consumption(obj):
return f"Total MBs size: {getsizeof(obj) / 1024 ** 2}"
data = [random() for _ in range(10 ** 6)]
После подгрузки данные занимают: Total MBs size: 8.057334899902344
Теперь создаем две функции:
# Первая функция через список
def squares_list(data):
result = []
for elem in data:
result.append(elem * 2)
return result
# Вторая функция через генератор
def squares_generator(data):
for elem in data:
yield elem * 2
# Пример использования
squares_list(1_000_000) # Занимает много памяти
squares_generator(1_000_000) # Экономит память
Тут вторая функция ничего не создает а просто возвращает вам итератор, по которому можно пробежаться и сохранить то, что вам необходимо.
✅Итоговые замеры: решение в "лоб" занимает столько же, сколько исходные данные - 8.06 MBs
, а генераторы обогнали - 1e-6 MBs
! Второе решение удобно применять там, где вам важно много раз создавать объекты либо же хранить только часть вывода
Локальные переменные 📍
Когда пишете свои pipeline, стоит учитывать, что обращение к глобальным переменным может затормозить ваш код. Избавляемся от этого немедленно!
Если это возможно не используйте оператор global
. Лучше создать новую переменную и ее сохранить в каком-либо виде
global_variable = 10
def func1():
global gloval_variable
global_variable = 52
print(global_variable)
def func2():
local_variable = 10
print(local_variable)
func1() # Доступ к глобальной переменной
func2() # Доступ к локальной переменной
Использование for вместо while ⚡️
Циклы for
в Python часто быстрее, чем циклы while
, потому что они оптимизированы для итерации по последовательностям. Если вы знаете количество итераций, используйте for
вместо while
.
# Цикл `while`
i = 0
while i < 10:
print(i)
i += 1
# Цикл `for`
for i in range(10):
print(i)
Избегайте append в циклах 🌀
Использование append
внутри цикла может замедлить выполнение функции, потому что Python каждый раз создает новый список. Если возможно, используйте list
сразу с нужным размером, чтобы избежать частых перераспределений памяти.
# Использование `append`
result = []
for i in range(10):
result.append(i)
# Создание списка с нужным размером
result = [0] * 10
for i in range(10):
result[i] = i
Использование map и filter 🤿
Эти функции позволяют применить функцию к каждому элементу итерируемого объекта (например, списка), не прибегая к написанию собственного цикла. Это делает код более понятным и иногда ускоряет его выполнение.
# Использование `map`
numbers = [1, 2, 3, 4]
squares = map(lambda x: x ** 2, numbers)
print(list(squares))
# Вывод: [1, 4, 9, 16]
# Использование `filter`
numbers = [1, 2, 3, 4, 5]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers))
# Вывод: [2, 4]
Итог 🏖
Эти 5 способов оптимизации функций в Python помогут вам сделать ваш код быстрее и эффективнее. Важно помнить, что не всегда нужно использовать все эти методы одновременно. Экспериментируйте и выбирайте подходящие решения в зависимости от конкретной задачи.
Бонусом забирайте mock-собеседование, где я рассказал что еще можно встретить на собеседование на позицию Data Science. Больше про эффективную оптимизацию кода для рабочих задач я написал тут - пользуйся!