Эффективность индексации списка (python 2 против python 3)

Отвечая на другой вопрос, я предложил использовать timeit для проверки разницы между индексированием списка с положительными целыми числами и отрицательными целыми числами. Вот код:

import timeit
t=timeit.timeit('mylist[99]',setup='mylist=list(range(100))',number=10000000)
print (t)
t=timeit.timeit('mylist[-1]',setup='mylist=list(range(100))',number=10000000)
print (t)

Я запустил этот код с Python 2.6:

$ python2.6 test.py
0.587687015533
0.586369991302

Затем я запустил его с помощью Python 3.2:

$ python3.2 test.py
0.9212150573730469
1.0225799083709717

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

Операционная система: OS-X (10.5.8) -- Intel Core2Duo

Мне кажется, это довольно существенная разница (разница более чем в 1,5 раза). Кто-нибудь знает, почему python3 намного медленнее, особенно для такой распространенной операции?

ИЗМЕНИТЬ

Я запустил тот же код на своем рабочем столе Ubuntu Linux (Intel i7) и добился сравнимых результатов с python2.6 и python 3.2. Похоже, что это проблема, зависящая от операционной системы (или процессора) (другие пользователи наблюдают такое же поведение на компьютерах с Linux — см. комментарии).

ИЗМЕНИТЬ 2

Баннер запуска был запрошен в одном из ответов, так что вот:

Python 2.6.4 (r264:75821M, Oct 27 2009, 19:48:32) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin

а также:

Python 3.2 (r32:88452, Feb 20 2011, 10:19:59) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin

ОБНОВЛЕНИЕ

Я только что установил свежие версии python2.7.3 и python3.2.3 с сайта http://www.python.org/download/

В обоих случаях я взял

«Python xx3 Mac OS X 32-разрядная программа установки i386/PPC (для Mac OS X 10.3–10.6 [2])»

так как я на OS X 10.5. Вот новые тайминги (которые достаточно стабильны после нескольких испытаний):

питон 2.7

$python2.7 test.py
0.577006101608
0.590042829514

питон 3.2.3

$python3.2 test.py
0.8882801532745361
1.034242868423462

person mgilson    schedule 09.07.2012    source источник
comment
попробуйте использовать xrange() в python 2.x   -  person Ashwini Chaudhary    schedule 09.07.2012
comment
В 2.7 на моем Mac я получаю 0,535/0,528 с диапазоном, 0,527/0,504 с xrange, поэтому я не думаю, что это разница.   -  person abarnert    schedule 09.07.2012
comment
@AshwiniChaudhary - все это в setup, которое не рассчитано по времени. xrange против range не должно иметь к этому никакого отношения.   -  person mgilson    schedule 09.07.2012
comment
В 3.2 на моем Mac я получаю 0,381/0,439, что на самом деле быстрее, чем 2,7. Но я также получаю разницу в 75% между последующими пробежками, что означает, что вы выполняете недостаточное количество повторений.   -  person abarnert    schedule 09.07.2012
comment
Попробуйте запустить один и тот же тест несколько раз подряд, ища самое быстрое время. Одна секунда выполнения достаточно коротка, поэтому фоновые операции на вашем компьютере могут нарушить синхронизацию.   -  person Russell Borogove    schedule 09.07.2012
comment
@abarnert: к моменту вызова mylist[99]/mylist[-1] mylist будет просто списком чисел, [0, 1, ..., 99]. И range, и xrange должны производить одинаковые входные данные для вызова stmt, что на самом деле рассчитано по времени.   -  person DSM    schedule 09.07.2012
comment
@abarnert - интересный момент. Не думал об этом. Я не понимаю вариант, который вы описываете. Я запускал его здесь 10-15 раз без каких-либо значительных изменений в результатах. Интересно, что я только что запустил его на своем рабочем столе Linux, и проблема исчезла.   -  person mgilson    schedule 09.07.2012
comment
Для справки: есть этот ответ, в котором обсуждается отсутствие специального регистра небольших целых чисел в python3.0, возможно, он все еще отсутствует в питоне3.2   -  person Otto Allmendinger    schedule 09.07.2012
comment
@abarnert: это выглядит глупо, но необходимо обеспечить совместимость с Python 2/Python 3..   -  person DSM    schedule 09.07.2012
comment
@OttoAllmendinger - это согласуется с некоторыми ответами (гораздо большая разница в OS-X и Linux). Возможно, ты прав.   -  person mgilson    schedule 09.07.2012
comment
@mgilson У меня примерно и постоянно те же результаты, что и у вас на моей машине с Linux.   -  person Otto Allmendinger    schedule 09.07.2012
comment
@OttoAllmendinger -- Какой у вас процессор? Я тестирую i7 (linux) и Core2Duo (OS-X)   -  person mgilson    schedule 09.07.2012
comment
Вы так и не сказали нам, откуда взяли Python 3.2. Вы откуда-то устанавливали бинарный файл, собирали его с помощью MacPorts или Homebrew, сами собирали исходники или как? (И если ваша версия 2.6 не является стандартной сборкой Apple, к ней применим тот же вопрос.)   -  person abarnert    schedule 09.07.2012
comment
@abarnert -- Честно говоря, я не помню, где я их взял. Это было, наверное, пару лет назад. Вероятно, я скачал бинарники с сайта Python. (Я собрал его из исходного кода на нескольких машинах, но, судя по информации компилятора, крайне маловероятно, что я сам собрал какую-либо из этих версий)   -  person mgilson    schedule 09.07.2012
comment
@mgilson двухъядерный процессор E5200   -  person Otto Allmendinger    schedule 09.07.2012


