python vs bc w ocenie 6 ^ 6 ^ 6


29

Oceniam wyrażenie 6^6^6za pomocą pythoni bcosobno.

Zawartość pliku python to print 6**6**6. Kiedy wykonuję time python test.py, otrzymuję wynik jako

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

Następnie uruchomiłem polecenie, time echo 6^6^6 | bcktóre dało mi następujący wynik

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

Z tych wyników jasno wynika, że ​​czas sys zajęty przez python i bc wynosił odpowiednio 11 ms i 5 ms. Komenda bc przewyższyła Pythona na poziomie czasu sys, ale jeśli chodzi o użytkownika i python w czasie rzeczywistym, była prawie 4 razy szybsza niż bc . Co mogło tam pójść. Nie nadałem żadnych priorytetów procesom jako takim. Próbuję zrozumieć tę sytuację.


Czy masz na myśli, że składnik sys podaje tylko czas potrzebny do załadowania, a środowisko wykonawcze zostanie podane w elemencie użytkownika danych wyjściowych?
ganessh

Naprawdę nie jestem pewien, dlatego opublikowałem komentarz. To tylko przypuszczenie.
terdon

7
echo | bcpolega na uruchomieniu podpowłoki z powodu potoku - stąd prawdopodobnie pochodzi część twojego dodatkowego czasu użytkownika. Aby ten test był sprawiedliwy, skrypt Pythona powinien czytać ze standardowego wejścia, abyś mógł time echo 6**6**6 | whatever.py.
goldilocks

1
Wolałbym umieścić wiersz polecenia be w skrypcie i czas wykonania tego. Lub użyj echo 6^6^6 | time bc.
Daniel Kullmann

1
Uwaga dodatkowa: w pythonie 6**6**6wyrażenie jest obliczane w czasie kompilacji . Ponieważ jednak uruchamiasz plik bezpośrednio zamiast importować go z modułu, nie powinno to mieć znaczenia. Aby zobaczyć różnicę wprowadzoną 10**12345678do a.pypliku i spróbować zaimportować go z interaktywnego interpretera. Następnie zamknij interpreter, uruchom go ponownie i zaimportuj aponownie. Za pierwszym razem powinno to zająć zauważalnie dużo czasu (ponieważ Python kompiluje moduł), a za drugim razem ładuje .pyc, co powinno być natychmiastowe,
Bakuriu

Odpowiedzi:


25

Python importuje dużą liczbę plików podczas uruchamiania:

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

Każdy z nich wymaga jeszcze większej liczby prób otwarcia pliku Python, ponieważ istnieje wiele sposobów definiowania modułu:

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

Każde „próbowanie”, z wyjątkiem tych, które są wbudowane, wymaga wywołań systemu operacyjnego / systemu operacyjnego, a każde „importowanie” wydaje się wywoływać około 8 wiadomości „próbujących”. (Były sposoby na zmniejszenie tego za pomocą zipimport, a każda ścieżka w Twojej PYTHONPATH może wymagać innego połączenia.)

Oznacza to, że istnieje prawie 200 wywołań systemowych statystyki, zanim Python uruchomi się na moim komputerze, a „czas” przypisuje to do „sys”, a nie do „użytkownika”, ponieważ program użytkownika czeka w systemie na wykonanie różnych czynności.

Dla porównania, jak powiedział terdon, „bc” nie ma tak wysokich kosztów startowych. Patrząc na dane wyjściowe dtruss (mam komputer Mac; „strace” dla systemu operacyjnego opartego na Linuksie), widzę, że bc nie wykonuje własnych wywołań systemowych open () ani stat (), z wyjątkiem ładowania kilku wspólnych biblioteki są początkiem, co oczywiście robi również Python. Ponadto Python ma więcej plików do odczytania, zanim będzie gotowy do przetworzenia czegokolwiek.

Oczekiwanie na dysk jest wolne.

Możesz poznać koszt uruchomienia Pythona, wykonując:

time python -c pass

Na mojej maszynie jest 0,032 sekundy, podczas gdy „drukuj 6 ** 6 ** 6” to 0,072 s, więc koszt uruchomienia to 1/2 całkowitego czasu, a obliczenie + konwersja na dziesiętne to druga połowa. Podczas:

time echo 1 | bc

zajmuje 0,005 sek., a „6 ^ 6 ^ 6” zajmuje 0,184 sek., więc potęgowanie bc jest ponad 4x wolniejsze niż Pythona, mimo że 7-krotnie szybciej jest zacząć.


4
W pewnym sensie zakopałeś tam ołów. Możesz przenieść końcówkę na górę.
Riking

Tylko z odsetek od mojego komputera: czas python -c 'przechodzą' 0m0.025s, czas python -c 'print 6 6 6' 0m0.087s ale czas python -c 'x = 6 6 6' 0m0.028s Więc większość z czas wyprowadza dużą liczbę.
Steve Barnes

Tak, konwersja do bazy 10 zajmuje kwadratowy czas w liczbie cyfr. W skrajnym przypadku spróbuj wydrukować jedną z większych liczb pierwszych Mersenne. Obliczenie jest bardzo szybkie, ale drukowanie w bazie 10. zajmuje dużo czasu
Andrew Dalke

11

Znalazłem fajną odpowiedź na SO wyjaśniającą różne pola:

  • Rzeczywisty jest czas naścienny - czas od początku do końca połączenia. Jest to cały czas, który upłynął, w tym przedziały czasowe używane przez inne procesy oraz czas, jaki proces spędza zablokowany (na przykład, jeśli czeka na zakończenie operacji we / wy).

  • Użytkownik to ilość czasu procesora spędzona w kodzie trybu użytkownika (poza jądrem) w procesie. Jest to tylko rzeczywisty czas pracy procesora użyty do wykonania procesu. Inne procesy i czas zablokowany przez proces nie liczą się do tej liczby.

  • Sys to ilość czasu procesora spędzonego w jądrze w procesie. Oznacza to wykonywanie czasu procesora spędzonego na wywołaniach systemowych w jądrze, w przeciwieństwie do kodu biblioteki, który wciąż działa w przestrzeni użytkownika. Podobnie jak „użytkownik”, proces ten zajmuje tylko czas procesora. Poniżej znajduje się krótki opis trybu jądra (znanego również jako tryb „superwizora”) i mechanizmu wywołania systemowego.

Tak więc w twoim konkretnym przykładzie wersja Pythona jest szybsza pod względem faktycznego czasu potrzebnego do jej ukończenia. Jednak podejście python spędza więcej czasu w przestrzeni jądra, wykonując wywołania funkcji jądra. bcKomenda zasadniczo nie spędza czasu w przestrzeni jądra i wszystko to jest czas spędzony w przestrzeni użytkownika, przypuszczalnie działa wewnętrzny bckod.

Nie ma to dla ciebie żadnej różnicy, jedyne informacje, na których naprawdę Ci zależy, realto faktyczny czas, jaki upłynął między uruchomieniem polecenia a uzyskaniem jego wyniku.

Należy również pamiętać, że te niewielkie różnice nie są stabilne, będą również zależeć od obciążenia systemu i będą się zmieniać za każdym razem, gdy uruchomisz polecenie:

$ 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

Wyjaśnię to z innej perspektywy.

Szczerze bcmówiąc , ma tę zaletę, że nie musi niczego odczytywać z dysku i potrzebuje tylko swoich obiektów blob / binariów, podczas gdy Python musi zaimportować serię modułów + odczyt pliku. Więc twój test może być stronniczy bc. Aby go przetestować, powinieneś użyć bc -q filegdzie filezawiera:

6^6^6
quit

Zmiana tylko zmodyfikowała czas używania echo:

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

Aby użyć pliku:

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

(będziesz musiał użyć metody Terdona, aby zauważyć większe różnice, ale przynajmniej wiemy, że są)

Teraz, z perspektywy Pythona, python musi czytać z dysku, kompilować i uruchamiać za każdym razem plik, a także ładować moduły jak wskazuje Andrew , co skraca czas wykonywania. Jeśli skompilujesz kod bajtowy skryptu python, zauważysz, że wykonanie kodu zajmuje o 50% mniej czasu:

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

opracowano:

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

Jak widać, istnieje kilka czynników, które mogą wpływać na czas wykonywania różnych narzędzi.


3

Miałem tę zaletę, że przeczytałem inne odpowiedzi. Na początek ludzie tacy jak ja, powinien wiedzieć, dlaczego mamy do czynienia z tak wielką liczbą całkowitą jest to, że zarówno Pythoni bczrobić prawym asocjacyjną ekspansję potęgowanie, co oznacza, że to nie 6^36my oceny, lecz 6^46656która jest znacznie większa. 1

Używając wariantów następujących poleceń, możemy wyodrębnić średnią dla określonego elementu wyniku zarówno timesłowa zarezerwowanego, jak i polecenia:

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

Można przejść inną trasę i całkowicie usunąć plik z porównania. Możemy także porównać czas bc z czymś takim jak dcpolecenie, ponieważ historycznie ten pierwszy jest „procesorem front-end” do drugiego. Nastąpiły następujące komendy:

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

Zauważ, że dcpolecenie ma lewy asocjatywny dla potęgowania. 2)

Mamy kilka wyników dla time(bash) dla 1000 iteracji (w sekundach):

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

bci dcoferują porównywalną wydajność w tym kontekście.

Mniej dokładne 3 wyniki z /usr/bin/timenp. timePolecenia GNU (dokładność skali nie jest tutaj poprawna, ale wyniki są podobne):

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

Zaletą /usr/bin/timejest to, że oferuje -vopcję, która daje znacznie więcej informacji, które mogą być przydatne w końcu.

Można również ocenić to wewnętrznie , aby porozmawiać z timeitmodułem 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

To trochę szybciej niż to, co widzieliśmy wcześniej. Spróbujmy samego tłumacza:

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

To najszybszy jaki widziałem.


Jeśli ocenimy mniejszą potęgowanie 6^6, to polecenie czasowe daje zaskakujące wyniki - używając tych samych forpoleceń pętli, które teraz używamy:

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

Więc przy mniejszej liczbie całkowitej bcjest nagle znacznie szybsze? Od ponownego uruchomienia systemu do drugiego uruchomienia nie ma znaczenia. Jednak jednocześnie, jeśli używamy timeitPythona, otrzymujemy:

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

Są to mikrosekundy , a nie milisekundy, więc nie pasuje to do znacznie wolniejszych wyników w forpętli. Być może potrzebne są inne narzędzia do dalszego testowania, a jak wyjaśnili inni, tutaj jest więcej niż na pierwszy rzut oka. Wygląda na to, że Python był szybszy w scenariuszu pytania, ale nie jest jasne, czy można wyciągnąć wnioski poza tym ...


1. Nie trzeba dodawać, że wykracza poza zakres czegoś takiego jak ekspansja arytmetyczna echa, tj. echo $((6**6**6))- bashzdarza się również, że jest do tego słusznie asocjatywna, tj 6^6^6 = 6^(6^6).

2. Porównaj z tym: 6 6 ^ 6 ^ p.

3. Możliwe jest, że polecenie czasu GNU dostarcza więcej informacji, gdy jest uruchamiane w systemie BSD UNIX (dokument informacji o czasie GNU): Większość informacji wyświetlanych jako „czas” pochodzi z wywołania systemowego „wait3”. Liczby są tak dobre, jak te zwracane przez „wait3”. Wiele systemów nie mierzy wszystkich zasobów, o których „czas” może informować; zasoby te są zgłaszane jako zero. Systemy mierzące większość lub wszystkie zasoby oparte są na 4.2 lub 4.3BSD. Późniejsze wersje BSD używają innego kodu zarządzania pamięcią, który mierzy mniej zasobów. - W systemach, które nie mają wywołania „wait3”, które zwraca informacje o statusie, zamiast tego używane jest wywołanie systemowe „times”. Dostarcza znacznie mniej informacji niż „wait3”, więc w tych systemach „czas” zgłasza większość zasobów jako zero.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.