пользовательская производительность итератора - PullRequest
5 голосов
/ 20 января 2012

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

Сначала немного контекста; Я пишу несколько модулей расширения Python, использующих Boost :: Python, один из которых содержит привязку к 3D векторному типу с плавающей запятой, который реализует getitem. Так как он имеет getitem, его можно перебирать, однако он выглядит довольно медленно, но не понятно почему, поэтому я решил поиграть с некоторыми простыми пользовательскими итераторами в python, чтобы лучше понять, как все работает. Откуда пришли эти итераторы ...

class MyIterator1(object):
    __slots__ = ['values', 'popfn']

    def __init__(self):
        self.values = ['x', 'y', 'z']
        self.popfn = self.values.pop

    def __length_hint__(self):
        return 3

    def __iter__(self):
        return self

    def next(self):
        try:
            return self.popfn()
        except IndexError:
            raise StopIteration

class MyIterator2(object):
    __slots__ = ['values', 'itfn']

    def __init__(self):
        self.values = ['x', 'y', 'z']
        it = iter(self.values)
        self.itfn = it.next

    def __length_hint__(self):
        return 3

    def __iter__(self):
        return self

    def next(self):
        return self.itfn()

class MyIterator3(object):
    __slots__ = ['values', 'i']

    def __init__(self):
        self.values = ['x', 'y', 'z']
        self.i = 0

    def __length_hint__(self):
        return 3

    def __iter__(self):
        return self

    def next(self):
        if self.i >= 3:
            raise StopIteration
        value = self.values[self.i]
        self.i += 1
        return value

def MyIterator4():
    val = ['x', 'y', 'z']
    yield val[0]
    yield val[1]
    yield val[2]

Затем я провел их через скрипт с модулем timeit (который предполагает, что приведенный выше код находится в модуле под названием testiter)

import timeit

timer1 = timeit.Timer('r = list(testiter.MyIterator1())', 'import testiter')
timer2 = timeit.Timer('r = list(testiter.MyIterator2())', 'import testiter')
timer3 = timeit.Timer('r = list(testiter.MyIterator3())', 'import testiter')
timer4 = timeit.Timer('r = list(testiter.MyIterator4())', 'import testiter')
timer5 = timeit.Timer('r = list(iter(["x", "y", "z"]))', 'import testiter')

print 'list(testiter.MyIterator1())'
print timer1.timeit()

print "\n"

print 'list(testiter.MyIterator2())'
print timer2.timeit()

print "\n"

print 'list(testiter.MyIterator3())'
print timer3.timeit()

print "\n"

print 'list(testiter.MyIterator4())'
print timer4.timeit()

print "\n"

print 'list(iter(["x", "y", "z"]))'
print timer5.timeit()

Это распечатывает следующее

list(testiter.MyIterator1())
8.57359290123


list(testiter.MyIterator2())
5.28959393501


list(testiter.MyIterator3())
6.11230111122


list(testiter.MyIterator4())
2.31263613701


list(iter(["x", "y", "z"]))
1.26243281364

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

Любая помощь в объяснении этих различий будет принята с благодарностью! Извиняюсь за длинный пост.

1 Ответ

2 голосов
/ 20 января 2012

Несколько идей с моей головы; извините, если они недостаточно ощутимы:

  • Первый итератор использует pop списка для реализации next, то есть список мутирует после извлечения каждого элемента. Возможно, эта мутация динамически распределенной составной структуры данных снижает производительность. Все зависит от деталей реализации списков, так что это может быть совершенно неактуально.
  • Последний итератор использует функцию, встроенную в язык (yield), в простом контексте для получения результата. Предположим, я скажу, что это означает, что для оптимизации имеется больше возможностей, чем для пользовательского класса итераторов, пытающегося достичь того же результата.
  • Пятый таймер не использует пользовательский итератор, а вместо него использует тот, который предоставлен для списков напрямую. Итераторы для списков, вероятно, хорошо оптимизированы, и нет никакого уровня косвенности, который используют эти пользовательские классы, некоторые из которых все равно используют такой итератор списка.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...