python vs bc dalam mengevaluasi 6 ^ 6 ^ 6

29

Saya mengevaluasi ekspresi 6^6^6menggunakan pythondan bcsecara terpisah.

Isi dari file python adalah print 6**6**6. Ketika saya mengeksekusi time python test.py, saya mendapatkan output sebagai

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

Dan kemudian, saya menjalankan perintah time echo 6^6^6 | bcyang memberi saya output berikut

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

Dari hasil ini jelas bahwa waktu sistem yang diambil oleh python dan bc adalah 11ms dan 5ms masing-masing. The perintah bc mengungguli python di tingkat saat sys tetapi ketika datang ke pengguna dan real time python hampir 4 kali lebih cepat dari bc . Apa yang mungkin terjadi di sana. Saya belum memberikan prioritas pada prosesnya. Saya mencoba memahami situasi ini.

Ganessh
sumber
Jadi maksud Anda komponen sys hanya memberikan waktu yang diperlukan untuk memuat dan runtime akan diberikan dalam komponen pengguna dari output?
ganessh
Saya benar-benar tidak yakin, itu sebabnya saya mengirim komentar. Itu hanya tebakan.
terdon
7
echo | bcmelibatkan meluncurkan subkulit karena pipa - di situlah sebagian waktu pengguna tambahan Anda mungkin berasal. Untuk membuat ini menjadi uji yang adil, skrip python harus membaca dari stdin sehingga Anda bisa time echo 6**6**6 | whatever.py.
goldilocks
1
Saya lebih suka meletakkan baris perintah be menjadi naskah dan mengatur waktu pelaksanaannya. Atau gunakan echo 6^6^6 | time bc.
daniel kullmann
1
Catatan: dalam python 6**6**6ekspresi sebenarnya dihitung pada waktu kompilasi . Namun karena Anda meluncurkan file secara langsung alih-alih mengimpornya dari modul, ini tidak masalah. Untuk melihat perbedaan yang dimasukkan 10**12345678ke dalam a.pyfile dan mencoba mengimpornya dari juru bahasa interaktif. Kemudian tutup penerjemah, mulai ulang dan impor alagi. Pertama kali harus mengambil jumlah waktu yang nyata (karena python menyusun modul), sedangkan yang kedua memuat .pyc, yang harus seketika,
Bakuriu

Jawaban:

25

Python mengimpor sejumlah besar file pada saat startup:

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

Masing-masing membutuhkan lebih banyak upaya membuka file Python, karena ada banyak cara untuk mendefinisikan modul:

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

Setiap "mencoba", kecuali yang sudah dibangun, membutuhkan os-level / system calls, dan setiap "impor" tampaknya memicu sekitar 8 pesan "mencoba". (Ada cara untuk mengurangi ini menggunakan zipimport, dan setiap jalur di PYTHONPATH Anda mungkin memerlukan panggilan lain.)

Ini berarti ada hampir 200 panggilan sistem stat sebelum Python dimulai pada mesin saya, dan "waktu" menetapkan itu untuk "sys" daripada "pengguna", karena program pengguna menunggu pada sistem untuk melakukan sesuatu.

Sebagai perbandingan, dan seperti kata terdon, "bc" tidak memiliki biaya startup yang tinggi. Melihat output dtruss (saya punya Mac; "strace" untuk OS berbasis Linux), saya melihat bahwa bc tidak membuat panggilan sistem terbuka () atau stat () sendiri, kecuali untuk memuat beberapa yang dibagi perpustakaan adalah awal, yang tentu saja Python juga. Selain itu, Python memiliki lebih banyak file untuk dibaca, sebelum siap untuk memproses apa pun.

Menunggu disk lambat.

Anda dapat memahami biaya startup Python dengan melakukan:

time python -c pass

Ini 0,032 di komputer saya, sedangkan 'cetak 6 ** 6 ** 6' adalah 0,072, jadi biaya awal adalah 1/2 dari keseluruhan waktu dan perhitungan + konversi ke desimal adalah setengahnya. Sementara:

time echo 1 | bc

membutuhkan 0,005s, dan "6 ^ 6 ^ 6" membutuhkan 0,184s sehingga eksponensial bc lebih dari 4x lebih lambat dari Python walaupun itu 7x lebih cepat untuk memulai.

