Итерация подмножества показателей эффективности - PullRequest
0 голосов
/ 08 февраля 2019

Я пытаюсь перебрать подмножество элементов в списке на основе другого списка индексов.

Самый короткий / самый питонический способ выглядит так, как если бы он использовал списочное понимание с эффектом:

    for elt in [lst[idx] for idx in idxs]:
        elt.do_stuff()
        elt.do_more_stuff()

Но я не могу избавиться от ощущения, что если я собираюсь зацикливатьсяэто много, перестройка понимания списка каждый раз может быть дорогой, и я должен потратить дополнительную строку, чтобы просто написать:

    for idx in indxs:
        elt = lst[idx]
        elt.do_stuff()
        elt.do_more_stuff()

Может ли кто-то, кто знает больше о Python и его относительной эффективности / неэффективности, войти нафактическая разница в вычислительных затратах между этими подходами?Я знаю, я знаю, что оба способа могли бы работать нормально, но как только я начал задаваться вопросом о разнице, это вызвало у меня любопытство.

1 Ответ

0 голосов
/ 08 февраля 2019

Ваш второй цикл в порядке, но ваш первый цикл можно заставить работать без создания временного list, просто используя выражение генератора :

for elt in (lst[idx] for idx in idxs):
    elt.do_stuff()
    elt.do_more_stuff()

или (возможно,немного быстрее, если есть много индексов) (ab?) с использованием map:

for elt in map(lst.__getitem__, idxs):
    elt.do_stuff()
    elt.do_more_stuff()

В обоих случаях (по крайней мере, для Py3, где map возвращает итератор, а не новый list) эффект заключается в том, чтобы лениво искать каждый индекс при запросе следующего elt;он не с готовностью набирает list до начала цикла.

Есть еще один вариант, который вы могли бы рассмотреть, если вы собираетесь повторно искать один и тот же набор индексов (то есть idxs не меняется).Вы можете создать operator.itemgetter один раз и использовать его.Он будет работать с энтузиазмом (например, понимание list), но он будет:

  1. Возвращать tuple вместо list (немного более эффективный в использовании памяти и с лучшей локализацией памяти, но обычноне имеет существенного значения)
  2. Протолкните здание упомянутого tuple, начните заканчивать, вниз к слою C, где понимание list, при использовании специализированных байт-кодов, все еще должно выполнять всю свою работув обычном интерпретаторе, который, по крайней мере, в CPython медленнее, чем большая часть работы, переданная в C

Для этого подхода вы должны сделать:

# Done once up front
from operator import itemgetter
getidxs = itemgetter(*idxs)  # Note: Will fail if idxs is not at least length 2; won't return tuple when getting one item

# Done every time
for elt in getidxs(lst):
    elt.do_stuff()
    elt.do_more_stuff()

Вам необходимопрофиль, чтобы определить:

  1. нужна ли какая-либо из этих оптимизаций
  2. Какое решение наиболее целесообразно для вас (такие решения, как itemgetter и listcomp, используют больше памяти, номожет работать быстрее, ленивые решения имеют фиксированные и небольшие накладные расходы памяти, но могут работать на много медленнее)
...