Ответы (3)


Похоже, это артефакт некоторых сборок Python 3.2. Лучшая гипотеза на данный момент заключается в том, что все 32-битные сборки Intel имеют замедление, а 64-битные - нет. Читайте дальше для получения дополнительной информации.

Вы не провели достаточно тестов, чтобы что-то определить. Повторив ваш тест несколько раз, я получил значения от 0,31 до 0,54 для одного и того же теста, что является огромным разбросом.

Итак, я провел ваш тест с 10x числом и repeat=10, используя несколько разных установок Python2 и Python3. Отбрасывая верхний и нижний результаты, усредняя остальные 8 и разделив на 10 (чтобы получить число, эквивалентное вашим тестам), вот что я увидел:

 1. 0.52/0.53 Lion 2.6
 2. 0.49/0.50 Lion 2.7
 3. 0.48/0.48 MacPorts 2.7
 4. 0.39/0.49 MacPorts 3.2
 5. 0.39/0.48 HomeBrew 3.2

Итак, похоже, что 3.2 на самом деле немного быстрее с [99] и примерно такой же скоростью с [-1].

Однако на машине 10.5 я получил такие результаты:

 1. 0.98/1.02 MacPorts 2.6
 2. 1.47/1.59 MacPorts 3.2

Вернувшись на исходную (Lion) машину, я запустил 32-битный режим и получил следующее:

 1. 0.50/0.48 Homebrew 2.7
 2. 0.75/0.82 Homebrew 3.2

Итак, похоже, что важна именно 32-битность, а не Leopard vs. Lion, gcc 4.0 vs. gcc 4.2 или clang, аппаратные различия и т. д. Было бы полезно протестировать 64-битные сборки под Leopard, с разными компиляторами и т. д. ., но, к сожалению, моя коробка Leopard — это Intel Mini первого поколения (с 32-разрядным процессором Core Solo), поэтому я не могу провести этот тест.

В качестве еще одного косвенного доказательства я провел множество других быстрых тестов на Lion box, и оказалось, что 32-разрядная версия 3.2 примерно на 50% медленнее, чем 2.x, а 64-разрядная версия 3.2, возможно, немного быстрее, чем 2. Икс. Но если мы действительно хотим подтвердить это, кто-то должен выбрать и запустить настоящий набор тестов.

В любом случае, на данный момент я думаю, что при оптимизации ветки 3.x никто не прилагал особых усилий для сборки 32-битных i386 Mac. Что на самом деле является разумным выбором для них.

Или, наоборот, они даже не приложили особых усилий к 32-битному периоду i386. Эта возможность может объяснить, почему ОП видел, что 2.x и 3.2 давали одинаковые результаты на Linux-боксе, в то время как Отто Алмендингер видел, что 3.2 так же медленнее, чем 2.6 на Linux-боксе. Но поскольку ни один из них не упомянул, используют ли они 32-битный или 64-битный Linux, трудно понять, имеет ли это значение.

Есть еще много других возможностей, которые мы не исключили, но этот кажется лучшим.

