Modul dis Python dan konvolusi konstanta

Halo semuanya. Hari ini kami ingin membagikan terjemahan lain yang disiapkan untuk mengantisipasi peluncuran kursus "Pengembang web dengan Python . " Ayo pergi!



Saya sangat terkejut baru-baru ini ketika saya menemukan itu


>>> pow(3,89) 

bekerja lebih lambat daripada


 >>> 3**89 

Saya mencoba memberikan beberapa penjelasan yang dapat diterima, tetapi tidak bisa. Saya melacak waktu eksekusi kedua ekspresi ini menggunakan modul timeit dari Python 3:


 $ python3 -m timeit 'pow(3,89)' 500000 loops, best of 5: 688 nsec per loop $ python3 -m timeit '3**89' 500000 loops, best of 5: 519 nsec per loop 

Perbedaannya kecil. Hanya 0,1 μs, tapi itu menghantuiku. Jika saya tidak dapat menjelaskan apa pun dalam pemrograman, saya mulai menderita insomnia


Saya menemukan jawabannya menggunakan umpan IRC Python di Freenode. Alasan pow berfungsi sedikit lebih lambat adalah karena CPython sudah memiliki langkah tambahan memuat pow dari namespace. Sedangkan saat memanggil 3 ** 9, beban seperti itu pada prinsipnya tidak diperlukan. Ini juga berarti bahwa perbedaan waktu ini akan tetap lebih atau kurang konstan jika nilai input meningkat.


Hipotesis dikonfirmasi:


 $ python3 -m timeit 'pow(3,9999)' 5000 loops, best of 5: 58.5 usec per loop $ python3 -m timeit '3**9999' 5000 loops, best of 5: 57.3 usec per loop 

Dalam proses menemukan solusi untuk masalah ini, saya juga belajar tentang modul dis. Ini memungkinkan Anda untuk mendekompilasi bytecode Python dan mempelajarinya. Itu adalah penemuan yang sangat menarik, karena baru-baru ini saya telah mempelajari rekayasa balik file biner, dan modul yang ditemukan berguna dalam hal ini.


Saya mendekompilasi bytecode dari ekspresi di atas dan mendapatkan yang berikut:


 >>> import dis >>> dis.dis('pow(3,89)') # 1 0 LOAD_NAME 0 (pow) # 2 LOAD_CONST 0 (3) # 4 LOAD_CONST 1 (89) # 6 CALL_FUNCTION 2 # 8 RETURN_VALUE >>> dis.dis('3**64') # 1 0 LOAD_CONST 0 (3433683820292512484657849089281) # 2 RETURN_VALUE >>> dis.dis('3**65') # 1 0 LOAD_CONST 0 (3) # 2 LOAD_CONST 1 (65) # 4 BINARY_POWER # 6 RETURN_VALUE 

Anda dapat membaca tentang bagaimana memahami dengan benar keluaran dis.dis dengan merujuk jawaban ini di Stackoverflow.


Oke, kembali ke kode. Pow dekompilasi masuk akal. Bytecode memuat pow dari namespace, memuat ke register 3 dan 89, dan akhirnya memanggil fungsi pow. Tetapi mengapa output dari dua dekompilasi berikutnya berbeda? Bagaimanapun, semua yang telah kami ubah adalah nilai eksponen dari 64 menjadi 65!


Pertanyaan ini memperkenalkan saya pada konsep baru lain yang disebut "konvolusi konstanta". Itu berarti bahwa ketika kita memiliki ekspresi konstan, Python menghitung nilainya pada tahap kompilasi, jadi ketika Anda menjalankan program, itu tidak akan memakan banyak waktu, karena Python menggunakan nilai yang sudah dihitung. Lihatlah ini:


 def one_plue_one(): return 1+1 # --vs-- def one_plue_one(): return 2 

Python mengkompilasi fungsi pertama menjadi fungsi kedua dan menggunakannya saat menjalankan kode. Tidak buruk, ya?


Jadi mengapa konvolusi konstanta bekerja untuk 3 ** 64, tetapi tidak untuk 3 ** 65? Ya saya tidak tahu. Ini mungkin terkait dengan pembatasan jumlah derajat yang sebelumnya dihitung oleh sistem dalam memori. Saya bisa saja salah. Langkah selanjutnya yang saya rencanakan adalah menggali kode sumber Python di waktu senggang saya dan mencoba memahami apa yang sedang terjadi. Saya masih mencari jawaban untuk pertanyaan saya, jadi jika Anda punya ide, bagikan di komentar.


Saya ingin Anda mengambil inspirasi dari pos ini untuk menemukan solusi untuk masalah Anda sendiri. Anda tidak pernah tahu ke mana jawabannya akan membawa Anda. Pada akhirnya, Anda dapat mempelajari sesuatu yang sama sekali baru, seperti yang terjadi pada saya. Saya harap nyala rasa ingin tahu masih menyala di dalam diri Anda!


Pernahkah Anda memperhatikan hal serupa? Menunggu komentar Anda!

Source: https://habr.com/ru/post/id460143/


All Articles