Rumah >pembangunan bahagian belakang >Tutorial Python >99% orang tidak tahu! Sambungan Python, C, C, perbandingan perbezaan Cython!

99% orang tidak tahu! Sambungan Python, C, C, perbandingan perbezaan Cython!

WBOY
WBOYke hadapan
2023-04-14 17:40:032003semak imbas

99% orang tidak tahu! Sambungan Python, C, C, perbandingan perbezaan Cython!

Mari kita ambil jujukan Fibonacci yang mudah sebagai contoh untuk menguji perbezaan dalam kecekapan pelaksanaannya.

Kod Python:

def fib(n):
a, b = 0.0, 1.0
for i in range(n):
a, b = a + b, a
return a

Kod C:

double cfib(int n) {
int i;
double a=0.0, b=1.0, tmp;
for (i=0; i<n; ++i) {
tmp = a; a = a + b; b = tmp;
}
return a;
}

Di atas ialah jujukan Fibonacci yang dilaksanakan dalam C. Sesetengah orang mungkin tertanya-tanya mengapa kami menggunakan jenis titik terapung dan bukannya integer? Jawapannya ialah jenis integer C mempunyai julat, jadi kami menggunakan double, dan float Python sepadan dengan PyFloatObject di lapisan bawah, yang juga disimpan secara dalaman dengan double.

Sambungan C:

Kemudian sambungan C, nota: Sambungan C bukan tumpuan kami, menulis sambungan C The Intipati penulisan Cython adalah sama, menulis modul sambungan untuk Python, tetapi menulis Cython pastinya lebih mudah daripada menulis sambungan C.

#include "Python.h"

double cfib(int n) {
int i;
double a=0.0, b=1.0, tmp;
for (i=0; i<n; ++i) {
tmp = a; a = a + b; b = tmp;
}
return a;
}

static PyObject *fib(PyObject *self, PyObject *n) {
if (!PyLong_CheckExact(n)) {
wchar_t *error = L"函数 fib 需要接收一个整数";
PyErr_SetObject(PyExc_ValueError,
PyUnicode_FromWideChar(error, wcslen(error)));
return NULL;
}
double result = cfib(PyLong_AsLong(n));
return PyFloat_FromDouble(result);
}

static PyMethodDef methods[] = {
{"fib",
 (PyCFunction) fib,
 METH_O,
 "这是 fib 函数"},
 {NULL, NULL, 0, NULL}
};

static PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"c_extension",
"这是模块 c_extension",
-1,
methods,
NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC PyInit_c_extension(void) {
return PyModule_Create(&module);
}

Anda boleh melihat bahawa jika anda menulis sambungan C, walaupun Fibonacci yang ringkas adalah sangat rumit.

Kod Cython:

Akhirnya lihat cara menggunakan Cython untuk menulis Fibonacci, anda Apa yang dilakukan anda fikir kod yang ditulis dalam Cython sepatutnya kelihatan seperti?

def fib(int n):
cdef int i
cdef double a = 0.0, b = 1.0
for i in range(n):
a, b = a + b, a
return a

Bagaimana pula, adakah kod Cython dan kod Python sangat serupa? Walaupun kami belum mempelajari sintaks Cython secara rasmi lagi, anda sepatutnya dapat meneka maksud kod di atas. Kami menentukan pembolehubah peringkat C menggunakan kata kunci cdef dan mengisytiharkan jenisnya.

Kod Cython mesti disusun menjadi modul sambungan sebelum ia boleh dikenali oleh jurubahasa, jadi ia perlu diterjemahkan ke dalam kod C terlebih dahulu dan kemudian disusun menjadi modul sambungan. Sekali lagi, pada dasarnya tiada perbezaan antara menulis sambungan C dan menulis kod Cython juga perlu diterjemahkan ke dalam kod C.

Tetapi adalah jelas bahawa menulis Cython adalah lebih mudah daripada menulis sambungan C Jika kualiti kod Cython yang ditulis adalah tinggi, kualiti kod C yang diterjemahkan juga akan menjadi tinggi. dan terjemahan Proses ini juga secara automatik akan menjalankan pengoptimuman maksimum. Tetapi jika ia adalah sambungan C tulisan tangan, maka semua pengoptimuman mesti dikendalikan secara manual oleh pembangun, apatah lagi apabila fungsinya rumit, menulis sambungan C itu sendiri adalah sakit kepala.