person abarnert    schedule 09.07.2012
comment
Оба должны быть 32-битными. OS-X 10.5.8 — 32-битная ОС, поэтому я не смог бы запустить 64-битную версию, если бы захотел. - person mgilson; 09.07.2012
comment
Leopard может запускать 64-битные приложения. В отличие от Snow Leopard, по крайней мере, на Intel, он имеет только 32-разрядное ядро, и большинство встроенных приложений являются 32-разрядными, а стандартный gcc по умолчанию имеет 32-разрядную версию, но он по-прежнему может собирать и запускать 64 -битные приложения. - person abarnert; 09.07.2012
comment
Во-первых, это отличный ответ (+1), хотя я не на 100% уверен, что причина в этом. Я разместил баннер запуска и процессор для двух своих машин. Насколько я могу судить, похоже, что я получил обе установки Python из одного и того же места, скомпилированные одним и тем же компилятором. Если кто-то еще сможет воспроизвести ваш тест с помощью OS-X 10.5.8, я, вероятно, буду убежден. (если ничего другого не появится, я, вероятно, все равно приму этот ответ - это отличный ответ). - person mgilson; 09.07.2012
comment
Я устанавливаю текущие MacPorts 2.6 и 3.2 на машину 10.5, что займет вечность… но через час после вечности я запущу там тест и посмотрю, что произойдет. Если он не воспроизводит ваши результаты, это должно убедительно доказать, что в вашей сборке 3.2 есть что-то странное, а не в 3.2 вообще. Если это так, есть место для дальнейшего расследования. - person abarnert; 09.07.2012
comment
Я только что переустановил двоичные файлы с python.org/download. В обоих случаях (теперь python 2.7.3 vs. 3.2.3), я взял 32-битную версию i386/PPC. Проблема осталась, хотя тайминги теперь чуть ближе. Я обновлю свой пост. - person mgilson; 09.07.2012
comment
Я тоже настроен скептически. Я использую стандартные версии Python 2.7.3 и 3.2.3 для Arch Linux и получаю очень похожие результаты с @mgilson. - person Otto Allmendinger; 09.07.2012
comment
@OttoAllmendinger: Но Мгилсон не получил аналогичные результаты на своей Linux-системе; он видел 2.6 и 3.2 имели сопоставимые скорости. Так что ваши результаты противоречат его, а не подтверждают. (Если вы используете 32-битный Python, а он использует 64-битный, это многое нам скажет.) - person abarnert; 10.07.2012
comment
Это круто! Я использую 64-битную версию на своем Linux-боксе. Я думаю, что я считаю, что ваш диагноз (@OttoAllmendinger может забить гвоздь в гроб, если он использует 32-битную реализацию stackoverflow.com/q/ 1405913/748858). Он по-прежнему не отвечает, почему он медленнее, хотя, кроме махания рукой, они не удосужились оптимизировать для 32-битной версии. - person mgilson; 10.07.2012
comment
хорошо, верно; если вы хотите знать, почему он работает медленнее, вам придется просмотреть исходный код и/или профилировать интерпретатор и/или просмотреть архивы списка python-dev, что требует больших усилий. чем я готов вложить в данный момент… - person abarnert; 10.07.2012

вот код, который иллюстрирует хотя бы часть ответа:

$ python
Python 2.7.3 (default, Apr 20 2012, 22:44:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> t=timeit.timeit('mylist[99]',setup='mylist=list(range(100))',number=50000000)
>>> print (t)
2.55517697334
>>> t=timeit.timeit('mylist[99L]',setup='mylist=list(range(100))',number=50000000)
>>> print (t)
3.89904499054

$ python3
Python 3.2.3 (default, May  3 2012, 15:54:42) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> t=timeit.timeit('mylist[99]',setup='mylist=list(range(100))',number=50000000)
>>> print (t)
3.9906489849090576

python3 не имеет старого типа int.

person subdir    schedule 09.07.2012
comment
Очень интересно. Использование длинных целых чисел дает результаты синхронизации, очень похожие на версию python 3.x. - person mgilson; 10.07.2012
comment
Хороший. Быстрый поиск показал сообщение об ускорении long в Python 3.1, потому что он намного медленнее, чем 2.x int, но, к сожалению, сам сайт недоступен. Более тщательный поиск, вероятно, выявит именно то, что они сделали, чтобы исправить ситуацию, и, возможно, почему это не помогло для 32-битной версии. Но я могу представить множество возможностей. Например, с 64-битными словами трюки, в которых маленькие длинные вписываются в одно слово, не требуют чтения символов за раз… - person abarnert; 10.07.2012

Python 3 range() — это Python 2 xrange(). Если вы хотите имитировать Python 2 range() в коде Python 3, вы должны использовать list(range(num). Чем больше num, тем больше будет разница с исходным кодом.

Индексация не должна зависеть от того, что хранится внутри списка, поскольку список хранит только ссылки на целевые объекты. Ссылки нетипизированы и все одного вида. Таким образом, тип списка является однородной структурой данных — технически. Индексация означает преобразование значения индекса в начальный адрес + смещение. Вычисление смещения очень эффективно с не более чем одним вычитанием. Это очень дешевая дополнительная операция по сравнению с другими операциями.

person pepr    schedule 09.07.2012
comment
вздох -- мы это уже проходили. часть setup по времени не рассчитана. В моей настройке я использую list(range(num)), поэтому вещи, которые синхронизируются (в обоих случаях), представляют собой список чисел, а не объект диапазона. И я тоже понимаю материал во втором абзаце. Вопрос в том, почему упомянутые операции с использованием python3.x выполняются намного медленнее по сравнению с python 2.x? - person mgilson; 10.07.2012
comment
@мгилсон; +1, ты прав. В следующий раз надо читать лучше ;) - person pepr; 10.07.2012