Каков самый быстрый (для доступа) структурный объект в Python?
я оптимизирую некоторый код, основным узким местом которого является запуск и доступ к очень большому списку структурно-подобных объектов. В настоящее время я использую namedtuples, для удобства чтения. Но некоторые быстрые бенчмаркинги с использованием "timeit" показывают, что это действительно неправильный способ пойти туда, где производительность является фактором:
именованный кортеж с a, b, c:
>>> timeit("z = a.c", "from __main__ import a")
0.38655471766332994
класс с помощью __slots__, С a, b, c:
>>> timeit("z = b.c", "from __main__ import b")
0.14527461047146062
словарь с ключами a, b, c:
>>> timeit("z = c['c']", "from __main__ import c")
0.11588272541098377
кортеж с тремя значениями, используя постоянный ключ:
>>> timeit("z = d[2]", "from __main__ import d")
0.11106188992948773
список с тремя значениями, используя постоянный ключ:
>>> timeit("z = e[2]", "from __main__ import e")
0.086038238242508669
кортеж с тремя значениями, используя локальный ключ:
>>> timeit("z = d[key]", "from __main__ import d, key")
0.11187358437882722
список с тремя значениями, используя локальный ключ:
>>> timeit("z = e[key]", "from __main__ import e, key")
0.088604143037173344
во-первых, есть ли что-нибудь об этих маленьких timeit тесты, которые сделают их недействительными? Я запускал каждый несколько раз, чтобы убедиться, что не было случайного системного события их сняли, и результаты оказались почти идентичными.
похоже, что словари предлагают лучший баланс между производительностью и удобочитаемостью, причем классы идут на втором месте. Это неудачно, так как для моих целей мне также нужно, чтобы объект был похож на последовательность; следовательно, мой выбор namedtuple.
списки существенно быстрее, но постоянные ключи недоступны; мне пришлось бы создать кучу индексов-констант, т. е. KEY_1 = 1, KEY_2 = 2 и т. д. что тоже не идеальный.
Я застрял с этими вариантами, или есть альтернатива, которую я пропустил?
5 ответов:
одна вещь, чтобы иметь в виду, что namedtuples оптимизированы для доступа в виде кортежей. Если вы измените свой аксессуар на
a[2]вместоa.c, вы увидите аналогичную производительность кортежей. Причина в том, что методы доступа к именам эффективно преобразуются в вызовы self[idx], поэтому оплачивайте оба индексирования и цена поиска имени.если ваш шаблон использования таков, что доступ по имени является общим, но доступ как кортеж не является, вы может напишите быстрый эквивалент namedtuple, который делает все наоборот: откладывает поиск индекса для доступа по имени. Тем не менее, вы будете платить цену на поиск индекса тогда. Например, вот быстрая реализация:
def makestruct(name, fields): fields = fields.split() import textwrap template = textwrap.dedent("""\ class {name}(object): __slots__ = {fields!r} def __init__(self, {args}): {self_fields} = {args} def __getitem__(self, idx): return getattr(self, fields[idx]) """).format( name=name, fields=fields, args=','.join(fields), self_fields=','.join('self.' + f for f in fields)) d = {'fields': fields} exec template in d return d[name]но тайминги очень плохо, когда
__getitem__должно называться:namedtuple.a : 0.473686933517 namedtuple[0] : 0.180409193039 struct.a : 0.180846214294 struct[0] : 1.32191514969т. е. такая же производительность, как и
__slots__класс для доступа к атрибутам (неудивительно - вот что это такое), но огромные штрафы из-за двойного поиска в индексных обращается. (Примечательно, что__slots__на самом деле не очень помогает быстро. Это экономит память, но время доступа примерно такое же без них.)один третий вариант будет дублировать информацию, например. подкласс из списка и хранить значения как в атрибутах и listdata. Однако вы на самом деле не получаете эквивалентную по списку производительность. Существует большая скорость хита только в том, что подкласс (вводит проверки для перегрузок pure-python). Таким образом, struct [0] все еще занимает около 0,5 С (по сравнению с 0.18 для необработанного списка) в этом случае, и вы удваиваете использование памяти, так что это может не стоить того.
этот вопрос довольно старый (Интернет-время), поэтому я подумал, что попробую повторить ваш тест сегодня, как с обычным CPython (2.7.6), так и с pypy (2.2.1) и посмотреть, как сравниваются различные методы. (Я также добавил в индексированном поиске для именованного кортежа.)
Это немного микро-бенчмарк, поэтому YMMV, но pypy, казалось, ускорял доступ к именованному кортежу в 30 раз против CPython (в то время как доступ к словарю был ускорен только в несколько раз 3).
from collections import namedtuple STest = namedtuple("TEST", "a b c") a = STest(a=1,b=2,c=3) class Test(object): __slots__ = ["a","b","c"] a=1 b=2 c=3 b = Test() c = {'a':1, 'b':2, 'c':3} d = (1,2,3) e = [1,2,3] f = (1,2,3) g = [1,2,3] key = 2 if __name__ == '__main__': from timeit import timeit print("Named tuple with a, b, c:") print(timeit("z = a.c", "from __main__ import a")) print("Named tuple, using index:") print(timeit("z = a[2]", "from __main__ import a")) print("Class using __slots__, with a, b, c:") print(timeit("z = b.c", "from __main__ import b")) print("Dictionary with keys a, b, c:") print(timeit("z = c['c']", "from __main__ import c")) print("Tuple with three values, using a constant key:") print(timeit("z = d[2]", "from __main__ import d")) print("List with three values, using a constant key:") print(timeit("z = e[2]", "from __main__ import e")) print("Tuple with three values, using a local key:") print(timeit("z = d[key]", "from __main__ import d, key")) print("List with three values, using a local key:") print(timeit("z = e[key]", "from __main__ import e, key"))Результаты Python:
Named tuple with a, b, c: 0.124072679784 Named tuple, using index: 0.0447055962367 Class using __slots__, with a, b, c: 0.0409136944224 Dictionary with keys a, b, c: 0.0412045334915 Tuple with three values, using a constant key: 0.0449477955531 List with three values, using a constant key: 0.0331083467148 Tuple with three values, using a local key: 0.0453569025139 List with three values, using a local key: 0.033030056702PyPy Результаты:
Named tuple with a, b, c: 0.00444889068604 Named tuple, using index: 0.00265598297119 Class using __slots__, with a, b, c: 0.00208616256714 Dictionary with keys a, b, c: 0.013897895813 Tuple with three values, using a constant key: 0.00275301933289 List with three values, using a constant key: 0.002760887146 Tuple with three values, using a local key: 0.002769947052 List with three values, using a local key: 0.00278806686401
пара моментов и идей:
1) Вы синхронизируете доступ к одному и тому же индексу много раз подряд. Ваша фактическая программа, вероятно, использует случайный или линейный доступ, который будет иметь другое поведение. В частности, будет больше пропусков кэша процессора. Вы можете получить немного другие результаты, используя вашу фактическую программу.
2) OrderedDictionary пишется как обертка вокруг
dict, следовательно, это будет медленнее, чемdict. Это не решение проблемы.3) Вы пробовали как новый стиль, так и старый? (классы нового стиля наследуются от
object; классы старого стиля не делают)4) Вы пробовали использовать psyco или Без Груза Глотать?
5) ваш внутренний цикл, чтобы изменить данные или просто доступ к нему? Возможно, можно преобразовать данные в наиболее эффективную возможную форму перед входом в цикл, но использовать наиболее удобную форму в другом месте программы.
У меня возникнет соблазн либо (а) изобрести какое-то конкретное кэширование рабочей нагрузки и разгрузить хранение и извлечение моих данных в процесс, подобный memcachedb, чтобы улучшить масштабируемость, а не только производительность, или (б) переписать как расширение C, с собственным хранилищем данных. Возможно, тип упорядоченного словаря.
вы могли бы начать с этого: http://www.xs4all.nl/~anthon / Python / ordereddict/
вы можете сделать ваши классы последовательности, как путем добавления
__iter__и__getitem__методы, чтобы сделать их последовательность как (индексируемые и итерационные.)будет
OrderedDictработы? Есть несколько реализаций доступны, и он включен в Python31collections.