Kenapa Cython boleh mempercepatkan?

Melihat pada kod Cython, berbanding dengan Python Fibonacci tulen, kita melihat bahawa perbezaan nampaknya adalah jenis pembolehubah i, a, dan b telah ditentukan terlebih dahulu. Kuncinya ialah Mengapa ini mempunyai kesan pecutan (walaupun belum diuji lagi, kelajuannya pasti akan meningkat, jika tidak, tidak perlu belajar Cython).

Tetapi sebabnya di sini, kerana semua pembolehubah dalam Python ialah penunjuk generik PyObject *. PyObject (struktur dalam C) mempunyai dua ahli dalaman, iaitu ob_refcnt: yang menyimpan kiraan rujukan objek, dan ob_type *: yang menyimpan penunjuk jenis objek.

Sama ada integer, nombor titik terapung, rentetan, tuple, kamus atau apa-apa lagi, semua pembolehubah yang menunjuk kepadanya ialah PyObject *. Apabila beroperasi, anda mesti mendapatkan penuding jenis yang sepadan terlebih dahulu melalui -> ob_type dan kemudian melakukan penukaran.

Sebagai contoh, a dan b dalam kod Python, kita tahu bahawa tidak kira tahap gelung yang dilakukan, hasilnya menunjukkan kepada nombor titik terapung, tetapi penterjemah tidak akan membuat inferens ini. Setiap penambahan mesti dikesan untuk menentukan jenisnya dan ditukar; kemudian apabila penambahan dilakukan, pergi ke kaedah __add__ dalaman untuk menambah dua objek dan mencipta objek baru selepas pelaksanaan selesai, Tukar penunjuk kepada yang baru ini objek kepada PyObject * dan kembali.

Dan objek Python memperuntukkan ruang pada timbunan, ditambah dengan a dan b tidak boleh diubah, jadi setiap kitaran akan mencipta objek baharu dan mengitar semula objek sebelumnya Kalah.

Semua perkara di atas menjadikannya mustahil untuk kecekapan pelaksanaan kod Python menjadi tinggi Walaupun Python juga menyediakan kumpulan memori dan mekanisme caching yang sepadan, ia jelas masih tidak dapat menahannya kecekapan rendah.

Mengapa Cython boleh memecut, kita akan bercakap mengenainya kemudian.

Perbezaan Kecekapan

Jadi apakah perbezaan kecekapan antara mereka? Mari gunakan jadual untuk membandingkan:

99% orang tidak tahu! Sambungan Python, C, C, perbandingan perbezaan Cython!

Faktor peningkatan merujuk kepada berapa kali kecekapan dipertingkatkan berbanding Python tulen.

Lajur kedua ialah fib(0). Jelas sekali ia sebenarnya tidak memasuki gelung fib(0) mengukur overhed yang diperlukan untuk memanggil fungsi. Lajur kedua terakhir "Penggunaan masa badan gelung" merujuk kepada masa yang dibelanjakan untuk melaksanakan badan gelung dalam apabila melaksanakan fib(90), tidak termasuk overhed panggilan fungsi itu sendiri.

Secara keseluruhannya, Fibonacci yang ditulis dalam bahasa C tulen sudah pasti yang terpantas, tetapi terdapat banyak perkara yang patut difikirkan.

Pure Python

Seperti yang dijangkakan, ia adalah pemain yang paling teruk dalam semua aspek satu. Berdasarkan fib(0), memanggil fungsi mengambil masa 590 nanosaat, yang jauh lebih perlahan daripada C. Sebabnya ialah Python perlu mencipta bingkai tindanan apabila memanggil fungsi, dan bingkai tindanan ini diperuntukkan pada heap , dan selepas itu akhirnya, ia juga melibatkan pemusnahan bingkai tindanan dan sebagainya. Bagi fib(90), jelas tiada analisis diperlukan.

C Pure

Jelas sekali tiada interaksi dengan runtime Python pada masa ini, jadi penggunaan prestasi adalah minima. fib(0) menunjukkan bahawa memanggil fungsi dalam C hanya mengambil masa 2 nanosaat; fib(90) menunjukkan bahawa melaksanakan gelung, C hampir 80 kali lebih cepat daripada Python.

