Python vs BC bei der Bewertung von 6 ^ 6 ^ 6

29

Ich bewerte den Ausdruck 6^6^6mit pythonund bcgetrennt.

Der Inhalt der Python-Datei ist print 6**6**6. Wenn ich ausführe time python test.py, erhalte ich die Ausgabe als

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

Und dann habe ich den Befehl ausgeführt, time echo 6^6^6 | bcder mir die folgende Ausgabe gab

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

Aus diesen Ergebnissen ist klar, dass die von Python und BC benötigte Sys-Zeit 11 ms bzw. 5 ms betrug. Der Befehl bc übertraf Python auf Sys-Zeit-Ebene, war jedoch in Bezug auf Benutzer- und Echtzeit-Python fast viermal schneller als bc . Was könnte dahin gegangen sein. Ich habe den Prozessen als solchen keine Priorität eingeräumt. Ich versuche diese Situation zu verstehen.

Ganessh
quelle
Meinen Sie damit, dass die sys-Komponente nur die Zeit angibt, die zum Laden benötigt wird, und die Laufzeit in der Benutzerkomponente der Ausgabe angegeben wird?
Ganessh
Ich bin mir wirklich nicht sicher, deshalb habe ich einen Kommentar gepostet. Es ist nur eine Vermutung.
terdon
7
echo | bcDazu gehört das Starten einer Subshell aufgrund der Pipe. Daher stammt wahrscheinlich ein Teil Ihrer zusätzlichen Benutzerzeit. Um dies zu einem gerechten Test zu machen, sollte das Python-Skript von stdin lesen, damit Sie es können time echo 6**6**6 | whatever.py.
Goldlöckchen
1
Ich möchte lieber die be-Befehlszeile in ein Skript einfügen und die Ausführung zeitlich festlegen. Oder benutze echo 6^6^6 | time bc.
Daniel Kullmann
1
Randnotiz: In Python wird der 6**6**6Ausdruck tatsächlich zur Kompilierungszeit berechnet . Da Sie die Datei jedoch direkt starten, anstatt sie aus einem Modul zu importieren, sollte dies keine Rolle spielen. Um den Unterschied zu sehen , fügen Sie ihn 10**12345678in eine a.pyDatei ein und versuchen Sie, ihn aus dem interaktiven Interpreter zu importieren. Schließen Sie dann den Interpreter, starten Sie ihn neu und importieren Sie ihn aerneut. Das erste Mal sollte es eine merkliche Zeit dauern (weil Python das Modul kompiliert), während das zweite Mal das .pyc, was augenblicklich sein sollte,
Bakuriu 21.02.14

Antworten:

25

Python importiert beim Start eine große Anzahl von Dateien:

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

Jedes dieser Verfahren erfordert eine noch größere Anzahl von Versuchen, eine Python-Datei zu öffnen, da es viele Möglichkeiten gibt, ein Modul zu definieren:

% 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
    ...

Jeder "Versuch", mit Ausnahme derjenigen, die eingebaut sind, erfordert einen Aufruf auf Betriebssystemebene / System, und jeder "Import" scheint ungefähr 8 "Versuch" -Nachrichten auszulösen. (Es gab Möglichkeiten, dies mit zipimport zu reduzieren, und jeder Pfad in Ihrem PYTHONPATH erfordert möglicherweise einen weiteren Aufruf.)

Dies bedeutet, dass es fast 200 stat-Systemaufrufe gibt, bevor Python auf meinem Computer gestartet wird, und "time" weist dies eher "sys" als "user" zu, da das Benutzerprogramm darauf wartet, dass das System die Dinge ausführt.

Im Vergleich dazu und wie Terdon sagte, hat "bc" nicht so hohe Startkosten. Wenn ich mir die Ausgabe von dtruss ansehe (ich habe einen Mac; "strace" für ein Linux-basiertes Betriebssystem), sehe ich, dass bc keine eigenen open () - oder stat () -Systemaufrufe ausführt, außer ein paar gemeinsam genutzte Bibliotheken sind der Anfang, was natürlich auch Python tut. Darüber hinaus muss Python mehr Dateien lesen, bevor es zur Verarbeitung bereit ist.

Das Warten auf die Festplatte ist langsam.

Sie können ein Gefühl für die Startkosten von Python bekommen, indem Sie Folgendes tun:

time python -c pass

Auf meinem Computer sind es 0,032 s, während "print 6 ** 6 ** 6" 0,072 s ist, sodass die Startkosten 1/2 der Gesamtzeit betragen und die Berechnung + Konvertierung in Dezimalzahl die andere Hälfte ist. Während:

time echo 1 | bc

dauert 0.005s, und "6 ^ 6 ^ 6" dauert 0.184s, so dass die Potenzierung von bc über 4x langsamer ist als die von Python, obwohl es 7x schneller ist, um anzufangen.

Andrew Dalke
quelle
4
Sie haben die Führung dort irgendwie vergraben. Möglicherweise möchten Sie das Endbit nach oben verschieben.
Riking
Nur aus Interesse an meiner Maschine: Zeit python -c 'passiert' 0m0.025s, Zeit python -c 'druckt 6 6 6' 0m0.087s aber Zeit python -c 'x = 6 6 6' 0m0.028s So die meisten von Die Zeit gibt die große Zahl aus.
Steve Barnes
Ja, die Konvertierung zur Basis 10 dauert in der Anzahl der Stellen quadratisch. Versuchen Sie im Extremfall, eine der größeren Mersenne-Primzahlen zu drucken. Es ist sehr schnell zu berechnen, aber der Druck in Basis 10 dauert sehr lange.
Andrew Dalke
11

Ich fand eine schöne Antwort auf SO die verschiedene Felder zu erklären:

  • Real ist die Uhrzeit der Wanduhr - Zeit vom Beginn bis zum Ende des Anrufs. Dies ist die gesamte verstrichene Zeit einschließlich der Zeitscheiben, die von anderen Prozessen verwendet werden, und der Zeit, die der Prozess blockiert (z. B. wenn er auf den Abschluss der E / A wartet).

  • Benutzer ist die Menge an CPU-Zeit, die im Benutzermoduscode (außerhalb des Kernels) innerhalb des Prozesses verbracht wird. Dies ist nur die tatsächliche CPU-Zeit, die zur Ausführung des Prozesses benötigt wird. Andere Prozesse und die Zeit, in der der Prozess blockiert ist, zählen nicht zu dieser Zahl.

  • Sys ist die Menge an CPU-Zeit, die im Kernel innerhalb des Prozesses verbracht wird. Dies bedeutet, dass die für Systemaufrufe im Kernel aufgewendete CPU-Zeit ausgeführt wird, im Gegensatz zu Bibliothekscode, der noch im Benutzerbereich ausgeführt wird. Dies ist wie bei 'user' nur die vom Prozess verwendete CPU-Zeit. Im Folgenden finden Sie eine kurze Beschreibung des Kernelmodus (auch als "Supervisor" -Modus bezeichnet) und des Systemaufrufmechanismus.

In Ihrem speziellen Beispiel ist die Python-Version also schneller als die tatsächliche Zeit, die für die Fertigstellung benötigt wird. Der Python-Ansatz verbringt jedoch mehr Zeit im Kernel-Space und ruft Kernelfunktionen auf. Der bcBefehl verbringt im Wesentlichen keine Zeit im Kernelraum und seine gesamte Zeit wird im Benutzerraum verbracht, vermutlich um internen bcCode auszuführen.

Das macht für Sie keinen Unterschied. Die einzige Information, die Sie wirklich interessieren real, ist die tatsächliche Zeit, die zwischen dem Starten des Befehls und der Ausgabe vergangen ist.

Sie sollten sich auch darüber im Klaren sein, dass diese winzigen Unterschiede nicht stabil sind, sondern auch von der Auslastung Ihres Systems abhängen und sich jedes Mal ändern, wenn Sie den Befehl ausführen:

$ 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
terdon
quelle
10

Ich werde es aus einer anderen Perspektive erklären.

Fairerweise bchat es den Vorteil, dass es nichts von der Festplatte lesen muss und nur seine Blobs / Binärdateien benötigt, während Python eine Reihe von Modulen importieren und eine Datei lesen muss. So könnte Ihr Test in Richtung voreingenommen sein bc. Um es tatsächlich zu testen, sollten Sie verwenden, bc -q filewo fileenthält:

6^6^6
quit

Durch diese Änderung wurde die Verwendungsdauer geändert echo:

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

So verwenden Sie die Datei:

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

(Sie müssen die Methode von Terdon anwenden, um größere Unterschiede festzustellen, aber zumindest wissen wir, dass dies der Fall ist.)

Aus Sicht von Python muss Python jetzt jedes Mal, wenn die Datei geladen wird, von der Festplatte lesen, kompilieren und ausführen sowie Module als Andrew-Punkte laden , was die Ausführungszeit verlangsamt. Wenn Sie den Bytecode des Python-Skripts kompilieren, werden Sie feststellen, dass die Gesamtzeit für die Ausführung des Codes um 50% verkürzt wird:

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

