Недавно я сравнил скорости обработки []
и list()
был удивлен, обнаружив, что он []
работает более чем в три раза быстрее, чем list()
. Я побежал же тест с {}
и dict()
и результаты были практически идентичны: []
и {}
оба приняли около 0.128sec / млн циклов, в то время как list()
и dict()
взяли примерно 0.428sec / млн циклов каждый.
Почему это? Есть []
и {}
(и , вероятно , ()
и ''
тоже) сразу перейти назад на копии какой - нибудь пустой складе литерала , а их явно Названные аналоги ( list()
, dict()
, tuple()
, str()
) полностью идти о создании объекта, на самом деле или нет у них есть элементы?
Я понятия не имею, как эти два метода отличаются, но я хотел бы узнать. Я не смог найти ответ в документации или на SO, и поиск пустых скобок оказался более проблематичным, чем я ожидал.
Я получил результаты измерения времени, вызвав timeit.timeit("[]")
и timeit.timeit("list()")
, и timeit.timeit("{}")
и timeit.timeit("dict()")
, чтобы сравнить списки и словари, соответственно. Я использую Python 2.7.9.
Недавно я обнаружил « Почему если True медленнее, чем если 1? », Который сравнивает производительность if True
с if 1
и, кажется, затрагивает похожий сценарий «буквально против глобального»; возможно, это стоит рассмотреть.
источник
()
и''
специальные, так как они не только пусты, они неизменны, и как таковой, это легко выиграть , чтобы сделать их одиночек; они даже не создают новые объекты, просто загружают синглтон для пустогоtuple
/str
. Технически детали реализации, но мне трудно представить, почему они не будут кешировать пустоеtuple
/str
по соображениям производительности. Так ваша интуиция о[]
и{}
передавая обратно фондовую Литерал был неправ, но это не распространяется на()
и''
.{}
звонит быстрееset()
?Ответы:
Потому
[]
и{}
являются буквальным синтаксис . Python может создавать байт-код только для создания списка или словаря объектов:list()
иdict()
являются отдельными объектами. Их имена должны быть разрешены, стек должен быть задействован для передачи аргументов, фрейм должен быть сохранен для последующего извлечения, и должен быть сделан вызов. Это все занимает больше времени.Для пустого регистра это означает, что у вас есть по крайней мере a
LOAD_NAME
(который должен искать в глобальном пространстве имен, а также в__builtin__
модуле ), за которым следует aCALL_FUNCTION
, который должен сохранить текущий кадр:Вы можете рассчитать время поиска имени отдельно
timeit
:Несовпадение времени, вероятно, является столкновением хэша словаря. Вычтите это время из времени для вызова этих объектов и сравните результат со временем для использования литералов:
Таким образом, необходимость вызова объекта занимает дополнительные
1.00 - 0.31 - 0.30 == 0.39
секунды на 10 миллионов вызовов.Вы можете избежать затрат на глобальный поиск, присвоив глобальные имена псевдонимам (используя
timeit
настройку, все, что вы привязываете к имени, является локальным):но вы никогда не сможете преодолеть эту
CALL_FUNCTION
цену.источник
list()
требует глобального поиска и вызова функции, но[]
компилируется в одну инструкцию. Видеть:источник
Потому
list
что это функция для преобразования, скажем, строки в список объектов, в то время[]
как используется для создания списка с нуля. Попробуйте это (может иметь больше смысла для вас):Пока
Дает вам фактический список, содержащий все, что вы положили в него.
источник
[]
быстрееlist()
, а не почему['wham bam']
быстрееlist('wham bam')
.[]
/list()
точно так же, как['wham']
/,list('wham')
потому что они имеют такие же различия в переменных,1000/10
как и100/1
в математике. Теоретически вы можете убрать,wham bam
и тот же факт будет тем же: онlist()
пытается что-то преобразовать, вызывая имя функции, а[]
просто преобразует переменную. Вызовы функций различны, да, это просто логический обзор проблемы, поскольку, например, карта сети компании также логична для решения / проблемы. Голосуйте сколько хотите.Ответы здесь замечательные, по сути и полностью охватывают этот вопрос. Я опущу еще один шаг вниз от байт-кода для интересующихся. Я использую самое последнее репо CPython; старые версии ведут себя аналогично в этом отношении, но могут быть небольшие изменения.
Вот разбивка исполнения для каждого из них,
BUILD_LIST
для[]
иCALL_FUNCTION
дляlist()
.BUILD_LIST
Инструкция:Вы должны просто посмотреть ужас:
Ужасно запутанный, я знаю. Вот как это просто:
PyList_New
(это в основном распределяет память для нового объекта списка),oparg
указывая количество аргументов в стеке. Прямо в точку.if (list==NULL)
.PyList_SET_ITEM
(макроса).Не удивительно, что это быстро! Это специально для создания новых списков, ничего больше :-)
CALL_FUNCTION
Инструкция:Вот первое, что вы видите, когда смотрите на обработку кода
CALL_FUNCTION
:Выглядит довольно безобидно, верно? Нет, к сожалению, нет,
call_function
это не простой человек, который немедленно вызовет функцию, это не может. Вместо этого он захватывает объект из стека, захватывает все аргументы стека и затем переключается в зависимости от типа объекта; это:PyCFunction_Type
? Нет, этоlist
,list
не относится к типуPyCFunction
PyMethodType
? Нет, смотри предыдущий.PyFunctionType
? Нет, смотри предыдущий.Мы вызываем
list
тип, передаваемый аргументcall_function
isPyList_Type
. CPython теперь должен вызывать универсальную функцию для обработки любых именуемых вызываемых объектов_PyObject_FastCallKeywords
, или больше вызовов функций.Эта функция снова делает некоторые проверки для определенных типов функций (что я не могу понять почему), а затем, после создания dict для kwargs, если требуется , продолжает вызывать
_PyObject_FastCallDict
._PyObject_FastCallDict
наконец-то получает нас куда-то! После выполнения еще большего количества проверок он захватываетtp_call
слот изtype
того, чтоtype
мы передали, то есть захватываетtype.tp_call
. Затем он приступает к созданию кортежа из аргументов, переданных с помощью,_PyStack_AsTuple
и, наконец, наконец-то можно сделать вызов !tp_call
, который соответствует,type.__call__
вступает во владение и, наконец, создает список объектов. Он вызывает списки,__new__
которые ему соответствуют,PyType_GenericNew
и выделяет для него память следующим образомPyType_GenericAlloc
: На самом деле это та часть, в которой онPyList_New
, наконец, догоняет . Все предыдущее необходимо для обработки объектов в общем виде.В конце концов,
type_call
вызываетlist.__init__
и инициализирует список с любыми доступными аргументами, затем мы возвращаемся тем же путем, которым пришли. :-)Наконец, вспомните
LOAD_NAME
, что это еще один парень, который вносит свой вклад здесь.Легко видеть, что при работе с нашим вводом Python, как правило, должен перепрыгивать через обручи, чтобы действительно найти подходящую
C
функцию для выполнения работы. Он не имеет права немедленно вызывать его, потому что он динамический, кто-то может замаскироватьlist
( и это делают многие люди ), и нужно выбрать другой путь.Вот где
list()
многое теряется: исследующий Python должен выяснить, какого черта он должен делать.С другой стороны, буквальный синтаксис означает ровно одну вещь; это не может быть изменено и всегда ведет себя заранее определенным образом.
Сноска. Все названия функций могут быть изменены с одного выпуска на другой. Точка все еще стоит и, скорее всего, будет стоять в любых будущих версиях, это динамический поиск, который замедляет вещи.
источник
Самая большая причина в том, что Python обрабатывает так
list()
же, как пользовательскую функцию, что означает, что вы можете перехватить ее, добавив псевдоним в другоеlist
и сделать что-то другое (например, использовать свой собственный подклассовый список или, возможно, деку).Он сразу создает новый экземпляр встроенного списка с помощью
[]
.Мое объяснение стремится дать вам интуицию для этого.
объяснение
[]
широко известен как буквальный синтаксис.В грамматике это называется «отображением списка». Из документов :
Короче говоря, это означает, что создается встроенный объект типа
list
.Обойти это невозможно - это означает, что Python может сделать это так быстро, как может.
С другой стороны,
list()
может быть перехвачен от создания встроенногоlist
с помощью встроенного конструктора списка.Например, скажем, мы хотим, чтобы наши списки создавались с шумом:
Затем мы можем перехватить имя
list
в глобальной области видимости на уровне модуля, а затем, когда мы создаемlist
, мы фактически создаем наш подтипированный список:Точно так же мы могли бы удалить его из глобального пространства имен
и поместите его во встроенное пространство имен:
И сейчас:
И обратите внимание, что отображение списка создает список безоговорочно:
Мы, вероятно, делаем это только временно, поэтому давайте отменим наши изменения - сначала удалим новый
List
объект из встроенных:О нет, мы потеряли след оригинала.
Не волнуйтесь, мы все еще можем получить
list
- это тип литерала списка:Так...
Как мы уже видели - мы можем перезаписать
list
- но мы не можем перехватить создание литерального типа. Когда мы используем,list
мы должны сделать поиск, чтобы увидеть, есть ли что-нибудь.Затем мы должны позвонить тому, что мы вызываем. Из грамматики:
Мы видим, что он делает то же самое для любого имени, а не только для списка:
Поскольку
[]
на уровне байт-кода Python нет вызова функции:Это просто идет прямо к построению списка без каких-либо поисков или вызовов на уровне байт-кода.
Вывод
Мы продемонстрировали, что
list
его можно перехватить с помощью пользовательского кода, используя правила области видимости, которыйlist()
ищет вызываемый объект и затем вызывает его.Принимая во внимание,
[]
что это отображение списка или литерала, что позволяет избежать поиска имени и вызова функции.источник
list
и компилятор python не может быть уверен, что он действительно вернет пустой список.