Mail.ruПочтаМой МирОдноклассникиВКонтактеИгрыЗнакомстваНовостиКалендарьОблакоЗаметкиВсе проекты

Время выполнения функций

Человек Знаток (472), открыт 5 дней назад
Вопрос новичка, связанный с кишками питона. Почему записанная в две строки функция выполняется быстрее, чему в одну? У второго варианта время выполнения стабильно меньше в 1,5 раза.

 def benchmark(funct): 
import time

def wrapper():
start = time.time()
funct()
print(f'Выполнение: {time.time()-start} сек.')
return wrapper


@benchmark
def test1():
res = [i for i in list(range(10000)) if i % 2 == 0]

@benchmark
def test2():
l = list(range(10000))
res = [i for i in l if i % 2 == 0]

test1()
test2()
5 ответов
speexz Мыслитель (6634) 5 дней назад
На первый взгляд кажется, что оба варианта должны работать с одинаковой скоростью, так как они выполняют по сути одно и то же. Однако в действительности разница во времени выполнения может быть связана с деталями работы Python. Давайте разберем это по шагам:

### В чем различие между функциями?
1. **`test1`**:
    res = [i for i in list(range(10000)) if i % 2 == 0] 
Здесь `range(10000)` сначала преобразуется в список с помощью функции `list()`, а затем этот список используется в списочном выражении (`list comprehension`).

2. **`test2`**:
    l = list(range(10000))
res = [i for i in l if i % 2 == 0]
Здесь `list(range(10000))` создается заранее и сохраняется в переменную `l`, а затем используется в списочном выражении.

### Почему `test2` быстрее?
1. **Обращение к встроенным функциям:**
В первом случае (`test1`) вызов `list(range(10000))` происходит **внутри списочного выражения**, что добавляет некоторую дополнительную нагрузку. Python вынужден каждый раз вызывать встроенную функцию `list()` и одновременно работать с результатом.

2. **Оптимизация промежуточных объектов:**
Во втором случае (`test2`) переменная `l` хранит уже готовый список. Python обращается к уже созданному объекту, что уменьшает накладные расходы на повторное выполнение `list(range(10000))`.

3. **Повторная работа с объектом:**
В первом случае Python обрабатывает генерацию списка и фильтрацию "на лету". Это может быть менее эффективно, поскольку требует больше переключений контекста.

4. **Кэширование объектов:**
В `test2` список `l` уже находится в оперативной памяти и готов к использованию. В `test1` каждый раз создается временный объект, что может быть менее оптимально с точки зрения времени выполнения.

### Как проверить разницу объективно?
Если вы хотите измерить разницу времени выполнения более точно, используйте модуль `timeit`, который специально предназначен для измерения производительности:
 import timeit

setup_code = "l = list(range(10000))"

test1_code = "[i for i in list(range(10000)) if i % 2 == 0]"
test2_code = "[i for i in l if i % 2 == 0]"

print("test1:", timeit.timeit(test1_code, number=1000, globals=globals()))
print("test2:", timeit.timeit(test2_code, setup=setup_code, number=1000, globals=globals()))

### Итог
Разница во времени выполнения связана с тем, что `test1` каждый раз создает новый список с помощью `list(range(10000))`, а в `test2` список создается один раз и используется повторно. Это объясняет, почему `test2` работает быстрее.

Если вам важно максимизировать производительность, старайтесь избегать излишнего создания временных объектов внутри списочных выражений или циклов.
Gamer SANS Лаымоуь Ученик (161) 5 дней назад
Работает не трогай, не работает - иди проветрись
Андрей Высший разум (468399) 5 дней назад
Например, потому, что в первом случае list не имеет никакого смысла и только замедляет код:
 def test3():
res = [i for i in range(10000) if i % 2 == 0]
Но даже это не имеет смысла, т.к. есть многократно более быстрый способ:
 def test4():
res = list(range(0, 10000, 2))
ЧеловекЗнаток (472) 5 дней назад
  1. Так list есть и в первой, и во второй функции. Но стабильно быстрее вторая.
  2. Это же теоретический вопрос.
Читая ответ гптшника выше: получается, питон каждую итерацию заново формирует список?
Андрей Высший разум (468399) Человек, Нет, если бы массив заново формировался на каждой итерации, цикл работал бы не в полтора раза, а на порядки медленнее. Увеличь размер массива до 10000000 и посмотри, как поменяются результаты. Да, разница останется, но совсем другого уровня. Из полученных тобой полутора раз значительная часть времени уходит не на сам цикл, а на действия, выполняемые интерпретатором при запуске цикла.
Жарь Птицев Гуру (3324) 5 дней назад
нипочему она не выполняется быстрее, просто питон медленно разгоняется, измени порядок вызова, сначала 2, потом 1 - теперь 1 быстрее, да? :)
Капиталист Коллаборация Мудрец (12154) 5 дней назад
Сделайте много замеров, например, 10000, а не по одному. Посмотрите, какой будет результат. У меня разница получается намного меньше, но тем не менее всё равно второй вариант быстрее. Весьма интересный вопрос. Но значительную практическую ценность результат вряд ли несёт: если я задумаюсь о производительности своей программы, я вряд ли буду писать её на питоне.
Похожие вопросы