Dari halaman ini , kita tahu bahwa:
Perbandingan berantai lebih cepat daripada menggunakan
and
operator. Tulisx < y < z
alih-alihx < y and y < z
.
Namun, saya mendapat hasil berbeda menguji cuplikan kode berikut:
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y < z"
1000000 loops, best of 3: 0.322 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y and y < z"
1000000 loops, best of 3: 0.22 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y < z"
1000000 loops, best of 3: 0.279 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y and y < z"
1000000 loops, best of 3: 0.215 usec per loop
Sepertinya itu x < y and y < z
lebih cepat daripada x < y < z
. Mengapa?
Setelah mencari beberapa posting di situs ini (seperti ini ) saya tahu bahwa "dievaluasi hanya sekali" adalah kuncinya x < y < z
, namun saya masih bingung. Untuk melakukan studi lebih lanjut, saya membongkar dua fungsi ini menggunakan dis.dis
:
import dis
def chained_compare():
x = 1.2
y = 1.3
z = 1.1
x < y < z
def and_compare():
x = 1.2
y = 1.3
z = 1.1
x < y and y < z
dis.dis(chained_compare)
dis.dis(and_compare)
Dan hasilnya adalah:
## chained_compare ##
4 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
5 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
6 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
7 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 DUP_TOP
25 ROT_THREE
26 COMPARE_OP 0 (<)
29 JUMP_IF_FALSE_OR_POP 41
32 LOAD_FAST 2 (z)
35 COMPARE_OP 0 (<)
38 JUMP_FORWARD 2 (to 43)
>> 41 ROT_TWO
42 POP_TOP
>> 43 POP_TOP
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
## and_compare ##
10 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
11 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
12 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
13 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 COMPARE_OP 0 (<)
27 JUMP_IF_FALSE_OR_POP 39
30 LOAD_FAST 1 (y)
33 LOAD_FAST 2 (z)
36 COMPARE_OP 0 (<)
>> 39 POP_TOP
40 LOAD_CONST 0 (None)
Tampaknya perintah itu x < y and y < z
memiliki lebih sedikit perbedaan daripada x < y < z
. Haruskah saya mempertimbangkan x < y and y < z
lebih cepat daripada x < y < z
?
Diuji dengan Python 2.7.6 pada CPU Intel (R) Xeon (R) E5640 @ 2.67GHz.
sumber
timeit
tes Anda, saya tertarik pada ini.y
bukan hanya pencarian variabel, tetapi proses yang lebih mahal seperti pemanggilan fungsi? Yaitu10 < max(range(100)) < 15
lebih cepat daripada10 < max(range(100)) and max(range(100)) < 15
karenamax(range(100))
disebut sekali untuk kedua perbandingan.Jawaban:
Perbedaannya adalah bahwa
x < y < z
y
hanya dievaluasi sekali. Ini tidak membuat perbedaan besar jika y adalah variabel, tetapi itu terjadi ketika itu adalah panggilan fungsi, yang membutuhkan waktu untuk menghitung.sumber
sleep()
fungsi di dalamnya?Bytecode optimal untuk kedua fungsi yang Anda tetapkan adalah
karena hasil perbandingan tidak digunakan. Mari kita buat situasi lebih menarik dengan mengembalikan hasil perbandingan. Mari juga hasilnya tidak diketahui pada waktu kompilasi.
Sekali lagi, dua versi perbandingan secara semantik identik, sehingga bytecode optimal adalah sama untuk kedua konstruksi. Sebisa mungkin aku bisa menyelesaikannya, akan terlihat seperti ini. Saya telah membubuhi keterangan setiap baris dengan konten stack sebelum dan sesudah setiap opcode, dalam Forth notation (atas stack di kanan,
--
membagi sebelum dan sesudah, trailing?
menunjukkan sesuatu yang mungkin ada atau tidak ada di sana). Perhatikan bahwaRETURN_VALUE
buang semua yang terjadi pada tumpukan di bawah nilai yang dikembalikan.Jika implementasi bahasa, CPython, PyPy, apa pun, tidak menghasilkan bytecode ini (atau urutan operasinya yang setara) untuk kedua variasi, yang menunjukkan kualitas buruk kompiler bytecode itu . Mendapatkan dari urutan bytecode yang Anda posting di atas adalah masalah yang terpecahkan (saya pikir semua yang Anda butuhkan untuk kasus ini adalah pelipatan konstan , penghilangan kode mati , dan pemodelan yang lebih baik dari isi tumpukan; penghapusan subekspresi umum juga akan murah dan berharga ), dan benar-benar tidak ada alasan untuk tidak melakukannya dalam implementasi bahasa modern.
Sekarang, semua implementasi bahasa saat ini memiliki kompiler bytecode berkualitas rendah. Tetapi Anda harus mengabaikannya saat melakukan pengkodean! Anggap kompiler bytecode itu bagus, dan tulis kode yang paling mudah dibaca . Mungkin akan cukup cepat. Jika tidak, cari peningkatan algoritmik terlebih dahulu, dan coba Cython kedua - yang akan memberikan peningkatan lebih banyak untuk upaya yang sama daripada tweak tingkat ekspresi apa pun yang mungkin Anda terapkan.
sumber
interesting_compare
kode bytecode sederhana di atas (yang hanya akan berfungsi dengan inlining). Sepenuhnya offtopic tetapi: Inlining adalah salah satu optimisasi yang paling penting untuk setiap kompiler. Anda dapat mencoba menjalankan beberapa tolok ukur dengan mengatakan HotSpot pada program nyata (bukan tes matematika yang menghabiskan 99% waktu mereka dalam satu loop panas yang dioptimalkan dengan tangan [dan karenanya tidak memiliki panggilan fungsi lagi]] ketika Anda menonaktifkan kemampuan untuk inline apa pun - Anda akan melihat regresi besar.interesting_compare
.y
tidak mengubah tumpukan, karena ia memiliki banyak alat debug.Karena perbedaan dalam output tampaknya karena kurangnya optimasi, saya pikir Anda harus mengabaikan perbedaan itu untuk kebanyakan kasus - bisa jadi perbedaannya akan hilang. Perbedaannya adalah karena
y
hanya harus dievaluasi sekali dan itu diselesaikan dengan menduplikasi pada tumpukan yang membutuhkan tambahanPOP_TOP
- solusi untuk menggunakanLOAD_FAST
mungkin dapat dilakukan.Namun perbedaan yang penting adalah bahwa pada
x<y and y<z
yang keduay
harus dievaluasi dua kali jikax<y
dievaluasi benar, ini memiliki implikasi jika evaluasiy
memakan waktu yang cukup lama atau memiliki efek samping.Dalam sebagian besar skenario Anda harus menggunakan
x<y<z
meskipun itu agak lambat.sumber
Pertama-tama, perbandingan Anda tidak banyak berarti karena dua konstruksi yang berbeda tidak diperkenalkan untuk memberikan peningkatan kinerja, jadi Anda tidak boleh memutuskan apakah akan menggunakan satu di tempat yang lain berdasarkan itu.
The
x < y < z
membangun:x
,y
danz
sekali dan periksa apakah seluruh kondisi berlaku. Menggunakanand
perubahan semantik dengan mengevaluasiy
beberapa kali, yang dapat mengubah hasilnya .Jadi pilih satu di tempat yang lain tergantung pada semantik yang Anda inginkan dan, jika mereka setara, apakah satu lebih mudah dibaca daripada yang lain.
Ini mengatakan: lebih banyak kode yang dibongkar tidak menyiratkan kode lebih lambat. Namun mengeksekusi lebih banyak operasi bytecode berarti bahwa setiap operasi lebih sederhana dan membutuhkan iterasi dari loop utama. Ini berarti bahwa jika operasi yang Anda lakukan sangat cepat (misalnya pencarian variabel lokal seperti yang Anda lakukan di sana), maka overhead untuk mengeksekusi lebih banyak operasi bytecode bisa jadi masalah.
Tetapi perhatikan bahwa hasil ini tidak berlaku dalam situasi yang lebih umum, hanya pada "kasus terburuk" yang terjadi pada profil Anda. Seperti yang dicatat orang lain, jika Anda berubah
y
sesuatu yang membutuhkan waktu sedikit lebih lama, Anda akan melihat bahwa hasilnya berubah, karena notasi berantai mengevaluasinya hanya sekali.Meringkas:
sumber