zusammengestellt:

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

Wie Sie sehen, gibt es mehrere Faktoren, die die zeitliche Ausführung zwischen verschiedenen Tools beeinflussen können.

Braiam
quelle
3

Ich hatte den Vorteil, die anderen Antworten zu lesen. Für den Anfang sollten Leute wie ich wissen, warum wir es hier mit einer so großen Ganzzahl zu tun haben, dass beide Pythonund bcdie rechtsassoziative Exponentiation expandieren, was bedeutet, dass dies nicht der Fall ist6^36 wir die Bewertung , sondern 6^46656die wesentlich größer ist . 1

Mit Variationen der folgenden Befehle können wir einen Durchschnitt für ein bestimmtes Element der Ausgabe von beiden extrahieren time reservierten Worts als auch des Befehls :

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}'

Es ist möglich, eine andere Route zu wählen und die Datei vollständig aus dem Vergleich zu entfernen. Außerdem können wir das Timing von bc historisch mit dem dcBefehl vergleichen ersterer ein "Front-End-Prozessor" für letzteren ist. Die folgenden Befehle wurden zeitgesteuert:

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

Beachten Sie, dass der dcBefehl für die Potenzierung linksassoziativ ist.2

Wir haben einige Ergebnisse mit time(bash) für 1000 Iterationen (in Sekunden):

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 und dc bieten in diesem Zusammenhang eine vergleichbare Leistung.

Weniger genau 3 ergibt sich aus /usr/bin/timezB GNUtime Befehl (Skalengenauigkeit ist hier nicht gültig, aber die Ergebnisse sind ähnlich):

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

Ein Vorteil von /usr/bin/time ist, dass es die -vOption bietet, die viel mehr Informationen liefert, die schließlich nützlich sein könnten.

Es ist auch möglich, dies intern sozusagen mit dem timeitPython-Modul auszuwerten :

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

Das ist etwas schneller als das, was wir vorher gesehen haben. Lassen Sie uns den Interpreter selbst ausprobieren:

>>> 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

Das ist das Schnellste, was ich gesehen habe.


Wenn wir eine geringere Exponentiation wie auswerten 6^6, liefert der Befehl time überraschende Ergebnisse - unter Verwendung der gleichen forSchleifenbefehle, die wir jetzt verwendet haben:

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

Also mit einer kleineren ganzen Zahl bcist das plötzlich viel schneller ?? Vom Systemneustart bis zum zweiten Durchlauf macht es keinen Unterschied. Gleichzeitig erhalten wir, wenn wir timeitfür Python verwenden, Folgendes:

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

Dies ist Mikrosekunden , nicht Millisekunden, und entspricht nicht den viel langsameren Ergebnissen, die mit der forSchleife erzielt werden . Möglicherweise sind andere Tools erforderlich, um dies weiter zu testen, und wie andere bereits erklärt haben, gibt es hier mehr, als man auf den ersten Blick sieht. Es scheint, dass Python im Szenario der Frage schneller war, aber es ist nicht klar, ob darüber hinaus Schlussfolgerungen gezogen werden können ...


1. Unnötig zu erwähnen, dass es über den Rahmen der arithmetischen Erweiterung von Echo hinausgeht, dh echo $((6**6**6))- bashauch dafür ist es zufällig rechtsassoziativ, dh 6^6^6 = 6^(6^6).

2. Vergleichen Sie mit diesem: 6 6 ^ 6 ^ p.

3. Es ist möglich, dass der GNU-Zeitbefehl weitere Informationen liefert, wenn er unter BSD UNIX ausgeführt wird (GNU-Zeitinformationsdokument): Die meisten von 'time' angezeigten Informationen stammen aus dem Systemaufruf 'wait3'. Die Zahlen sind nur so gut wie die von 'wait3' zurückgegebenen. Viele Systeme messen nicht alle Ressourcen, über die "Zeit" berichten kann. Diese Ressourcen werden als Null gemeldet. Die Systeme, die die meisten oder alle Ressourcen messen, basieren auf 4.2 oder 4.3BSD. Spätere BSD-Versionen verwenden einen anderen Speicherverwaltungscode, der weniger Ressourcen misst. - Auf Systemen, die keinen 'wait3'-Aufruf haben, der Statusinformationen zurückgibt, wird stattdessen der' times'-Systemaufruf verwendet. Es liefert viel weniger Informationen als 'wait3', so dass 'time' auf diesen Systemen die meisten Ressourcen als Null anzeigt.

Gemeinschaft
quelle