sambungan C

Apa yang dilakukan oleh sambungan C adalah seperti yang dinyatakan di atas, ia adalah menggunakan C untuk menulis sambungan untuk Modul Python. Kami melihat penggunaan masa badan gelung dan mendapati sambungan C hampir sama dengan C tulen. Perbezaannya ialah lebih banyak masa dibelanjakan untuk panggilan fungsi. Sebabnya ialah apabila kita memanggil fungsi modul sambungan, kita perlu terlebih dahulu menukar data Python kepada data C, kemudian menggunakan fungsi C untuk mengira jujukan Fibonacci, dan kemudian menukar data C kepada data Python.

Jadi sambungan C pada asasnya adalah bahasa C, tetapi ia perlu mengikut spesifikasi API yang disediakan oleh CPython semasa menulis, supaya kod C boleh disusun ke dalam fail pyd dan biarkan terus Python lakukan panggilan itu. Dari sudut pandangan keputusan, ia adalah perkara yang sama seperti Cython. Tetapi sekali lagi, menulis sambungan dalam C pada asasnya menulis C, dan anda juga perlu biasa dengan Python/C API yang mendasari, yang agak sukar.

Cython

Jika anda melihat masa badan gelung sahaja, sambungan C, C tulen dan Cython adalah semuanya hampir sama, tetapi menulis dalam Cython jelas adalah yang paling mudah. Kami mengatakan bahawa apa yang Cython lakukan pada asasnya serupa dengan sambungan C Kedua-duanya menyediakan modul sambungan untuk Python Perbezaannya ialah satu menulis kod C secara manual, dan satu lagi ialah menulis kod Cython dan kemudian menterjemahkannya ke dalam kod C. Oleh itu, untuk Cython, proses menukar data Python kepada data C, melakukan pengiraan, dan kemudian menukar semula data Python tidak dapat dielakkan.

Tetapi kami melihat bahawa Cython mengambil masa yang lebih singkat apabila memanggil fungsi daripada sambungan C Sebab utama ialah kod C yang dihasilkan oleh Cython sangat dioptimumkan. Tetapi sejujurnya, kita tidak perlu terlalu mengambil berat tentang masa yang diperlukan untuk panggilan fungsi Apa yang perlu kita beri perhatian ialah masa yang diperlukan untuk blok kod dalaman dilaksanakan. Sudah tentu, kita juga akan bercakap tentang cara mengurangkan overhed panggilan fungsi itu sendiri kemudian.

Mengapa Python untuk gelung begitu perlahan?

Melalui penggunaan masa badan gelung, kita dapat melihat bahawa gelung Python untuk benar-benar terkenal perlahan, jadi apakah sebabnya? Mari kita analisanya.

1 Mekanisme gelung Python

Apabila Python melintasi objek boleh lelar, ia akan memanggil kaedah __iter__ terlebih dahulu. di dalam objek boleh lelar mengembalikan lelarannya yang sepadan; kemudian ia terus memanggil kaedah __next__ lelaran untuk mengulangi nilai satu demi satu sehingga lelaran melemparkan pengecualian StopIteration, yang ditangkap oleh gelung untuk dan menamatkan gelung.

Dan iterator adalah stateful, dan penterjemah Python perlu merekodkan status lelaran iterator pada setiap masa.

2. Operasi aritmetik dalam Python

Kami sebenarnya telah menyebut perkara ini di atas disebabkan ciri dinamiknya sendiri, Python tidak boleh melakukan sebarang pengoptimuman berasaskan jenis.

Contohnya: a + b dalam badan gelung, a dan b ini boleh menunjuk kepada integer, nombor titik terapung, rentetan, tupel, senarai atau pun kaedah ajaib yang kami laksanakan __add__ objek contoh kelas, dan sebagainya.

Walaupun kita tahu ia adalah nombor titik terapung, Python tidak membuat andaian ini, jadi setiap kali a + b dilaksanakan, apakah jenisnya? Kemudian tentukan sama ada terdapat kaedah __add__ secara dalaman Jika ya, buat panggilan dengan a dan b sebagai parameter untuk menambah objek yang ditunjuk oleh a dan b. Selepas keputusan dikira, penunjuknya ditukar menjadi PyObject * dan dikembalikan.

