+− THE DAILY DIFFdev & AI news
SHIP IT

Performa Python: Loop NumPy asli, callback, dan biaya konversi

Sebuah uji numerik terkontrol khusus CPU mengikuti nilai-nilai identik melalui loop CPython, aritmetika NumPy, "callback wrapper" Python, dan loop Numba yang sudah dikompilasi.

Want to see our stories first on Google? Add us to your preferred sources — it helps us grow.

Sebuah uji numerik terkontrol khusus CPU mengikuti nilai-nilai identik melalui loop CPython, aritmetika NumPy, "callback wrapper" Python, dan loop Numba yang sudah dikompilasi. NumPy dengan "input ready" memiliki keunggulan besar, sementara konversi dari dan kembali ke daftar Python mengurangi keuntungan itu hingga hampir seri. Episode ini menjelaskan batas eksekusi dan cara mengukur hasil yang dibutuhkan aplikasi.

Baca edisi tertulis (Inggris) ↗

Isi video ini

  • CPython mengkompilasi sumber ke bytecode. Pekerjaan interpreter/objek yang berulang dan loop numerik asli yang terketik adalah "arrangement" eksekusi yang berbeda.
  • Aritmetika numerik NumPy dapat menyerahkan "batch" yang terketik ke operasi yang dikompilasi; loop aritmetika masih ada, dan "view" dapat memiliki tata letak yang tidak "contiguous".
  • Ukur konversi dan representasi output yang dibutuhkan. Dalam "run" ini, mengkonversi daftar Python menjadi array dan kembali lagi menghilangkan keunggulan besar dari "array ready".
  • Beban kerja kecil dapat menguntungkan Python biasa karena biaya panggilan array yang tetap penting. "Comprehension" delapan nilai memenangkan perbandingan terukur ini.
  • np.vectorize adalah "wrapper" kemudahan yang memanggil Python untuk elemen-elemen berurutan; antarmuka "batch" saja tidak membangun eksekusi numerik asli.
  • Loop sintaks Python yang dikompilasi juga bisa cepat. Sertakan biaya kompilasi/penyiapan "cold" saat itu penting bagi pemanggil.
  • Eksekusi asli, SIMD CPU, "multithreading", dan "backend" GPU adalah ide-ide terpisah. Fitur CPU yang tersedia tidak melacak instruksi yang digunakan dalam "run" tertentu.

Transkrip terjemahan

Diterjemahkan dari narasi asli bahasa Inggris. Audio dan teks tersedia yang dikontrol oleh YouTube.

Mengapa CPU yang sama bisa selesai lebih cepat?

0:00 Anda berpikir Python membutuhkan bahasa baru agar cepat. CPU yang sama menjalankan "batch" kami lebih cepat setelah "loop" dipindahkan. Ke mana pekerjaan itu pergi? Kapan percepatan itu menghilang? Jalan pintas mana yang mengembalikan Python? Ini adalah The Daily Diff, di balik layar. Perhatikan jalan pintas itu. CPython adalah implementasi Python standar.

0:17 Ini mengkompilasi sumber Anda menjadi "bytecode", lalu menjalankan instruksi-instruksi tersebut dalam sebuah interpreter. NumPy adalah perpustakaan numerik yang "array"-nya menyimpan nilai-nilai dengan tipe data yang dideklarasikan. Aritmetika bawaannya dapat menyerahkan seluruh "batch" ke operasi yang dikompilasi. Ambil satu juta nilai "floating point", kuadratkan masing-masing, lalu tambahkan nilai aslinya. Setiap versi mendapatkan nilai-nilai identik dan menghasilkan jawaban yang cocok. Nilai-nilai cukup kecil untuk menjaga perbedaan pembulatan keluar dari "race".

0:40 Di mesin ini, "comprehension" membutuhkan sekitar tiga puluh tiga milidetik. NumPy membutuhkan kurang dari satu. Keduanya berjalan di CPU. Itu adalah bukti yang berguna untuk beban kerja ini, dan sebuah obituari yang mengerikan untuk sebuah bahasa pemrograman. Mulai dengan loop biasa.

Pekerjaan apa yang diulang oleh loop Python?

0:53 Python mendapatkan nilai berikutnya, melakukan aritmetika, dan menambahkan jawabannya. Lalu ia melakukannya lagi. Perkalian itu murah. Mengatur perkalian berikutnya, dan menangani objek Python di sekitarnya, menambah pekerjaan setiap saat. Ini adalah "disassembly" sebenarnya dari fungsi yang diuji. Iterasi, perkalian, dan penambahan muncul sebagai instruksi "bytecode". Versi dan interpreter Python membuat pilihan yang berbeda.

1:14 Waktu-waktu ini termasuk versi di layar, yaitu Python tiga titik sepuluh. Daftar Python menyimpan referensi ke objek, sehingga berbagai jenis hal dapat berbagi daftar. Pekerjaan aritmetika kami menggunakan angka di seluruhnya. Ini membayar untuk pengaturan umum sambil meminta layanan yang berulang. Bayangkan memesan kopi yang sama satu juta kali dan mengisi formulir baru untuk setiap cangkir. Mesin kopi baik-baik saja.

1:36 Pekerjaan kertas telah mengembangkan karirnya sendiri.

Ke mana NumPy memindahkan loop?

1:38 NumPy mengubah kontraknya. "Array" ini menyimpan nilai "floating point" berukuran tetap dengan satu tipe yang dideklarasikan. "Array" kami "contiguous", sehingga nilai-nilainya berdekatan di memori. "View" dapat memiliki tata letak lain. Operasi aritmetika tahu jenis data apa yang diterimanya. Panggilan menyerahkan "batch" ke operasi asli. Sebuah "loop" masih mengunjungi nilai-nilai di dalam kode yang dikompilasi. Ia dapat menggunakan kembali pengetahuan tentang tipe tersebut dalam pekerjaan itu.

2:00 Python tetap menjadi orang yang menyerahkan pekerjaan itu. Itamar Turner Trauring menjelaskan perbedaannya dalam Python Speed. Antarmuka "batch", implementasi asli, dan instruksi vektor prosesor adalah hal-hal terpisah. Nama fungsi memberikan sangat sedikit bukti.

Apakah cepat berarti GPU atau banyak core CPU?

2:13 Hasil ini menggunakan NumPy di CPU. "Backend" GPU seperti CuPy memilih rumah yang berbeda untuk "array" dan operasi. Membeli GPU meninggalkan "loop" Python biasa ini berjalan di tempat ia sudah berjalan. Instruksi vektor CPU dapat memproses beberapa nilai secara bersamaan. NumPy memiliki mekanisme untuk memilih kernel yang didukung. "Runtime" kami melaporkan fitur yang tersedia, tetapi tidak melacak instruksi ekspresi ini. Memberikan seluruh peningkatan yang terukur ke instruksi vektor akan melebih-lebihkan bukti.

2:40 Keranjang belanja GPU bisa tetap tertutup. Ada sesuatu yang sangat memuaskan tentang perbaikan kinerja yang juga membatalkan pembelian. Sayangnya, "benchmark" berlanjut.

Kapan percepatan yang mengesankan itu menghilang?

2:49 Dengan delapan nilai, "comprehension" menang. Memanggil mekanisme "array" membutuhkan biaya lebih dari pekerjaan kecil itu. "Array ready" yang lebih besar unggul. Kami menggunakan percobaan berulang dan proses yang dihangatkan. "Build" Python yang lebih baru dapat mengubah "crossover". Kemudian ubah apa yang dibutuhkan aplikasi. Mulai dengan daftar Python dan akhiri dengan daftar Python yang baru. NumPy harus mengubah input menjadi "array" dan mengembalikan jawabannya menjadi objek

3:11 Python. Langkah-langkah itu termasuk dalam tagihan jika aplikasi Anda membutuhkannya. Pada satu juta nilai, jalur lengkap itu membutuhkan sekitar dua puluh delapan milidetik. "Comprehension" juga membutuhkan sekitar dua puluh delapan. Keunggulan dramatis kami menjadi hampir seri. Bagian yang cepat itu nyata. Keunggulan itu tergantung pada membiarkan data tetap dalam bentuk numeriknya.

Batas mana yang termasuk dalam benchmark?

3:28 Ikuti datanya. Jika operasi berikutnya menerima "array", pertahankan "array" itu. Jika Anda berulang kali mengkonversi antara daftar dan "array", ukur persilangan tersebut. Berhenti sebelum penyerahan yang mahal memberikan angka yang akurat untuk pertanyaan yang salah. Mengalikan "array" ini menciptakan hasil sementara. Menambahkan nilai-nilai asli menciptakan output. Eksekusi asli masih membutuhkan memori dan memindahkan data.