Andrew Dalke
sumber
4
Anda agak mengubur timah di sana. Anda mungkin ingin memindahkan bit akhir ke atas.
Bersepeda
Hanya karena ketertarikan pada mesin saya: waktu python -c 'pass' 0m0.025s, waktu python -c 'print 6 6 6' 0m0.087s tetapi waktu python -c 'x = 6 6 6' 0m0.028s Jadi sebagian besar waktu menghasilkan jumlah yang besar.
Steve Barnes
Ya, konversi ke basis 10 membutuhkan waktu kuadratik dalam jumlah digit. Sebagai kasus ekstrem, coba cetak salah satu bilangan prima Mersenne yang lebih besar. Sangat cepat untuk menghitung, tetapi membutuhkan waktu lama untuk mencetak dalam basis 10.
Andrew Dalke
11

Saya menemukan jawaban yang bagus pada SO menjelaskan berbagai bidang:

  • Real adalah jam dinding - waktu dari awal hingga akhir panggilan. Ini semua waktu yang telah berlalu termasuk irisan waktu yang digunakan oleh proses lain dan waktu proses yang dihabiskan diblokir (misalnya jika menunggu I / O untuk menyelesaikan).

  • Pengguna adalah jumlah waktu CPU yang dihabiskan dalam kode mode pengguna (di luar kernel) dalam proses. Ini hanya waktu CPU aktual yang digunakan dalam menjalankan proses. Proses dan waktu lain yang dihabiskan proses tidak termasuk dalam angka ini.

  • Sys adalah jumlah waktu CPU yang dihabiskan di kernel dalam proses. Ini berarti mengeksekusi waktu CPU yang dihabiskan dalam panggilan sistem di dalam kernel, sebagai lawan dari kode pustaka, yang masih berjalan di ruang pengguna. Seperti 'pengguna', ini hanya waktu CPU yang digunakan oleh proses. Lihat di bawah untuk penjelasan singkat tentang mode kernel (juga dikenal sebagai mode 'pengawas') dan mekanisme panggilan sistem.

Jadi, dalam contoh spesifik Anda, versi python lebih cepat dalam hal waktu aktual yang diperlukan untuk menyelesaikannya. Namun, pendekatan python menghabiskan lebih banyak waktu di ruang kernel, membuat panggilan ke fungsi kernel. The bcperintah menghabiskan dasarnya tidak ada waktu dalam ruang kernel dan semua waktu yang dihabiskan di user-space, mungkin menjalankan internal yang bckode.

Ini tidak ada bedanya bagi Anda, satu-satunya informasi yang benar-benar Anda pedulikan adalah realwaktu aktual yang berlalu antara meluncurkan perintah dan mendapatkan outputnya.

Anda juga harus menyadari bahwa perbedaan kecil ini tidak stabil, mereka juga akan tergantung pada beban sistem Anda dan akan berubah setiap kali Anda menjalankan perintah:

$ 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
sumber
10

Saya akan menjelaskannya dari perspektif lain.

Agar adil, bcmemiliki keuntungan karena tidak harus membaca apa pun dari disk dan hanya perlu blob / binari-nya sementara python harus mengimpor serangkaian modul + membaca file. Jadi tes Anda mungkin bias bc. Untuk benar-benar mengujinya, Anda harus menggunakan bc -q filetempat yang fileberisi:

6^6^6
quit

Mengubah hal itu mengubah waktu penggunaan echo:

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

Untuk menggunakan file:

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

(Anda harus menggunakan metode terdon untuk melihat perbedaan yang lebih besar, tetapi setidaknya kita tahu itu)

Sekarang, dari perspektif python, python perlu membaca dari disk, mengkompilasi dan mengeksekusi setiap kali file, ditambah memuat modul sebagai poin Andrew , yang membuat waktu eksekusi lebih lambat. Jika Anda mengkompilasi kode byte skrip python, Anda akan melihat bahwa dibutuhkan 50% lebih sedikit waktu total untuk mengeksekusi kode:

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

dikompilasi:

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

Seperti yang Anda lihat, ada beberapa faktor yang dapat mempengaruhi waktu eksekusi antara berbagai alat.

Braiam
sumber
3