Untuk C dan Cython, apabila mencipta pembolehubah, jenis ditentukan terlebih dahulu sebagai dua kali ganda, bukan yang lain, jadi a + b yang disusun hanyalah arahan mesin yang mudah . Sebagai perbandingan, bagaimana Python tidak boleh perlahan?

3. Peruntukan memori objek Python

Objek Python diperuntukkan pada timbunan, kerana objek Python Pada asasnya, Fungsi malloc C digunakan untuk sekeping memori dalam kawasan timbunan untuk struktur. Memperuntukkan dan melepaskan memori di kawasan timbunan memerlukan banyak wang, tetapi timbunan jauh lebih kecil, dan ia diselenggara oleh sistem pengendalian dan akan dikitar semula secara automatik, yang sangat cekap Peruntukan dan pelepasan memori pada timbunan sahaja memerlukan satu langkah sahaja.

Tetapi heap jelas tidak mempunyai rawatan ini, dan objek Python semuanya diperuntukkan pada heap Walaupun Python memperkenalkan mekanisme kumpulan memori, ia mengelakkan interaksi yang kerap dengan sistem pengendalian kepada a tahap tertentu Interaksi, dan juga memperkenalkan kumpulan objek integer kecil, mekanisme intern rentetan, kolam cache, dll.

Tetapi sebenarnya, apabila ia berkaitan dengan penciptaan dan pemusnahan objek (sebarang objek, termasuk skalar), terdapat peningkatan dalam overhed memori yang diperuntukkan secara dinamik dan subsistem memori Python . Objek apungan tidak boleh diubah, jadi ia akan dicipta dan dimusnahkan setiap kali ia gelung, jadi kecekapan masih tidak tinggi.

Dan pembolehubah yang diberikan oleh Cython (apabila jenisnya adalah jenis dalam C), mereka bukan lagi penunjuk (Pembolehubah Python ialah penunjuk), untuk arus a dan b Dalam erti kata lain , ia ialah nombor titik terapung berketepatan dua yang diperuntukkan pada tindanan. Kecekapan peruntukan pada timbunan jauh lebih tinggi daripada timbunan, jadi ia sangat sesuai untuk gelung, jadi kecekapan jauh lebih tinggi daripada Python. Di samping itu, bukan sahaja peruntukan, tetapi juga apabila menangani, timbunan adalah lebih cekap daripada timbunan.

Jadi tidak menghairankan bahawa C dan Cython adalah tertib magnitud lebih pantas daripada Python tulen apabila ia melibatkan gelung, memandangkan Python melakukan banyak kerja pada setiap lelaran.

Bila hendak menggunakan Cython?

Kami melihat bahawa dalam kod Cython, hanya menambah beberapa cdef boleh mencapai peningkatan prestasi yang begitu besar, yang jelas sangat mengujakan. Walau bagaimanapun, tidak semua kod Python akan mengalami peningkatan prestasi yang besar apabila ditulis dalam Cython.

Contoh jujukan Fibonacci yang kami ada di sini adalah sengaja, kerana data di dalam terikat pada CPU, dan masa jalan dibelanjakan untuk memproses beberapa pembolehubah dalam daftar CPU Tiada pergerakan data diperlukan . Jika fungsi ini melakukan kerja berikut:

  • Intensif memori, seperti menambah elemen pada tatasusunan yang besar; intensif , seperti membaca fail besar daripada cakera; 🎜> Kemudian
  • perbezaan antara Python, C, Cython mungkin berkurangan dengan ketara (untuk operasi intensif storan), malah hilang sepenuhnya (untuk operasi intensif I/O atau intensif rangkaian).
  • Apabila meningkatkan prestasi program Python adalah matlamat kami, prinsip Pareto banyak membantu kami, iaitu: 80% daripada masa berjalan program adalah Disebabkan oleh kod 20. Tetapi tanpa analisis yang teliti, sukar untuk mencari 20 peratus kod ini. Oleh itu, sebelum kami menggunakan Cython untuk meningkatkan prestasi, menganalisis logik perniagaan keseluruhan ialah langkah pertama. Jika kami menentukan bahawa kesesakan program disebabkan oleh rangkaian IO selepas analisis, maka kami tidak boleh mengharapkan Cython membawa peningkatan prestasi yang ketara. Oleh itu, sebelum anda menggunakan Cython, adalah perlu untuk menentukan terlebih dahulu apa yang menyebabkan kesesakan dalam program. Oleh itu, walaupun Cython adalah alat yang berkuasa, ia mesti digunakan dengan cara yang betul.