3:47 Beberapa operasi menerima "buffer" output, mengurangi alokasi ketika kode Anda dapat menggunakannya kembali dengan aman. Satu baris cantik dapat memesan beberapa "buffer" besar. Python Speed juga menyebutkan masalah ini. Alokator sangat tidak peduli dengan estetika Anda.

Bisakah loop Python yang dikompilasi juga cepat?

4:00 Anda juga dapat mempertahankan sintaks "loop" dan mengubah eksekusinya. Numba dapat mengkompilasi fungsi numerik Python yang didukung menjadi kode asli. Ini adalah "loop" dari pengujian kami dengan "compiler decorator"-nya. Ini masih mengatakan "for". Jalur interpreter yang mahal telah berubah. Setelah kompilasi, "loop" ini membutuhkan kurang dari setengah milidetik dengan "array ready". Kompilasi itu sendiri membutuhkan sekitar satu detik dalam "run" ini. Biaya penyiapan itu dikecualikan dari waktu yang berulang,

4:22 jadi pekerjaan kecil yang hanya sekali pakai membutuhkan perbandingan yang berbeda. Panduan Numba sendiri senang menunjukkan "loop" eksplisit. Memulai migrasi bahasa sebelum menemukan "hot path" adalah cara yang rumit untuk menghindari membuka "profiler". Pertama-tama cari apa yang menghabiskan waktu. Penundaan jaringan, "parsing" objek, dan aritmetika massal membutuhkan perbaikan yang berbeda. Operasi "array" asli membantu ketika perhitungan dan data cocok.

4:43 Ukur hasil yang sebenarnya dibutuhkan pemanggil Anda. Sekarang untuk jalan pintas itu. Anda memiliki fungsi Python dan ingin fungsi itu menerima "array". Sebuah "wrapper" bernama "vectorize" terdengar seperti tombol kinerja yang hilang. Dokumentasinya memiliki kalimat penting yang menunggu.

Apa yang sebenarnya dieksekusi oleh np.vectorize?

4:56 NumPy mengatakan "wrapper vectorize"-nya disediakan terutama untuk kenyamanan. Implementasinya pada dasarnya adalah "for loop". "Wrapper" tersebut menerima "batch", tetapi terus memanggil fungsi Python Anda untuk elemen-elemen berurutan. Antarmukanya menjadi lebih pendek. Batas eksekusi tetap sibuk. Saksikan nilai-nilai yang sama kembali melalui "callback" Python. Dengan "array ready", versi ini membutuhkan sekitar delapan puluh tujuh milidetik. Ini lebih lambat dari "comprehension" kami.

5:18 Sebuah "wrapper broadcasting" yang berguna dapat mempertahankan pekerjaan per elemen yang persis sama yang Anda coba hapus. Periksa implementasi di balik antarmuka "batch", pertahankan data yang kompatibel dalam bentuknya yang berguna, dan sertakan konversi dalam perbandingan. Loop asli, "callback", dan yang dikompilasi dapat terlihat serupa dari kursi pemanggil. Nama "vectorize" membuat janji yang tidak pernah ditandatangani oleh "stopwatch" Anda. Pertahankan jika kenyamanan adalah yang Anda butuhkan. Untuk kinerja, periksa "loop" yang berjalan.

Apa yang harus Anda ubah dalam kode Anda?

5:41 Keputusan saya adalah SHIP IT untuk mem-batch "hot path" numerik yang terukur. Pertahankan loop biasa jika sudah berfungsi dengan baik. Periksa di mana pekerjaan terjadi dan apa yang dibutuhkan pemanggil berikutnya. Berlangganan, tekan bel, dan beritahu saya di komentar jika Anda akan menganggapnya berbeda. Dan itulah "diff" untuk hari ini. Saya Niko dari Axrisi. Gabungkan dengan bertanggung jawab.

Sumber

  1. Universal functions (ufunc) basics — NumPy 2.2NumPy
  2. The N-dimensional array — NumPy 2.2NumPy
  3. numpy.vectorize — convenience and performance notesNumPy
  4. CPU/SIMD optimizations — NumPy 2.2NumPy
  5. dis — Disassembler for Python bytecodePython Software Foundation
  6. CPython 3.10.12 list representationCPython contributors
  7. The limits of Python vectorization as a performance techniqueItamar Turner-Trauring / Python⇒Speed
  8. Performance TipsNumba
  9. Basics of CuPyCuPy

Video terkait