Saya mendapat manfaat dari membaca jawaban lain. Sebagai permulaan, orang-orang seperti saya harus tahu alasan mengapa kita berurusan dengan bilangan bulat yang sangat besar di sini adalah karena keduanya Pythondan bcmelakukan ekspansi eksponensial asosiatif yang benar, yang berarti bahwa ini bukan 6^36kita sedang mengevaluasi tetapi lebih tepatnya 6^46656yang jauh lebih besar. 1

Menggunakan variasi pada perintah berikut, kita dapat mengekstrak rata-rata untuk elemen spesifik dari output timekata dan perintah yang dipesan:

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

Dimungkinkan untuk memilih rute lain dan menghapus file sepenuhnya dari perbandingan. Juga, kita dapat membandingkan waktu bc dengan sesuatu seperti dcperintah, karena secara historis yang pertama adalah "prosesor ujung depan" ke yang terakhir. Perintah berikut diberi batas waktu:

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

Perhatikan bahwa dcperintah tersebut adalah asosiatif kiri untuk eksponensial. 2

Kami memiliki beberapa hasil dengan time(bash) untuk 1000 iterasi (dalam detik):

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

bcdan dcmenawarkan kinerja yang sebanding dalam konteks ini.

Kurang akurat 3 hasil dari perintah /usr/bin/timeGNU yaitu time(skala presisi tidak valid di sini tetapi hasilnya serupa):

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

Keuntungannya /usr/bin/timeadalah ia menawarkan -vopsi yang menghasilkan lebih banyak informasi yang akhirnya berguna.

Dimungkinkan juga untuk mengevaluasi ini secara internal sehingga dapat berbicara dengan timeitmodul 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

Itu sedikit lebih cepat dari yang kita lihat sebelumnya. Mari kita coba penerjemahnya sendiri:

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

Itu tercepat yang pernah saya lihat.


Jika kita mengevaluasi exponentiation yang lebih rendah seperti 6^6, maka perintah waktu menghasilkan hasil yang mengejutkan - menggunakan forperintah loop yang sama yang kita gunakan sekarang kita miliki:

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

Jadi dengan integer yang lebih kecil bctiba-tiba jauh lebih cepat ?? Dari sistem reboot untuk menjalankan kedua tidak ada bedanya. Namun pada saat yang sama, jika kita menggunakan timeituntuk Python, kita mendapatkan:

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

Ini adalah mikrodetik , bukan milidetik, jadi ini tidak cocok dengan hasil yang jauh lebih lambat menggunakan forloop. Mungkin alat lain diperlukan untuk menguji ini lebih lanjut dan seperti yang telah dijelaskan orang lain ada lebih dari memenuhi mata di sini. Tampaknya Python lebih cepat dalam skenario pertanyaan tetapi tidak jelas apakah kesimpulan dapat ditarik lebih dari itu ...


1. Tak perlu dikatakan itu di luar lingkup sesuatu seperti ekspansi aritmatika gema yaitu echo $((6**6**6))- bashjuga kebetulan asosiatif yang tepat untuk itu yaitu 6^6^6 = 6^(6^6).

2. Bandingkan dengan ini: 6 6 ^ 6 ^ p.

3. Mungkin perintah waktu GNU memberikan lebih banyak info saat dijalankan pada BSD UNIX (dokumen info waktu GNU): Sebagian besar informasi yang ditampilkan oleh 'waktu' berasal dari panggilan sistem 'wait3'. Jumlahnya hanya sebagus yang dikembalikan oleh 'wait3'. Banyak sistem tidak mengukur semua sumber daya yang 'waktu' dapat laporkan; sumber daya tersebut dilaporkan sebagai nol. Sistem yang mengukur sebagian besar atau semua sumber daya didasarkan pada 4.2 atau 4.3BSD. Rilis BSD kemudian menggunakan kode manajemen memori yang berbeda yang mengukur lebih sedikit sumber daya. - Pada sistem yang tidak memiliki panggilan 'wait3' yang mengembalikan informasi status, panggilan sistem 'kali' digunakan sebagai gantinya. Ini memberikan informasi yang jauh lebih sedikit daripada 'wait3', jadi pada sistem 'waktu' itu melaporkan sebagian besar sumber daya sebagai nol.

Komunitas
sumber