Misalkan program Python Anda lambat, dan Anda mengetahui bahwa ini
hanya sebagian karena kurangnya sumber daya prosesor . Bagaimana saya mengetahui bagian mana dari kode yang dipaksa untuk mengharapkan sesuatu yang tidak berlaku untuk CPU?

Setelah membaca materi, terjemahan yang kami terbitkan hari ini, Anda akan belajar cara menulis profiler Anda sendiri untuk kode Python. Kita berbicara tentang alat yang akan mendeteksi tempat di kode yang tidak aktif sambil menunggu rilis sumber daya tertentu. Secara khusus, kami akan membahas hal berikut di sini:
- Apa yang bisa program harapkan?
- Membuat profil penggunaan sumber daya yang bukan sumber daya CPU.
- Mengubah profil konteks yang tidak disengaja.
Apa yang diharapkan dari program ini?
Pada saat-saat ketika program tidak sibuk dengan perhitungan intensif menggunakan prosesor, sepertinya menunggu sesuatu. Inilah yang dapat menyebabkan tidak adanya program:
- Sumber daya jaringan. Ini mungkin termasuk menunggu selesainya pencarian DNS, menunggu tanggapan dari sumber daya jaringan, menunggu beberapa data untuk menyelesaikan pemuatan, dan sebagainya.
- Hard drive Membaca data dari hard drive mungkin memerlukan waktu. Hal yang sama dapat dikatakan tentang menulis ke disk. Kadang-kadang operasi baca atau tulis dilakukan hanya menggunakan cache yang terletak di RAM. Dengan pendekatan ini, semuanya terjadi dengan sangat cepat. Tetapi kadang-kadang, ketika suatu program berinteraksi langsung dengan disk, operasi seperti itu ternyata agak lambat.
- Kunci. Suatu program mungkin menunggu untuk membuka kunci utas atau proses.
- Penangguhan pekerjaan. Terkadang suatu program dapat dengan sengaja menghentikan pekerjaan, misalnya, berhenti di antara upaya untuk melakukan beberapa tindakan.
Bagaimana menemukan tempat-tempat program di mana terjadi sesuatu yang sangat mempengaruhi kinerja?
Metode nomor 1: analisis waktu selama program tidak menggunakan prosesor
Profiler built-in Python,
cProfile
, mampu mengumpulkan data tentang berbagai indikator yang terkait dengan pengoperasian program. Karena itu, ini dapat digunakan untuk membuat alat yang dengannya Anda dapat menganalisis waktu selama program tidak menggunakan sumber daya prosesor.
Sistem operasi dapat
memberi tahu kami dengan tepat berapa banyak waktu prosesor program yang digunakan.
Bayangkan kita sedang membuat profil sebuah program single-threaded. Program multithreaded lebih sulit diprofilkan, dan menggambarkan proses ini juga tidak mudah. Jika program berjalan selama 9 detik dan pada saat yang sama menggunakan prosesor selama 7,5 detik, ini berarti ia menghabiskan 1,5 detik menunggu.
Pertama, buat penghitung waktu yang akan mengukur batas waktu:
import os def not_cpu_time(): times = os.times() return times.elapsed - (times.system + times.user)
Kemudian buat profiler yang menganalisis saat ini:
import cProfile, pstats def profile_not_cpu_time(f, *args, **kwargs): prof = cProfile.Profile(not_cpu_time) prof.runcall(f, *args, **kwargs) result = pstats.Stats(prof) result.sort_stats("time") result.print_stats()
Setelah itu, Anda dapat membuat profil berbagai fungsi:
>>> profile_not_cpu_time( ... lambda: urlopen("https://pythonspeed.com").read()) ncalls tottime percall filename:lineno(function) 3 0.050 0.017 _ssl._SSLSocket.read 1 0.040 0.040 _socket.getaddrinfo 1 0.020 0.020 _socket.socket.connect 1 0.010 0.010 _ssl._SSLSocket.do_handshake 342 0.010 0.000 find.str 192 0.010 0.000 append.list
Hasilnya memungkinkan kita untuk menyimpulkan bahwa sebagian besar waktu dihabiskan untuk membaca data dari soket, tetapi butuh beberapa waktu untuk melakukan pencarian DNS (
getaddrinfo
), serta untuk melakukan jabat tangan TCP (
connect
) dan jabat tangan TLS / SSL.
Karena kami telah berhati-hati untuk menyelidiki periode operasi program di mana ia tidak menggunakan sumber daya prosesor, kami tahu bahwa semua ini adalah waktu tunggu murni, yaitu waktu ketika program tidak sibuk dengan perhitungan apa pun.
Mengapa ada waktu yang direkam untuk
str.find
dan
list.append
? Saat melakukan operasi seperti itu, program tidak perlu menunggu, sehingga penjelasannya tampak masuk akal, yang menurutnya kita berurusan dengan situasi di mana seluruh proses tidak dilakukan. Mungkin - menunggu penyelesaian beberapa proses lain, atau menunggu penyelesaian memuat data ke dalam memori dari file swap. Ini menunjukkan bahwa beberapa waktu dihabiskan untuk melakukan operasi ini, yang bukan bagian dari waktu prosesor.
Selain itu, saya ingin mencatat bahwa saya telah melihat laporan yang berisi fragmen kecil waktu yang negatif. Ini menyiratkan perbedaan tertentu antara waktu yang berlalu dan waktu prosesor, tetapi saya tidak berharap ini memiliki dampak signifikan pada analisis program yang lebih kompleks.
Metode nomor 2: analisis jumlah sakelar konteks yang disengaja
Masalah dengan mengukur waktu yang dihabiskan oleh program untuk menunggu sesuatu adalah bahwa, ketika melakukan sesi pengukuran yang berbeda untuk program yang sama, itu dapat bervariasi karena sesuatu yang berada di luar ruang lingkup program. Terkadang permintaan DNS bisa lebih lambat dari biasanya. Terkadang, lebih lambat dari biasanya, beberapa data dapat dimuat. Oleh karena itu, akan berguna untuk menggunakan beberapa indikator yang lebih dapat diprediksi yang tidak terikat dengan kecepatan apa yang mengelilingi program.
Salah satu cara untuk melakukan ini adalah menghitung berapa banyak operasi yang harus menunggu telah menyelesaikan proses. Artinya, kita berbicara tentang menghitung jumlah periode menunggu, dan bukan waktu yang dihabiskan untuk menunggu sesuatu.
Suatu proses dapat berhenti menggunakan sumber daya prosesor karena dua alasan:
- Setiap kali suatu proses melakukan operasi yang tidak berakhir secara instan, misalnya, ia membaca data dari soket, berhenti, dan seterusnya, ini setara dengan apa yang dikatakannya ke sistem operasi: "Bangunkan saya ketika saya dapat terus bekerja." Inilah yang disebut "sakelar konteks yang disengaja": prosesor dapat beralih ke proses lain hingga data muncul pada soket, atau hingga proses kami keluar dari mode siaga, serta dalam kasus serupa lainnya.
- "Peralihan konteks yang tidak disengaja" adalah situasi di mana sistem operasi menghentikan sementara proses, memungkinkan proses lain untuk mengambil keuntungan dari sumber daya prosesor.
Kami akan mengubah profil konteks yang disengaja.
Mari kita menulis profiler yang menghitung sakelar konteks yang disengaja menggunakan pustaka
psutil
:
import psutil _current_process = psutil.Process() def profile_voluntary_switches(f, *args, **kwargs): prof = cProfile.Profile( lambda: _current_process.num_ctx_switches().voluntary) prof.runcall(f, *args, **kwargs) result = pstats.Stats(prof) result.sort_stats("time") result.print_stats()
Sekarang, mari buat profil kode yang berfungsi dengan jaringan lagi:
>>> profile_voluntary_switches( ... lambda: urlopen("https://pythonspeed.com").read()) ncalls tottime percall filename:lineno(function) 3 7.000 2.333 _ssl._SSLSocket.read 1 2.000 2.000 _ssl._SSLSocket.do_handshake 1 2.000 2.000 _socket.getaddrinfo 1 1.000 1.000 _ssl._SSLContext.set_default_verify_path 1 1.000 1.000 _socket.socket.connect
Sekarang, alih-alih data waktu tunggu, kita dapat melihat informasi tentang jumlah sakelar konteks yang disengaja yang terjadi.
Perhatikan bahwa Anda terkadang dapat melihat sakelar konteks yang disengaja di tempat yang tidak terduga. Saya percaya ini terjadi ketika data dari file halaman sedang dimuat karena kesalahan halaman memori.
Ringkasan
Menggunakan teknik kode profil yang dijelaskan di sini menciptakan beban tambahan tertentu pada sistem, yang sangat memperlambat program. Namun, dalam kebanyakan kasus, ini tidak boleh mengarah pada distorsi yang signifikan dari hasil karena fakta bahwa kami tidak menganalisis penggunaan sumber daya prosesor.
Secara umum, dapat dicatat bahwa setiap indikator terukur yang terkait dengan pekerjaan program cocok untuk profiling. Misalnya, berikut ini:
- Jumlah
psutil.Process().read_count
( psutil.Process().read_count
) dan menulis ( psutil.Process().write_count
). - Di Linux, jumlah total byte yang dibaca dan ditulis (psutil.
Process().read_chars
). - Indikator alokasi memori (melakukan analisis semacam itu akan membutuhkan upaya; ini dapat dilakukan dengan menggunakan jemalloc ).
Rincian dua item pertama dari daftar ini dapat ditemukan di dokumentasi
psutil .
Pembaca yang budiman! Bagaimana Anda membuat profil aplikasi Python Anda?
