Python против BC в оценке 6 ^ 6 ^ 6

29

Я оцениваю выражение, 6^6^6используя pythonи bcотдельно.

Содержимое файла python есть print 6**6**6. Когда я выполняю time python test.py, я получаю вывод как

real        0m0.067s
user        0m0.050s
sys         0m0.011s

И затем я выполнил команду, time echo 6^6^6 | bcкоторая дала мне следующий вывод

real        0m0.205s
user        0m0.197s
sys         0m0.005s

Из этих результатов ясно, что системное время, используемое python и bc, составляло 11 мс и 5 мс соответственно. Команда bc превзошла python на уровне системного времени, но когда дело доходит до пользователя и реального времени, python был почти в 4 раза быстрее, чем bc . Что могло быть там. Я не дал никакого приоритета процессам как таковым. Я пытаюсь понять эту ситуацию.

ganessh
источник
То есть вы имеете в виду, что компонент sys дает только время, необходимое для загрузки, а время выполнения будет указано в пользовательском компоненте вывода?
ganessh
Я действительно не уверен, поэтому я оставил комментарий. Это всего лишь предположение.
Тердон
7
echo | bcвключает в себя запуск подоболочки из-за конвейера - вот откуда, вероятно, пришло ваше дополнительное пользовательское время. Чтобы сделать это справедливым тестом, скрипт python должен читать из stdin, чтобы вы могли time echo 6**6**6 | whatever.py.
Златовласка
1
Я бы предпочел поместить командную строку be в скрипт и рассчитать время его выполнения. Или используйте echo 6^6^6 | time bc.
Даниэль Куллманн
1
Примечание: в Python 6**6**6выражение фактически вычисляется во время компиляции . Однако, поскольку вы запускаете файл напрямую, а не импортируете его из модуля, это не должно иметь значения. Чтобы увидеть разницу 10**12345678в a.pyфайле и попытаться импортировать его из интерактивного интерпретатора. Затем закройте интерпретатор, перезапустите его и импортируйте aснова. В первый раз это должно занять заметное количество времени (потому что python компилирует модуль), в то время как во второй раз он загружает тот .pyc, который должен быть мгновенным,
Bakuriu

Ответы:

25

Python импортирует большое количество файлов при запуске:

% python -c 'import sys; print len(sys.modules)'
39

Каждый из них требует еще большего количества попыток открыть файл Python, потому что существует много способов определить модуль:

% python -vv -c 'pass'
# installing zipimport hook
import zipimport # builtin
# installed zipimport hook
# trying site.so
# trying sitemodule.so
# trying site.py
# trying site.pyc
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sitemodule.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.py
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc matches /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.py
import site # precompiled from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc
# trying os.so
# trying osmodule.so
# trying os.py
# trying os.pyc
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/osmodule.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc matches /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py
import os # precompiled from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc
    ...

Каждая «попытка», за исключением встроенных, требует системных вызовов / системных вызовов, и каждый «импорт» вызывает около 8 «попыток» сообщений. (Существовали способы уменьшить это с помощью zipimport, и для каждого пути в PYTHONPATH может потребоваться еще один вызов.)

Это означает, что на моем компьютере Python запускается почти 200 системных вызовов, и «time» назначает это «sys», а не «user», потому что пользовательская программа ожидает выполнения каких-либо действий в системе.

Для сравнения, и, как сказал Тердон, «bc» не имеет такой высокой стоимости стартапа. Глядя на вывод dtruss (у меня Mac; «strace» для ОС на базе Linux), я вижу, что bc не выполняет никаких системных вызовов open () или stat (), за исключением загрузки нескольких общих библиотеки - это начало, что, конечно, делает и Python. Кроме того, у Python есть больше файлов для чтения, прежде чем он будет готов что-либо обрабатывать.

Ожидание диска идет медленно.

Вы можете понять стоимость запуска Python, выполнив:

time python -c pass

На моем аппарате это 0,032 с, в то время как «печать 6 ** 6 ** 6» составляет 0,072 с, поэтому начальная стоимость составляет 1/2 от общего времени, а расчет + преобразование в десятичное число составляет другую половину. В то время как:

time echo 1 | bc

занимает 0,005 с, а "6 ^ 6 ^ 6" - 0,184 с, поэтому возведение в bc более чем в 4 раза медленнее, чем в Python, даже несмотря на то, что начало в 7 раз быстрее.

Эндрю Далке
источник
4
Вы как бы похоронили лидерство там. Возможно, вы захотите переместить конечный бит вверх.
Riking
Просто из интереса на моей машине: время питон -c «пройти» 0m0.025s, время питон -c «напечатать 6 6 6» 0m0.087s но время питона -c «х = 6 6 6» 0m0.028s Так большинство из время выводит большое число.
Стив Барнс
Да, преобразование в основание 10 занимает квадратичное время по количеству цифр. В крайнем случае попробуйте распечатать одно из больших простых чисел Мерсенна. Это очень быстро, чтобы рассчитать, но занимает много времени, чтобы напечатать в базе 10.
Эндрю Далке
11

Я нашел хороший ответ на SO, объясняющий различные поля:

  • Реальное время настенные часы - время от начала до конца разговора. Это все прошедшее время, включая временные интервалы, используемые другими процессами, и время, которое процесс блокирует (например, если он ожидает завершения ввода-вывода).

  • Пользователь - это количество процессорного времени, потраченного на код пользовательского режима (вне ядра) в процессе. Это только фактическое время процессора, используемое при выполнении процесса. Другие процессы и время, затраченное процессом заблокировано, не учитываются в этом показателе.

  • Sys - это количество процессорного времени, проведенного в ядре в процессе. Это означает выполнение процессорного времени, затраченного на системные вызовы в ядре, в отличие от библиотечного кода, который все еще выполняется в пользовательском пространстве. Как и «пользователь», это только время процессора, используемое процессом. Ниже приведено краткое описание режима ядра (также известного как режим 'supervisor') и механизма системных вызовов.

Итак, в вашем конкретном примере версия Python быстрее с точки зрения фактического времени, которое требуется для завершения. Тем не менее, подход python тратит больше времени в пространстве ядра, вызывая функции ядра. Команда bcпрактически не тратит время в пространстве ядра и все свое время проводит в пространстве пользователя, предположительно выполняя внутренний bcкод.

Для вас это не имеет значения, единственная информация, которая вас действительно волнует, - realэто фактическое время, прошедшее между запуском команды и ее выводом.

Вы также должны знать, что эти крошечные различия нестабильны, они также будут зависеть от загрузки вашей системы и будут меняться при каждом запуске команды:

$ for i in {1..10}; do ( time python test.py > /dev/null ) 2>&1; done | grep user
user    0m0.056s
user    0m0.052s
user    0m0.052s
user    0m0.052s
user    0m0.060s
user    0m0.052s
user    0m0.052s
user    0m0.056s
user    0m0.048s
user    0m0.056s

$ for i in {1..10}; do ( time echo 6^6^6 | bc > /dev/null ) 2>&1; done | grep user
user    0m0.188s
user    0m0.188s
user    0m0.176s
user    0m0.176s
user    0m0.172s
user    0m0.176s
user    0m0.180s
user    0m0.172s
user    0m0.172s
user    0m0.172s
Тердон
источник
10

Я объясню это с другой точки зрения.

Честно говоря, bcимеет преимущество, поскольку ему не нужно ничего читать с диска, а нужны только его двоичные объекты / двоичные файлы, в то время как python должен импортировать серию модулей + чтение файла. Таким образом, ваш тест может быть смещен в сторону bc. Чтобы действительно проверить это, вы должны использовать bc -q fileгде fileсодержит:

6^6^6
quit

Изменение только того, что изменило время использования echo:

bc  0.33s user 0.00s system 80% cpu 0.414 total

Чтобы использовать файл:

bc -q some  0.33s user 0.00s system 86% cpu 0.385 total

(вам придется использовать метод Тердона, чтобы заметить большие различия, но, по крайней мере, мы знаем, что они есть)

Теперь, с точки зрения python, python должен читать с диска, компилировать и выполнять каждый раз файл, а также загружать модули как точки Эндрю , что замедляет время выполнения. Если вы скомпилируете байт-код скрипта Python, вы заметите, что на выполнение кода уходит на 50% меньше времени:

python some.py > /dev/null  0.25s user 0.01s system 63% cpu 0.413 total

компилируется:

./some.pyc  0.22s user 0.00s system 77% cpu 0.282 total

Как видите, существует несколько факторов, которые могут повлиять на время выполнения между различными инструментами.

Braiam
источник
3

Я имел преимущество читать другие ответы. Во-первых, такие люди, как я, должны знать причину, по которой мы имеем дело с таким огромным целым числом, заключается в том, что оба Pythonи bcвыполняют расширение правой степени ассоциативного возведения в степень, что означает, что это не 6^36мы оцениваем, а 6^46656значительно больше. 1

Используя варианты следующих команд, мы можем извлечь среднее значение для конкретного элемента вывода как timeзарезервированного слова, так и команды:

