Каков самый быстрый (для доступа) структурный объект в 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.033030056702
PyPy Результаты:
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
.