Я только что прочитал "Dive into Python" , что "кортежи быстрее, чем списки".
Tuple неизменен, и список изменен, но я не совсем понимаю, почему кортеж выполняется быстрее.
Кто-нибудь сделал тест производительности?
Я только что прочитал "Dive into Python" , что "кортежи быстрее, чем списки".
Tuple неизменен, и список изменен, но я не совсем понимаю, почему кортеж выполняется быстрее.
Кто-нибудь сделал тест производительности?
Сообщаемое соотношение "скорость построения" выполняется только для кортежей константа (те, чьи позиции выражаются литералами). Соблюдайте осторожность (и повторяйте на своей машине - вам просто нужно ввести команды в окне оболочки/команды!)...:
$ python3.1 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]'
1000000 loops, best of 3: 0.379 usec per loop
$ python3.1 -mtimeit '[1,2,3]'
1000000 loops, best of 3: 0.413 usec per loop
$ python3.1 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)'
10000000 loops, best of 3: 0.174 usec per loop
$ python3.1 -mtimeit '(1,2,3)'
10000000 loops, best of 3: 0.0602 usec per loop
$ python2.6 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]'
1000000 loops, best of 3: 0.352 usec per loop
$ python2.6 -mtimeit '[1,2,3]'
1000000 loops, best of 3: 0.358 usec per loop
$ python2.6 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)'
10000000 loops, best of 3: 0.157 usec per loop
$ python2.6 -mtimeit '(1,2,3)'
10000000 loops, best of 3: 0.0527 usec per loop
Я не делал измерений на 3.0, потому что, конечно, у меня его нет - он полностью устарел, и нет абсолютно никаких оснований держать его вокруг, так как 3.1 превосходит его во всех отношениях (Python 2.7, если вы можете перейти на него, он оценивается как почти на 20% быстрее, чем 2.6 в каждой задаче - и 2.6, как видите, быстрее, чем 3.1 - поэтому, если вы серьезно относитесь к производительности, Python 2.7 действительно единственный релиз, вы должны идти!).
В любом случае ключевым моментом здесь является то, что в каждом выпуске Python построение списка из постоянных литералов примерно одинаковой скорости или немного медленнее, чем построение его из значений, на которые ссылаются переменные; но кортежи ведут себя по-другому: построение кортежа из постоянных литералов обычно в три раза быстрее, чем построение его значений, на которые ссылаются переменные! Вы можете задаться вопросом, как это может быть, правильно? -)
Ответ: кортеж, составленный из постоянных литералов, может быть легко идентифицирован компилятором Python как единый неизменный константный литерал: он по существу строится только один раз, когда компилятор превращает источник в байт-коды и спрятан в "таблица констант" соответствующей функции или модуля. Когда эти байт-коды выполняются, им просто нужно восстановить предварительно построенный постоянный кортеж - hey presto! -)
Эта простая оптимизация не может применяться к спискам, поскольку список является изменяемым объектом, поэтому важно, чтобы, если одно и то же выражение, такое как [1, 2, 3]
, выполняется дважды (в цикле - модуль timeit
делает цикл от вашего имени;-), новый новый объект списка создается заново каждый раз - и эта конструкция (например, построение кортежа, когда компилятор не может тривиально идентифицировать его как константу времени компиляции и неизменяемый объект) делает немного в то время.
Считая, что построение кортежа (когда обе конструкции на самом деле должны ) все еще примерно в два раза быстрее, чем построение списка, и это несоответствие может быть объяснено простой простотой, о чем неоднократно упоминались другие ответы. Но эта простота не учитывает ускорение в шесть раз и более, поскольку вы наблюдаете, только ли вы сравниваете построение списков и кортежей с простыми константными литералами как их элементы! _)
С помощью модуля timeit
вы можете часто решать вопросы, связанные с производительностью:
$ python2.6 -mtimeit -s 'a = tuple(range(10000))' 'for i in a: pass'
10000 loops, best of 3: 189 usec per loop
$ python2.6 -mtimeit -s 'a = list(range(10000))' 'for i in a: pass'
10000 loops, best of 3: 191 usec per loop
Это показывает, что кортеж пренебрежимо быстрее, чем список для итерации. Я получаю аналогичные результаты для индексации, но для построения список кортежей уничтожает:
$ python2.6 -mtimeit '(1, 2, 3, 4)'
10000000 loops, best of 3: 0.0266 usec per loop
$ python2.6 -mtimeit '[1, 2, 3, 4]'
10000000 loops, best of 3: 0.163 usec per loop
Так что, если скорость итерации или индексации являются единственными факторами, то нет никакой разницы, но для построения выигрывают кортежи.
Алекс дал отличный ответ, но я попытаюсь расширить несколько вещей, о которых я думаю, стоит упомянуть. Любые различия в производительности обычно невелики и специфичны для реализации: поэтому не ставьте на них ферму.
В CPython кортежи хранятся в одном блоке памяти, поэтому создание нового кортежа в худшем случае приводит к одному вызову для выделения памяти. Списки распределяются в двух блоках: фиксированный со всей информацией об объекте Python и блоком с переменным размером для данных. Эта часть причины, по которой создание кортежа выполняется быстрее, но, вероятно, также объясняет небольшую разницу в скорости индексирования, так как существует меньше указателей.
В CPython также есть оптимизация для уменьшения распределения памяти: объекты с выделенным списком сохраняются в свободном списке, поэтому их можно повторно использовать, но для распределения непустого списка по-прежнему требуется выделение памяти для данных. Кортежи сохраняются в 20 бесплатных списках для кортежей различного размера, поэтому выделение небольшого кортежа часто не требует каких-либо вызовов выделения памяти.
Оптимизации, подобные этому, полезны на практике, но они также могут рискованно зависеть от результатов "timeit" и, конечно, совершенно разные, если вы переходите к чему-то вроде IronPython, где распределение памяти работает совсем по-другому.
По сути, потому что неизменяемость терминов означает, что интерпретатор может использовать для этого более компактную, более быструю структуру данных по сравнению со списком.
Одной областью, где список заметно быстрее, является построение из генератора, и, в частности, перечисления списков намного быстрее, чем эквивалент ближайшего кортежа, tuple()
с аргументом генератора:
$ python --version
Python 3.6.0rc2
$ python -m timeit 'tuple(x * 2 for x in range(10))'
1000000 loops, best of 3: 1.34 usec per loop
$ python -m timeit 'list(x * 2 for x in range(10))'
1000000 loops, best of 3: 1.41 usec per loop
$ python -m timeit '[x * 2 for x in range(10)]'
1000000 loops, best of 3: 0.864 usec per loop
Обратите внимание, в частности, что tuple(generator)
кажется немного меньшим, чем list(generator)
, но [elem for elem in generator]
намного быстрее, чем оба.
Кортежи идентифицируются компилятором python как одна неизменяемая константа поэтому компилятор создал только одну запись в хеш-таблице и никогда не менял
Списки являются изменяемыми объектами. Так компилятор обновляет запись при обновлении списка поэтому он немного медленнее по сравнению с кортежем