for i in {1..1000}; do (time echo 6^6^6 | bc > /dev/null) 2>&1; done | grep 'rea' | sed -e s/.*m// | awk '{sum += $1} END {print sum / NR}'

for i in {1..1000}; do (/usr/bin/time -v sh -c 'echo 6^6^6 | bc > /dev/null') 2>&1; done | grep 'Use' | sed -e s/.*:// | awk '{sum += $1} END {print sum / NR}'

Можно пойти другим путем и полностью удалить файл из сравнения. Кроме того, мы можем сравнить синхронизацию bc с чем-то вроде dcкоманды, поскольку исторически первый является «процессором переднего плана» для второго. Следующие команды были рассчитаны по времени:

echo 6^6^6 | bc
echo 6 6 6 ^ ^ p | dc
echo print 6**6**6 | python2.7

Обратите внимание, что dcкоманда является левой ассоциативной для возведения в степень. 2

У нас есть некоторые результаты с time(bash) для 1000 итераций (в секундах):

0.229678 real bc
0.228348 user bc
0.000569 sys bc
0.23306  real dc
0.231786 user dc
0.000395 sys dc
0.07 real python
0.065907 user python
0.003141 sys python

bcи dcпредложить сопоставимую производительность в этом контексте.

Менее точные 3 результата из команды /usr/bin/timeGNU time(точность масштаба здесь недопустима, но результаты аналогичны):

0.2224 user bc
0 sys bc
0.23 Elapsed bc
0.22998 user dc
0 sys dc
0.23 Elapsed dc
0.06008 user python
0 sys python
0.07 Elapsed python

Преимущество /usr/bin/timeзаключается в том, что он предлагает -vопцию, которая дает гораздо больше информации, которая может быть полезна в конечном итоге.

Можно также оценить это внутренне, так сказать с timeitмодулем Python:

python2.7 -m timeit -n 1000 -r 1 'print 6**6**6' | grep 'loops'
1000 loops, best of 1: 55.4 msec per loop

Это немного быстрее, чем мы видели раньше. Давайте попробуем сам переводчик:

>>> import timeit
>>> import sys
>>> import os
>>> T = timeit.Timer("print 6**6**6")
>>> n = int(1000)
>>> f = open(os.devnull, 'w')
>>> sys.stdout = f
>>> t = t.timeit(n)
>>> sys.stdout = sys.__stdout__
>>> print t/n
0.0553743481636

Это самое быстрое, что я видел.


Если мы оценим как меньшее возведение в степень 6^6, то команда time дает удивительные результаты - используя те же самые forкоманды цикла, которые мы использовали, мы теперь имеем:

0.001001 bc real
0.000304 user
0.000554 sys
0.014    python real i.e. 10x more than bc??
0.010432 user
0.002606 sys

Так что с меньшим целым числом bcвдруг намного быстрее ?? От перезагрузки системы до второго запуска не имеет значения. В то же время, если мы используем timeitPython, мы получаем:

python2.7 -m timeit -n 100000 -r 1 'print 6**6' | grep loops  
100000 loops, best of 1: 0.468 usec per loop

Это микросекунды , а не миллисекунды, так что это не совпадает с гораздо более медленными результатами при использовании forцикла. Возможно, для дальнейшего тестирования требуются другие инструменты, и, как другие объясняли, здесь есть нечто большее, чем кажется на первый взгляд. Кажется, Python был быстрее в сценарии вопроса, но не ясно, можно ли сделать выводы за этим ...


1. Излишне говорить , что это выходит за рамки чего - то вроде эха арифметического расширения , т.е. echo $((6**6**6))- bashтакже случается правоассоциативной для этого есть 6^6^6 = 6^(6^6).

2. Сравните с этим: 6 6 ^ 6 ^ p.

3. Возможно, команда GNU time предоставляет больше информации при запуске в BSD UNIX (документ информации о времени GNU): большая часть информации, отображаемой в «time», получена из системного вызова «wait3». Числа такие же хорошие, как и те, что возвращены функцией wait3. Многие системы не измеряют все ресурсы, о которых может сообщать «время»; эти ресурсы указаны как ноль. Системы, которые измеряют большинство или все ресурсы, основаны на 4.2 или 4.3BSD. Более поздние выпуски BSD используют другой код управления памятью, который измеряет меньше ресурсов. - В системах, в которых нет вызова «wait3», который возвращает информацию о состоянии, вместо этого используется системный вызов «times». Он предоставляет гораздо меньше информации, чем «wait3», поэтому в «системном времени» этих систем большинство ресурсов указывается как ноль.

Сообщество
источник