Selain itu, Cython memperkenalkan sistem jenis C ke dalam Python, jadi batasan jenis data C adalah perkara yang perlu kita perhatikan. Kita tahu bahawa integer Python tidak dihadkan oleh panjang, tetapi integer C adalah terhad, yang bermaksud bahawa integer tidak boleh mewakili integer ketepatan tak terhingga dengan betul.

Walau bagaimanapun, beberapa ciri Cython boleh membantu kami menangkap limpahan ini Secara ringkasnya, perkara yang paling penting ialah: Jenis data C lebih pantas daripada jenis data Python, tetapi ia tertakluk kepada pengehadan yang menjadikan mereka kurang fleksibel dan Universal. Dari sini kita juga dapat melihat bahawa Python memilih yang terakhir dari segi kelajuan, fleksibiliti, dan serba boleh.

Selain itu, pertimbangkan satu lagi ciri Cython: menyambung kepada kod luaran. Katakan titik permulaan kami bukan Python, tetapi C atau C++, dan kami mahu menggunakan Python untuk menyambung berbilang modul C atau C++. Cython memahami pengisytiharan C dan C++, dan ia boleh menjana kod yang sangat dioptimumkan, jadi ia lebih sesuai sebagai jambatan.

Memandangkan saya seorang master Python, jika ia melibatkan C dan C++, saya akan memperkenalkan cara untuk memperkenalkan C dan C++ ke dalam Cython dan terus memanggil perpustakaan C yang telah ditulis. Ia tidak akan memperkenalkan cara memperkenalkan Cython ke dalam C dan C++ sebagai jambatan yang menghubungkan berbilang modul C dan C++. Saya harap anda memahami perkara ini, kerana saya tidak menggunakan C atau C++ untuk menulis perkhidmatan, saya hanya akan menggunakannya untuk membantu Python meningkatkan kecekapan.

Ringkasan

Setakat ini, kami hanya memperkenalkan Cython dan terutamanya membincangkan kedudukannya dan hubungannya dengan perbezaan Python dan C.. Mengenai cara menggunakan Cython untuk mempercepatkan Python, cara menulis kod Cython, dan sintaks terperincinya, kami akan memperkenalkannya kemudian.

Ringkasnya, Cython ialah bahasa matang yang melayani Python. Kod Cython tidak boleh dilaksanakan secara langsung kerana ia tidak mematuhi peraturan sintaks Python.

Cara kami menggunakan Cython ialah: mula-mula menterjemah kod Cython ke dalam kod C, kemudian susun kod C ke dalam modul sambungan (fail pyd), dan kemudian importnya dalam kod Python dan panggil Kaedah berfungsi di dalam adalah cara yang betul dan sudah tentu satu-satunya cara untuk kita menggunakan Cython.

Sebagai contoh, Fibonacci yang kami tulis dalam Cython di atas akan melaporkan ralat jika dilaksanakan secara langsung, kerana cdef jelas tidak mematuhi peraturan sintaks Python. Oleh itu, kod Cython perlu disusun ke dalam modul sambungan dan kemudian diimport dalam fail py biasa Kepentingan ini adalah untuk meningkatkan kelajuan berjalan. Oleh itu, kod Cython mestilah kod intensif CPU, jika tidak, sukar untuk meningkatkan kecekapan dengan ketara.

Jadi sebelum menggunakan Cython, sebaiknya anda menganalisis logik perniagaan dengan teliti, atau jangan gunakan Cython buat masa ini dan tulis sepenuhnya dalam Python. Selepas penulisan selesai, mula menguji dan menganalisis prestasi program untuk melihat di mana ia mengambil lebih banyak masa, tetapi pada masa yang sama, ia boleh dioptimumkan melalui penaipan statik. Carinya, tulis semula dalam Cython, susunkannya ke dalam modul sambungan, dan kemudian panggil fungsi dalam modul sambungan.

Atas ialah kandungan terperinci 99% orang tidak tahu! Sambungan Python, C, C, perbandingan perbezaan Cython!. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:51cto.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam