Как стать автором
Обновить

Комментарии 8

`zanuda_mode = on`

В данной статье, мы постарались обозреть методы оптимизации кода, от самых простых, до не самых очевидных, с которыми может встретиться в процессе работы DS.

кто может встретиться?

`zanuda_mode = off`

Привет, @dzis_science а что в DS еще не изобрели работу с генераторами как таковыми?

%timeit
a = list()
for i in range(100):
  a.append(i)
%timeit a = [i for i in range(100)]

каждый раз когда вижу цикл по листу или генератор листа, возникает вопрос: зачем?

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

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

попробуй

%timeit a = (i for i in range(100))

будешь удивлен. Как и в любом другом примере статьи.

Да я мухлюю, и прохода по циклу не происходит. Но в этом и есть знание: не делать того, чего не надо делать, и делать то, что надо. Спасибо Кэп.

Замерь время работы своего проекта. Замени все листы в проекте на генераторы. Вероятно, придется переделать часть кода. Замерь время работы своего проекта еще раз. Вот теперь ты реально почуствуешь что такое оптимизация.

Все мои стажеры, даже не претендующие на звание "Junior developer", были обучены этому в первую очередь, прежде чем начинать ловить наносекундных блох на итерациях разных типов.

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

По поводу занудства под DS имел ввиду data scientist.

@danilovmy Генераторы, конечно, - хорошая штука, но и её надо использовать с умом.

каждый раз когда вижу цикл по листу или генератор листа, возникает вопрос: зачем?

в очень редких случаях необходимости неоднократного прохода да

Небольшая проблема - в такой ситуации как раз и нужны генераторы. Списки создаются медленнее, чем генераторы, но вот первый проход по списку сильно обходит в скорости первый проход по генератору. Чтобы не быть голословным (да, обычный python):

# -*- coding: utf-8 -*-
import sys
import timeit


def loop(iterable):
    for i in iterable:
        pass


if __name__ == "__main__":
    listSetTime = timeit.timeit("l = [i for i in range(100_000)]", number=10)/10
    genSetTime = timeit.timeit("g = (i for i in range(100_000))", number=10)
    print("Время создания списка:", listSetTime)
    print("Время создания генератора:", genSetTime, "\n")

    list_ = [i for i in range(100_000)]
    gen = (i for i in range(100_000))

    #Торможением лямбды можно пренебречь, так как она тормозит все циклы.
    firstListLoop = timeit.timeit(lambda: loop(list_), number=1)
    firstGenLoop = timeit.timeit(lambda: loop(gen), number=1)

    print("Время первого обхода списка:", firstListLoop)
    print("Время первого обхода генератора:", firstGenLoop, "\n")
    
    othersListLoops = timeit.timeit(lambda: loop(list_), number=10)/10
    othersGenLoops = timeit.timeit(lambda: loop(gen), number=10)/10
    print("Время последующих обходов списка:", othersListLoops)
    print("Время последующих обходов генератора:", othersGenLoops, "\n")

    print("Итог список (1 проход):", listSetTime + firstListLoop)
    print("Итог генератор (1проход):", genSetTime + firstGenLoop)

И так, что же мы получаем (на моём динозавре):

Время создания списка: 0.007672091200000002
Время создания генератора: 1.5359999999992047e-05

Время первого обхода списка: 0.0018502880000000055
Время первого обхода генератора: 0.01038803100000002

Время последующих обходов списка: 0.0018962978999999991
Время последующих обходов генератора: 5.119999999997349e-07

Итог список (1 проход): 0.009522379200000007
Итог генератор (1 проход): 0.010403391000000012

Мы видим, что если нам надо один раз проитерировать и забыть, нам нужен список, а вот если надо много раз проитерировать, зовите генератор. Однако и про то, что список окаянный жрёт память посильнее хрома забывать не надо.

Эээ генератор это одноразовое,

Время последующих обходов генератора: 5.119999999997349e-07 потому, что генератор пуст.

Но замеры все равно не корректные:

import timeit

firstListLoop = timeit.timeit(lambda: list((a for a in [i for i in range(100_000)])), number=1)
firstGenLoop = timeit.timeit(lambda: list((a for a in (i for i in range(100_000)))), number=1)

print("Время первого обхода списка:", firstListLoop)
print("Время первого обхода генератора:", firstGenLoop, "\n")

Время первого обхода списка: 0.014074000000000808                                                                                                                                                  
Время первого обхода генератора: 0.012207599999999985 

list_ = [i for i in range(100_000)]
othersListLoops = timeit.timeit(lambda: list((a for a in list_)), number=10)
othersGenLoops = timeit.timeit(lambda: list((a for a in (i for i in range(100_000)))), number=10)

print("Время 10 обходов списка:", othersListLoops)
print("Время 10 обходов каждый раз новый генератор:", othersGenLoops, "\n")

Время 10 обходов списка: 0.08319589999999977                                                                                                                                                       
Время 10 обходов каждый раз новый генератор: 0.16070630000000108      

специально впихнул создание листа внутрь `firstListLoop`, что бы учесть время создания.

в первом случае (firstListLoop) два прохода сначала создание листа, потом проход по листу.

во втором случае (firstGenLoop) один проход сквозь два генератора.

в третьем случае (othersListLoops) Время 10 обходов одного списка

в четвертом случае (othersGenLoops) Время 10 обходов 10 новых генераторов.

Но. Я еще раз повторю. Если в коде программиста есть неоднократные проходы по одному листу из 100000 элементов, то ошибка явно в ДНК, ну и, как следствие, в архитектуре алгоритма.

Спасибо, для джуна много полезного. Пишите еще!

Спасибо!

Поддержу!

Спасибо! Подписывайтесь, постараюсь еще выкладывать полезные статьи для начинающих DS!

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории